Tab complete abbreviations

This allows abbreviations to be expanded by tab completions.

Fixes #3233
This commit is contained in:
ridiculousfish 2018-10-15 23:36:40 -07:00
parent 161196fe53
commit 202bf0bede
4 changed files with 64 additions and 15 deletions

View File

@ -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<wcstring, wcstring> abbrs = get_abbreviations();
std::vector<completion_t> 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<size_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<grammar::tok_string> 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<grammar::argument>();

View File

@ -27,9 +27,9 @@
#include <algorithm>
#include <functional>
#include <map>
#include <memory> // IWYU pragma: keep
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
@ -1176,7 +1176,7 @@ bool fish_xdm_login_hack_hack_hack_hack(std::vector<std::string> *cmds, int argc
return result;
}
static owning_lock<std::unordered_map<wcstring, wcstring>> s_abbreviations;
static owning_lock<std::map<wcstring, wcstring>> 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<wcstring, wcstring> get_abbreviations() {
auto abbreviations = s_abbreviations.acquire();
return *abbreviations;
}

View File

@ -9,6 +9,7 @@
#include <stddef.h>
#include <map>
#include <string>
#include <vector>
@ -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<wcstring, wcstring> get_abbreviations();
// Terrible hacks
bool fish_xdm_login_hack_hack_hack_hack(std::vector<std::string> *cmds, int argc,
const char *const *argv);

View File

@ -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");