From bcba5287ade17a23c47daad15a703c72f8c3ab88 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Mon, 23 Dec 2013 20:43:55 +0000 Subject: [PATCH] Add complete_command for completing commands in PATH use it for pipe completion --- src/file.cc | 130 ++++++++++++++++++++++++++++++++++++-------------- src/file.hh | 3 ++ src/normal.cc | 32 ++++++++++++- 3 files changed, 129 insertions(+), 36 deletions(-) diff --git a/src/file.cc b/src/file.cc index a003c68c..7df5abf4 100644 --- a/src/file.cc +++ b/src/file.cc @@ -251,13 +251,54 @@ String find_file(const String& filename, memoryview paths) return ""; } +template +std::vector list_files(const String& prefix, + const String& dirname, + Filter filter) +{ + kak_assert(dirname.empty() or dirname.back() == '/'); + DIR* dir = opendir(dirname.empty() ? "./" : dirname.c_str()); + auto closeDir = on_scope_end([=]{ closedir(dir); }); + + std::vector result; + if (not dir) + return result; + + std::vector subseq_result; + while (dirent* entry = readdir(dir)) + { + if (not filter(*entry)) + continue; + + String filename = entry->d_name; + if (filename.empty()) + continue; + + const bool match_prefix = prefix_match(filename, prefix); + const bool match_subseq = subsequence_match(filename, prefix); + if (match_prefix or match_subseq) + { + if (entry->d_type == DT_DIR) + filename += '/'; + if (prefix.length() != 0 or filename[0] != '.') + { + if (match_prefix) + result.push_back(filename); + if (match_subseq) + subseq_result.push_back(filename); + } + } + } + auto& real_result = result.empty() ? subseq_result : result; + return real_result; +} + std::vector complete_filename(const String& prefix, const Regex& ignored_regex, ByteCount cursor_pos) { String real_prefix = parse_filename(prefix.substr(0, cursor_pos)); - String dirname = "./"; - String dirprefix; + String dirname; String fileprefix = real_prefix; ByteCount dir_end = -1; @@ -269,49 +310,68 @@ std::vector complete_filename(const String& prefix, if (dir_end != -1) { dirname = real_prefix.substr(0, dir_end + 1); - dirprefix = dirname; fileprefix = real_prefix.substr(dir_end + 1); } - DIR* dir = opendir(dirname.c_str()); - auto closeDir = on_scope_end([=]{ closedir(dir); }); - - std::vector result; - if (not dir) - return result; - const bool check_ignored_regex = not ignored_regex.empty() and not boost::regex_match(fileprefix.c_str(), ignored_regex); - std::vector subseq_result; - while (dirent* entry = readdir(dir)) + auto filter = [&](const dirent& entry) { - String filename = entry->d_name; - if (filename.empty()) - continue; + return not check_ignored_regex or + not boost::regex_match(entry.d_name, ignored_regex); + }; + std::vector res = list_files(fileprefix, dirname, filter); + for (auto& file : res) + file = escape(dirname + file); + std::sort(res.begin(), res.end()); + return res; +} - if (check_ignored_regex and boost::regex_match(filename.c_str(), ignored_regex)) - continue; +std::vector complete_command(const String& prefix, ByteCount cursor_pos) +{ + String real_prefix = parse_filename(prefix.substr(0, cursor_pos)); + String dirname; + String fileprefix = real_prefix; - const bool match_prefix = prefix_match(filename, fileprefix); - const bool match_subseq = subsequence_match(filename, fileprefix); - if (match_prefix or match_subseq) - { - String name = dirprefix + filename; - if (entry->d_type == DT_DIR) - name += '/'; - if (fileprefix.length() != 0 or filename[0] != '.') - { - if (match_prefix) - result.push_back(escape(name)); - if (match_subseq) - subseq_result.push_back(escape(name)); - } - } + ByteCount dir_end = -1; + for (ByteCount i = 0; i < real_prefix.length(); ++i) + { + if (real_prefix[i] == '/') + dir_end = i; } - auto& real_result = result.empty() ? subseq_result : result; - std::sort(real_result.begin(), real_result.end()); - return real_result; + + std::vector path; + if (dir_end != -1) + { + path.emplace_back(real_prefix.substr(0, dir_end + 1)); + fileprefix = real_prefix.substr(dir_end + 1); + } + else + path = split(getenv("PATH"), ':'); + + std::vector res; + for (auto dirname : path) + { + if (not dirname.empty() and dirname.back() != '/') + dirname += '/'; + + auto filter = [&](const dirent& entry) { + struct stat st; + if (stat((dirname + entry.d_name).c_str(), &st)) + return false; + bool executable = (st.st_mode & S_IXUSR) + | (st.st_mode & S_IXGRP) + | (st.st_mode & S_IXOTH); + return S_ISREG(st.st_mode) and executable; + }; + auto completion = list_files(prefix, dirname, filter); + std::move(completion.begin(), completion.end(), std::back_inserter(res)); + } + std::sort(res.begin(), res.end()); + auto it = std::unique(res.begin(), res.end()); + res.erase(it, res.end()); + return res; } time_t get_fs_timestamp(const String& filename) diff --git a/src/file.hh b/src/file.hh index 5a5bddd6..818d6de1 100644 --- a/src/file.hh +++ b/src/file.hh @@ -38,6 +38,9 @@ time_t get_fs_timestamp(const String& filename); std::vector complete_filename(const String& prefix, const Regex& ignore_regex, ByteCount cursor_pos = -1); + +std::vector complete_command(const String& prefix, + ByteCount cursor_pos = -1); } #endif // file_hh_INCLUDED diff --git a/src/normal.cc b/src/normal.cc index e612f6f7..d042d9fa 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -420,7 +420,37 @@ void command(Context& context, int) void pipe(Context& context, int) { - context.input_handler().prompt("pipe:", get_color("Prompt"), complete_nothing, + auto completer = [](const Context& context, CompletionFlags flags, + const String& prefix, ByteCount cursor_pos) + { + ByteCount word_start = 0; + ByteCount word_end = 0; + + bool first = true; + const ByteCount len = prefix.length(); + for (ByteCount pos = 0; pos < cursor_pos;) + { + if (pos != 0) + first = false; + while (pos != len and is_blank(prefix[pos])) + ++pos; + word_start = pos; + while (pos != len and not is_blank(prefix[pos])) + ++pos; + word_end = pos; + } + Completions completions{word_start, word_end}; + if (first) + completions.candidates = complete_command(prefix.substr(word_start, word_end), + cursor_pos - word_start); + else + completions.candidates = complete_filename(prefix.substr(word_start, word_end), + context.options()["ignored_files"].get(), + cursor_pos - word_start); + return completions; + }; + + context.input_handler().prompt("pipe:", get_color("Prompt"), completer, [](const String& cmdline, PromptEvent event, Context& context) { if (event != PromptEvent::Validate)