From 4d19bb17a9161fc085e7b308d1ac30ec74186c33 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Tue, 5 Mar 2013 20:54:16 -0800 Subject: [PATCH] Break out COMPLETE_NO_CASE and COMPLETE_REPLACES_TOKEN into separate flags, in preparation for upcoming fuzzy completion work --- builtin_complete.cpp | 4 +- complete.cpp | 98 +++++++++++++++++++++++++++++--------------- complete.h | 37 ++++++++--------- fish_tests.cpp | 36 ++++++++++++---- reader.cpp | 21 ++++++---- reader.h | 2 +- wildcard.cpp | 6 +-- 7 files changed, 131 insertions(+), 73 deletions(-) diff --git a/builtin_complete.cpp b/builtin_complete.cpp index b4ac2a380..186fb9bcd 100644 --- a/builtin_complete.cpp +++ b/builtin_complete.cpp @@ -545,7 +545,7 @@ static int builtin_complete(parser_t &parser, wchar_t **argv) recursion_level++; std::vector comp; - complete(do_complete_param, comp, COMPLETE_DEFAULT); + complete(do_complete_param, comp, COMPLETION_REQUEST_DEFAULT); for (size_t i=0; i< comp.size() ; i++) { @@ -553,7 +553,7 @@ static int builtin_complete(parser_t &parser, wchar_t **argv) const wchar_t *prepend; - if (next.flags & COMPLETE_NO_CASE) + if (next.flags & COMPLETE_REPLACES_TOKEN) { prepend = L""; } diff --git a/complete.cpp b/complete.cpp index a99277c83..3cc82a2dd 100644 --- a/complete.cpp +++ b/complete.cpp @@ -104,14 +104,25 @@ #define C_(string) (string) #endif +/* Testing apparatus */ +const wcstring_list_t *s_override_variable_names = NULL; -/** - The maximum amount of time that we're willing to spend doing - username tilde completion. This special limit has been coded in - because user lookup can be extremely slow in cases of a humongous - LDAP database. (Google, I'm looking at you) - */ -#define MAX_USER_LOOKUP_TIME 0.2 +void complete_set_variable_names(const wcstring_list_t *names) +{ + s_override_variable_names = names; +} + +static inline wcstring_list_t complete_get_variable_names(void) +{ + if (s_override_variable_names != NULL) + { + return *s_override_variable_names; + } + else + { + return env_get_names(0); + } +} /** Struct describing a completion option entry. @@ -326,7 +337,7 @@ void sort_completions(std::vector &completions) /** Class representing an attempt to compute completions */ class completer_t { - const complete_type_t type; + const completion_request_flags_t flags; const wcstring initial_cmd; std::vector completions; wcstring_list_t commands_to_load; @@ -334,10 +345,32 @@ class completer_t /** Table of completions conditions that have already been tested and the corresponding test results */ typedef std::map condition_cache_t; condition_cache_t condition_cache; + + enum complete_type_t + { + COMPLETE_DEFAULT, + COMPLETE_AUTOSUGGEST + }; + + complete_type_t type() const + { + return (flags & COMPLETION_REQUEST_AUTOSUGGESTION) ? COMPLETE_AUTOSUGGEST : COMPLETE_DEFAULT; + } + + bool wants_descriptions() const + { + return !! (flags & COMPLETION_REQUEST_DESCRIPTIONS); + } + + bool fuzzy() const + { + return !! (flags & COMPLETION_REQUEST_FUZZY_MATCH); + } + public: - completer_t(const wcstring &c, complete_type_t t) : - type(t), + completer_t(const wcstring &c, completion_request_flags_t f) : + flags(f), initial_cmd(c) { } @@ -389,7 +422,7 @@ public: { /* Never do command substitution in autosuggestions. Sadly, we also can't yet do job expansion because it's not thread safe. */ expand_flags_t result = 0; - if (type == COMPLETE_AUTOSUGGEST) + if (this->type() == COMPLETE_AUTOSUGGEST) result |= EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_JOBS; return result; } @@ -449,7 +482,7 @@ bool completer_t::condition_test(const wcstring &condition) return 1; } - if (this->type == COMPLETE_AUTOSUGGEST) + if (this->type() == COMPLETE_AUTOSUGGEST) { /* Autosuggestion can't support conditions */ return 0; @@ -1096,8 +1129,6 @@ void completer_t::complete_cmd(const wcstring &str_cmd, bool use_function, bool if (cdpath.missing_or_empty()) cdpath = L"."; - const bool wants_description = (type == COMPLETE_DEFAULT); - if (str_cmd.find(L'/') != wcstring::npos || str_cmd.at(0) == L'~') { @@ -1106,7 +1137,7 @@ void completer_t::complete_cmd(const wcstring &str_cmd, bool use_function, bool if (expand_string(str_cmd, this->completions, ACCEPT_INCOMPLETE | EXECUTABLES_ONLY | this->expand_flags()) != EXPAND_ERROR) { - if (wants_description) + if (this->wants_descriptions()) { this->complete_cmd_desc(str_cmd); } @@ -1144,7 +1175,7 @@ void completer_t::complete_cmd(const wcstring &str_cmd, bool use_function, bool for (size_t i=prev_count; i< this->completions.size(); i++) { completion_t &c = this->completions.at(i); - if (c.flags & COMPLETE_NO_CASE) + if (c.flags & COMPLETE_REPLACES_TOKEN) { c.completion.erase(0, base_path.size()); @@ -1152,7 +1183,7 @@ void completer_t::complete_cmd(const wcstring &str_cmd, bool use_function, bool } } } - if (wants_description) + if (this->wants_descriptions()) this->complete_cmd_desc(str_cmd); } } @@ -1205,7 +1236,7 @@ void completer_t::complete_from_args(const wcstring &str, std::vector possible_comp; - bool is_autosuggest = (this->type == COMPLETE_AUTOSUGGEST); + bool is_autosuggest = (this->type() == COMPLETE_AUTOSUGGEST); parser_t parser(is_autosuggest ? PARSER_TYPE_COMPLETIONS_ONLY : PARSER_TYPE_GENERAL, false); /* If type is COMPLETE_AUTOSUGGEST, it means we're on a background thread, so don't call proc_push_interactive */ @@ -1335,11 +1366,11 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop wcstring cmd, path; parse_cmd_string(cmd_orig, path, cmd); - if (this->type == COMPLETE_DEFAULT) + if (this->type() == COMPLETE_DEFAULT) { complete_load(cmd, true); } - else if (this->type == COMPLETE_AUTOSUGGEST) + else if (this->type() == COMPLETE_AUTOSUGGEST) { /* Maybe indicate we should try loading this on the main thread */ if (! list_contains_string(this->commands_to_load, cmd) && ! completion_autoloader.has_tried_loading(cmd)) @@ -1514,11 +1545,14 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop size_t offset = 0; complete_flags_t flags = 0; - if (match) + { offset = wcslen(str); + } else - flags = COMPLETE_NO_CASE; + { + flags = COMPLETE_REPLACES_TOKEN | COMPLETE_CASE_INSENSITIVE; + } has_arg = ! o->comp.empty(); req_arg = (o->result_mode & NO_COMMON); @@ -1580,7 +1614,7 @@ void completer_t::complete_param_expand(const wcstring &sstr, bool do_file) flags |= EXPAND_SKIP_WILDCARDS; /* Squelch file descriptions per issue 254 */ - if (type == COMPLETE_AUTOSUGGEST || do_file) + if (this->type() == COMPLETE_AUTOSUGGEST || do_file) flags |= EXPAND_NO_DESCRIPTIONS; if (expand_string(comp_str, @@ -1608,9 +1642,8 @@ bool completer_t::complete_variable(const wcstring &str, size_t start_offset) const wchar_t *var = &whole_var[start_offset]; size_t varlen = wcslen(var); int res = 0; - bool wants_description = (type != COMPLETE_AUTOSUGGEST); - const wcstring_list_t names = env_get_names(0); + const wcstring_list_t names = complete_get_variable_names(); for (size_t i=0; iwants_descriptions()) { env_var_t value_unescaped = env_get_string(env_name); if (value_unescaped.missing()) continue; wcstring value = expand_escape_variable(value_unescaped); - if (type != COMPLETE_AUTOSUGGEST) + if (this->type() != COMPLETE_AUTOSUGGEST) desc = format_string(COMPLETE_VAR_DESC_VAL, value.c_str()); } @@ -1744,7 +1777,7 @@ bool completer_t::try_complete_user(const wcstring &str) append_completion(this->completions, name, desc, - COMPLETE_NO_CASE | COMPLETE_DONT_ESCAPE | COMPLETE_NO_SPACE); + COMPLETE_CASE_INSENSITIVE | COMPLETE_REPLACES_TOKEN | COMPLETE_DONT_ESCAPE | COMPLETE_NO_SPACE); res=1; } } @@ -1756,11 +1789,12 @@ bool completer_t::try_complete_user(const wcstring &str) return res; } -void complete(const wcstring &cmd, std::vector &comps, complete_type_t type, wcstring_list_t *commands_to_load) +void complete(const wcstring &cmd, std::vector &comps, completion_request_flags_t flags, wcstring_list_t *commands_to_load) { /* Make our completer */ - completer_t completer(cmd, type); + completer_t completer(cmd, flags); + const bool fuzzy = !! (flags & COMPLETION_REQUEST_FUZZY_MATCH); const wchar_t *tok_begin, *tok_end, *cmdsubst_begin, *cmdsubst_end, *prev_begin, *prev_end; wcstring current_token, prev_token; wcstring current_command; @@ -1969,7 +2003,7 @@ void complete(const wcstring &cmd, std::vector &comps, complete_ty do_file = false; /* And if we're autosuggesting, and the token is empty, don't do file suggestions */ - if (type == COMPLETE_AUTOSUGGEST && current_token_unescape.empty()) + if ((flags & COMPLETION_REQUEST_AUTOSUGGESTION) && current_token_unescape.empty()) do_file = false; /* diff --git a/complete.h b/complete.h index 555f2a9a6..0da1fa467 100644 --- a/complete.h +++ b/complete.h @@ -75,25 +75,20 @@ enum */ COMPLETE_NO_SPACE = 1 << 0, - /** - This completion is case insensitive. - - Warning: The contents of the completion_t structure is actually - different if this flag is set! Specifically, the completion string - contains the _entire_ completion token, not merely its suffix. - */ - COMPLETE_NO_CASE = 1 << 1, + /** This completion is case insensitive. */ + COMPLETE_CASE_INSENSITIVE = 1 << 1, + + /** This is not the suffix of a token, but replaces it entirely */ + COMPLETE_REPLACES_TOKEN = 1 << 2, /** This completion may or may not want a space at the end - guess by checking the last character of the completion. */ - COMPLETE_AUTO_SPACE = 1 << 2, + COMPLETE_AUTO_SPACE = 1 << 3, - /** - This completion should be inserted as-is, without escaping. - */ - COMPLETE_DONT_ESCAPE = 1 << 3 + /** This completion should be inserted as-is, without escaping. */ + COMPLETE_DONT_ESCAPE = 1 << 4 }; typedef int complete_flags_t; @@ -130,7 +125,7 @@ public: bool is_case_insensitive() const { - return !!(flags & COMPLETE_NO_CASE); + return !!(flags & COMPLETE_CASE_INSENSITIVE); } /* Construction. Note: defining these so that they are not inlined reduces the executable size. */ @@ -144,11 +139,13 @@ public: bool operator != (const completion_t& rhs) const; }; -enum complete_type_t -{ - COMPLETE_DEFAULT, - COMPLETE_AUTOSUGGEST +enum { + COMPLETION_REQUEST_DEFAULT = 0, + COMPLETION_REQUEST_AUTOSUGGESTION = 1 << 0, // indicates the completion is for an autosuggestion + COMPLETION_REQUEST_DESCRIPTIONS = 1 << 1, // indicates that we want descriptions + COMPLETION_REQUEST_FUZZY_MATCH = 1 << 2 // indicates that we don't require a prefix match }; +typedef uint32_t completion_request_flags_t; /** Given a list of completions, returns a list of their completion fields */ wcstring_list_t completions_to_wcstring_list(const std::vector &completions); @@ -233,7 +230,7 @@ void complete_remove(const wchar_t *cmd, */ void complete(const wcstring &cmd, std::vector &comp, - complete_type_t type, + completion_request_flags_t flags, wcstring_list_t *to_load = NULL); /** @@ -284,5 +281,7 @@ void complete_load(const wcstring &cmd, bool reload); */ void append_completion(std::vector &completions, const wcstring &comp, const wcstring &desc = L"", int flags = 0); +/* Function used for testing */ +void complete_set_variable_names(const wcstring_list_t *names); #endif diff --git a/fish_tests.cpp b/fish_tests.cpp index 33b2fdb70..27a70fcfa 100644 --- a/fish_tests.cpp +++ b/fish_tests.cpp @@ -959,6 +959,25 @@ static void test_colors() assert(rgb_color_t(L"mooganta").is_none()); } +static void test_complete(void) +{ + say(L"Testing complete"); + const wchar_t *name_strs[] = {L"Foo1", L"Foo2", L"Foo3", L"Bar1", L"Bar2", L"Bar3"}; + size_t count = sizeof name_strs / sizeof *name_strs; + const wcstring_list_t names(name_strs, name_strs + count); + + complete_set_variable_names(&names); + + std::vector completions; + complete(L"$F", completions, COMPLETION_REQUEST_DEFAULT); + assert(completions.size() == 3); + assert(completions.at(0).completion == L"oo1"); + assert(completions.at(1).completion == L"oo2"); + assert(completions.at(2).completion == L"oo3"); + + complete_set_variable_names(NULL); +} + static void test_1_completion(wcstring line, const wcstring &completion, complete_flags_t flags, bool append_only, wcstring expected, long source_line) { // str is given with a caret, which we use to represent the cursor position @@ -981,10 +1000,10 @@ static void test_1_completion(wcstring line, const wcstring &completion, complet assert(cursor_pos == out_cursor_pos); } -static void test_completions() +static void test_completion_insertions() { #define TEST_1_COMPLETION(a, b, c, d, e) test_1_completion(a, b, c, d, e, __LINE__) - say(L"Testing completions"); + say(L"Testing completion insertions"); TEST_1_COMPLETION(L"foo^", L"bar", 0, false, L"foobar ^"); TEST_1_COMPLETION(L"foo^ baz", L"bar", 0, false, L"foobar ^ baz"); //we really do want to insert two spaces here - otherwise it's hidden by the cursor TEST_1_COMPLETION(L"'foo^", L"bar", 0, false, L"'foobar' ^"); @@ -1006,8 +1025,8 @@ static void test_completions() TEST_1_COMPLETION(L"'foo\\'^", L"bar", COMPLETE_NO_SPACE, false, L"'foo\\'bar^"); TEST_1_COMPLETION(L"foo\\'^", L"bar", COMPLETE_NO_SPACE, false, L"foo\\'bar^"); - TEST_1_COMPLETION(L"foo^", L"bar", COMPLETE_NO_CASE, false, L"bar ^"); - TEST_1_COMPLETION(L"'foo^", L"bar", COMPLETE_NO_CASE, false, L"bar ^"); + TEST_1_COMPLETION(L"foo^", L"bar", COMPLETE_CASE_INSENSITIVE | COMPLETE_REPLACES_TOKEN, false, L"bar ^"); + TEST_1_COMPLETION(L"'foo^", L"bar", COMPLETE_CASE_INSENSITIVE | COMPLETE_REPLACES_TOKEN, false, L"bar ^"); } static void perform_one_autosuggestion_test(const wcstring &command, const wcstring &wd, const wcstring &expected, long line) @@ -1139,7 +1158,7 @@ void perf_complete() str[0]=c; reader_set_buffer(str, 0); - complete(str, out, COMPLETE_DEFAULT, NULL); + complete(str, out, COMPLETION_REQUEST_DEFAULT, NULL); matches += out.size(); out.clear(); @@ -1159,7 +1178,7 @@ void perf_complete() reader_set_buffer(str, 0); - complete(str, out, COMPLETE_DEFAULT, NULL); + complete(str, out, COMPLETION_REQUEST_DEFAULT, NULL); matches += out.size(); out.clear(); @@ -1695,7 +1714,7 @@ int main(int argc, char **argv) builtin_init(); reader_init(); env_init(); - + test_format(); test_escape(); test_convert(); @@ -1710,7 +1729,8 @@ int main(int argc, char **argv) test_word_motion(); test_is_potential_path(); test_colors(); - test_completions(); + test_complete(); + test_completion_insertions(); test_autosuggestion_combining(); test_autosuggest_suggest_special(); history_tests_t::test_history(); diff --git a/reader.cpp b/reader.cpp index 2c7d57062..f10e3ad09 100644 --- a/reader.cpp +++ b/reader.cpp @@ -979,7 +979,7 @@ wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flag { const wchar_t *val = val_str.c_str(); bool add_space = !(flags & COMPLETE_NO_SPACE); - bool do_replace = !!(flags & COMPLETE_NO_CASE); + bool do_replace = !!(flags & COMPLETE_REPLACES_TOKEN); bool do_escape = !(flags & COMPLETE_DONT_ESCAPE); const size_t cursor_pos = *inout_cursor_pos; @@ -1134,7 +1134,6 @@ static void run_pager(const wcstring &prefix, int is_quoted, const std::vector completions; - complete(search_string, completions, COMPLETE_AUTOSUGGEST, &this->commands_to_load); + complete(search_string, completions, COMPLETION_REQUEST_AUTOSUGGESTION, &this->commands_to_load); if (! completions.empty()) { const completion_t &comp = completions.at(0); @@ -3057,8 +3061,9 @@ const wchar_t *reader_readline() /* Construct a copy of the string from the beginning of the command substitution up to the end of the token we're completing */ const wcstring buffcpy = wcstring(cmdsub_begin, token_end); - - data->complete_func(buffcpy, comp, COMPLETE_DEFAULT, NULL); + + //fprintf(stderr, "Complete (%ls)\n", buffcpy.c_str()); + data->complete_func(buffcpy, comp, COMPLETION_REQUEST_DEFAULT | COMPLETION_REQUEST_DESCRIPTIONS, NULL); /* Munge our completions */ sort_and_make_unique(comp); diff --git a/reader.h b/reader.h index 174be574d..80dbe2585 100644 --- a/reader.h +++ b/reader.h @@ -163,7 +163,7 @@ void reader_pop(); - The command to be completed as a null terminated array of wchar_t - An array_list_t in which completions will be inserted. */ -typedef void (*complete_function_t)(const wcstring &, std::vector &, complete_type_t, wcstring_list_t * lst); +typedef void (*complete_function_t)(const wcstring &, std::vector &, completion_request_flags_t, wcstring_list_t * lst); void reader_set_complete_function(complete_function_t); /** diff --git a/wildcard.cpp b/wildcard.cpp index f8e1bed00..3177cf356 100644 --- a/wildcard.cpp +++ b/wildcard.cpp @@ -225,7 +225,7 @@ static bool wildcard_complete_internal(const wcstring &orig, wcstring out_completion; wcstring out_desc = (desc ? desc : L""); - if (flags & COMPLETE_NO_CASE) + if (flags & COMPLETE_REPLACES_TOKEN) { out_completion = orig; } @@ -292,7 +292,7 @@ static bool wildcard_complete_internal(const wcstring &orig, } else if (towlower(*wc) == towlower(*str)) { - return wildcard_complete_internal(orig, str+1, wc+1, 0, desc, desc_func, out, flags | COMPLETE_NO_CASE); + return wildcard_complete_internal(orig, str+1, wc+1, 0, desc, desc_func, out, flags | COMPLETE_CASE_INSENSITIVE | COMPLETE_REPLACES_TOKEN); } return false; } @@ -1095,7 +1095,7 @@ int wildcard_expand(const wchar_t *wc, { completion_t &c = out.at(i); - if (c.flags & COMPLETE_NO_CASE) + if (c.flags & COMPLETE_REPLACES_TOKEN) { c.completion = format_string(L"%ls%ls%ls", base_dir, wc_base.c_str(), c.completion.c_str()); }