Break out COMPLETE_NO_CASE and COMPLETE_REPLACES_TOKEN into separate flags, in preparation for upcoming fuzzy completion work

This commit is contained in:
ridiculousfish 2013-03-05 20:54:16 -08:00
parent b2012467b3
commit 4d19bb17a9
7 changed files with 131 additions and 73 deletions

View file

@ -545,7 +545,7 @@ static int builtin_complete(parser_t &parser, wchar_t **argv)
recursion_level++;
std::vector<completion_t> 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"";
}

View file

@ -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<completion_t> &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<completion_t> completions;
wcstring_list_t commands_to_load;
@ -335,9 +346,31 @@ class completer_t
typedef std::map<wcstring, bool> 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<completion_t> 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; i<names.size(); i++)
{
const wcstring & env_name = names.at(i);
@ -1640,18 +1673,18 @@ bool completer_t::complete_variable(const wcstring &str, size_t start_offset)
{
comp.append(whole_var, start_offset);
comp.append(env_name);
flags = COMPLETE_NO_CASE | COMPLETE_DONT_ESCAPE;
flags = COMPLETE_CASE_INSENSITIVE | COMPLETE_REPLACES_TOKEN | COMPLETE_DONT_ESCAPE;
}
wcstring desc;
if (wants_description)
if (this->wants_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<completion_t> &comps, complete_type_t type, wcstring_list_t *commands_to_load)
void complete(const wcstring &cmd, std::vector<completion_t> &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<completion_t> &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;
/*

View file

@ -75,25 +75,20 @@ enum
*/
COMPLETE_NO_SPACE = 1 << 0,
/**
This completion is case insensitive.
/** This completion is case insensitive. */
COMPLETE_CASE_INSENSITIVE = 1 << 1,
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 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<completion_t> &completions);
@ -233,7 +230,7 @@ void complete_remove(const wchar_t *cmd,
*/
void complete(const wcstring &cmd,
std::vector<completion_t> &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<completion_t> &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

View file

@ -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<completion_t> 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();
@ -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();

View file

@ -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<c
return;
wchar_t *escaped_separator;
int has_case_sensitive=0;
if (prefix.empty())
{
@ -1155,10 +1154,15 @@ static void run_pager(const wcstring &prefix, int is_quoted, const std::vector<c
escaped_separator = escape(COMPLETE_SEP_STR, 1);
bool has_case_sensitive = false;
for (size_t i=0; i< comp.size(); i++)
{
const completion_t &el = comp.at(i);
has_case_sensitive |= !(el.flags & COMPLETE_NO_CASE);
if (! (el.flags & COMPLETE_CASE_INSENSITIVE))
{
has_case_sensitive = true;
break;
}
}
for (size_t i=0; i< comp.size(); i++)
@ -1170,13 +1174,13 @@ static void run_pager(const wcstring &prefix, int is_quoted, const std::vector<c
wcstring completion_text;
wcstring description_text;
if (has_case_sensitive && (el.flags & COMPLETE_NO_CASE))
if (has_case_sensitive && (el.flags & COMPLETE_CASE_INSENSITIVE))
{
continue;
}
// Note that an empty completion is perfectly sensible here, e.g. tab-completing 'foo' with a file called 'foo' and another called 'foobar'
if (el.flags & COMPLETE_NO_CASE)
if (el.flags & COMPLETE_REPLACES_TOKEN)
{
if (base_len == -1)
{
@ -1328,7 +1332,7 @@ struct autosuggestion_context_t
/* Try normal completions */
std::vector<completion_t> 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);
@ -3058,7 +3062,8 @@ 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);

View file

@ -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<completion_t> &, complete_type_t, wcstring_list_t * lst);
typedef void (*complete_function_t)(const wcstring &, std::vector<completion_t> &, completion_request_flags_t, wcstring_list_t * lst);
void reader_set_complete_function(complete_function_t);
/**

View file

@ -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());
}