From 202bf0bedea34e855396f61a9a547b6e04927464 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 15 Oct 2018 23:36:40 -0700 Subject: [PATCH] Tab complete abbreviations This allows abbreviations to be expanded by tab completions. Fixes #3233 --- src/complete.cpp | 47 +++++++++++++++++++++++++++++++++++----------- src/expand.cpp | 9 +++++++-- src/expand.h | 4 ++++ src/fish_tests.cpp | 19 +++++++++++++++++-- 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/complete.cpp b/src/complete.cpp index e5f6673c0..38c9576fa 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -58,6 +58,9 @@ /// Description for short variables. The value is concatenated to this description. #define COMPLETE_VAR_DESC_VAL _(L"Variable: %ls") +/// Description for abbreviations. +#define ABBR_DESC _(L"Abbreviation: %ls") + /// The special cased translation macro for completions. The empty string needs to be special cased, /// since it can occur, and should not be translated. (Gettext returns the version information as /// the response). @@ -337,6 +340,9 @@ class completer_t { void complete_cmd(const wcstring &str, bool use_function, bool use_builtin, bool use_command, bool use_implicit_cd); + /// Attempt to complete an abbreviation for the given string. + void complete_abbr(const wcstring &str); + void complete_from_args(const wcstring &str, const wcstring &args, const wcstring &desc, complete_flags_t flags); @@ -539,12 +545,10 @@ void completer_t::complete_strings(const wcstring &wc_escaped, const description const wcstring wc = parse_util_unescape_wildcards(tmp); - for (size_t i = 0; i < possible_comp.size(); i++) { - wcstring temp = possible_comp.at(i).completion; - const wchar_t *next_str = temp.empty() ? NULL : temp.c_str(); - - if (next_str) { - wildcard_complete(next_str, wc.c_str(), desc_func, &this->completions, + for (const auto &comp : possible_comp) { + const wcstring &comp_str = comp.completion; + if (!comp_str.empty()) { + wildcard_complete(comp_str, wc.c_str(), desc_func, &this->completions, this->expand_flags(), flags); } } @@ -694,6 +698,22 @@ void completer_t::complete_cmd(const wcstring &str_cmd, bool use_function, bool } } +void completer_t::complete_abbr(const wcstring &cmd) { + std::map abbrs = get_abbreviations(); + std::vector possible_comp; + possible_comp.reserve(abbrs.size()); + for (const auto &kv : abbrs) { + possible_comp.emplace_back(kv.first); + } + + auto desc_func = [&](const wcstring &key) { + auto iter = abbrs.find(key); + assert(iter != abbrs.end() && "Abbreviation not found"); + return format_string(ABBR_DESC, iter->second.c_str()); + }; + this->complete_strings(cmd, desc_func, possible_comp, COMPLETE_NO_SPACE); +} + /// Evaluate the argument list (as supplied by complete -a) and insert any /// return matching completions. Matching is done using @c /// copy_strings_with_prefix, meaning the completion may contain wildcards. @@ -1353,11 +1373,6 @@ static maybe_t find_argument_containing_position(const arg_list_t &args, void completer_t::perform() { wcstring current_command; const size_t pos = cmd.size(); - bool use_command = 1; - bool use_function = 1; - bool use_builtin = 1; - bool use_implicit_cd = 1; - // debug( 1, L"Complete '%ls'", cmd.c_str() ); const wchar_t *tok_begin = nullptr; @@ -1421,6 +1436,12 @@ void completer_t::perform() { } else { assert(plain_statement && plain_statement.has_source()); + bool use_command = true; + bool use_function = true; + bool use_builtin = true; + bool use_implicit_cd = true; + bool use_abbr = true; + // Get the command node. tnode_t cmd_node = plain_statement.child<0>(); assert(cmd_node && cmd_node.has_source() && "Expected command node to be valid"); @@ -1435,6 +1456,7 @@ void completer_t::perform() { use_function = true; use_builtin = true; use_implicit_cd = true; + use_abbr = true; break; } case parse_statement_decoration_command: @@ -1443,6 +1465,7 @@ void completer_t::perform() { use_function = false; use_builtin = false; use_implicit_cd = false; + use_abbr = false; break; } case parse_statement_decoration_builtin: { @@ -1450,6 +1473,7 @@ void completer_t::perform() { use_function = false; use_builtin = true; use_implicit_cd = false; + use_abbr = false; break; } } @@ -1457,6 +1481,7 @@ void completer_t::perform() { if (cmd_node.location_in_or_at_end_of_source_range(pos)) { // Complete command filename. complete_cmd(current_token, use_function, use_builtin, use_command, use_implicit_cd); + if (use_abbr) complete_abbr(current_token); } else { // Get all the arguments. arg_list_t all_arguments = plain_statement.descendants(); diff --git a/src/expand.cpp b/src/expand.cpp index 97ed27dc3..a0e2dd7ef 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -27,9 +27,9 @@ #include #include +#include #include // IWYU pragma: keep #include -#include #include #include @@ -1176,7 +1176,7 @@ bool fish_xdm_login_hack_hack_hack_hack(std::vector *cmds, int argc return result; } -static owning_lock> s_abbreviations; +static owning_lock> s_abbreviations; void update_abbr_cache(const wchar_t *op, const wcstring &varname) { wcstring abbr; if (!unescape_string(varname.substr(wcslen(L"_fish_abbr_")), &abbr, 0, STRING_STYLE_VAR)) { @@ -1202,3 +1202,8 @@ bool expand_abbreviation(const wcstring &src, wcstring *output) { if (output != NULL) output->assign(abbr->second); return true; } + +std::map get_abbreviations() { + auto abbreviations = s_abbreviations.acquire(); + return *abbreviations; +} diff --git a/src/expand.h b/src/expand.h index da11a03e0..f92350a1c 100644 --- a/src/expand.h +++ b/src/expand.h @@ -9,6 +9,7 @@ #include +#include #include #include @@ -151,6 +152,9 @@ wcstring replace_home_directory_with_tilde(const wcstring &str); void update_abbr_cache(const wchar_t *op, const wcstring &varname); bool expand_abbreviation(const wcstring &src, wcstring *output); +/// \return a snapshot of all abbreviations as a map abbreviation->expansion. +std::map get_abbreviations(); + // Terrible hacks bool fish_xdm_login_hack_hack_hack_hack(std::vector *cmds, int argc, const char *const *argv); diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 6a9159aeb..80e997e8e 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -1738,8 +1738,8 @@ static void test_abbreviations() { {L"foo", L"bar"}, {L"gx", L"git checkout"}, }; - for (auto it : abbreviations) { - int ret = env_set_one(L"_fish_abbr_" + it.first, ENV_LOCAL, it.second); + for (const auto &kv : abbreviations) { + int ret = env_set_one(L"_fish_abbr_" + kv.first, ENV_LOCAL, kv.second); if (ret != 0) err(L"Unable to set abbreviation variable"); } @@ -2458,6 +2458,21 @@ static void test_complete() { completions.clear(); complete_set_variable_names(NULL); + // Test abbreviations. + function_data_t fd; + fd.name = L"testabbrsonetwothreefour"; + function_add(fd, parser_t::principal_parser()); + int ret = env_set_one(L"_fish_abbr_testabbrsonetwothreezero", ENV_LOCAL, L"expansion"); + complete(L"testabbrsonetwothree", &completions, COMPLETION_REQUEST_DEFAULT); + do_test(ret == 0); + do_test(completions.size() == 2); + do_test(completions.at(0).completion == L"four"); + do_test((completions.at(0).flags & COMPLETE_NO_SPACE) == 0); + // Abbreviations should not have a space after them. + do_test(completions.at(1).completion == L"zero"); + do_test((completions.at(1).flags & COMPLETE_NO_SPACE) != 0); + + // Test wraps. do_test(comma_join(complete_get_wrap_targets(L"wrapper1")) == L""); complete_add_wrapper(L"wrapper1", L"wrapper2");