Slightly refactor completion calculation

Move more stuff into completer_t
This commit is contained in:
ridiculousfish 2018-08-07 00:16:59 -07:00
parent 3175ccf266
commit 459c01df76

View file

@ -175,7 +175,7 @@ struct equal_to<completion_entry_t> {
return c1.cmd == c2.cmd; return c1.cmd == c2.cmd;
} }
}; };
} } // namespace std
typedef std::unordered_set<completion_entry_t> completion_entry_set_t; typedef std::unordered_set<completion_entry_t> completion_entry_set_t;
static completion_entry_set_t completion_set; static completion_entry_set_t completion_set;
@ -264,9 +264,10 @@ void completions_sort_and_prioritize(std::vector<completion_t> *comps) {
} }
// Throw out completions whose match types are less suitable than the best. // Throw out completions whose match types are less suitable than the best.
comps->erase(std::remove_if(comps->begin(), comps->end(), [&] (const completion_t &comp) { comps->erase(
return comp.match.type > best_type; std::remove_if(comps->begin(), comps->end(),
}), comps->end()); [&](const completion_t &comp) { return comp.match.type > best_type; }),
comps->end());
// Sort, provided COMPLETION_DONT_SORT isn't set // Sort, provided COMPLETION_DONT_SORT isn't set
stable_sort(comps->begin(), comps->end(), completion_t::is_naturally_less_than); stable_sort(comps->begin(), comps->end(), completion_t::is_naturally_less_than);
@ -282,8 +283,8 @@ void completions_sort_and_prioritize(std::vector<completion_t> *comps) {
/// Class representing an attempt to compute completions. /// Class representing an attempt to compute completions.
class completer_t { class completer_t {
const wcstring cmd;
const completion_request_flags_t flags; const completion_request_flags_t flags;
const wcstring initial_cmd;
std::vector<completion_t> completions; std::vector<completion_t> completions;
/// Table of completions conditions that have already been tested and the corresponding test /// Table of completions conditions that have already been tested and the corresponding test
@ -309,12 +310,6 @@ class completer_t {
return fuzzy_match_prefix_case_insensitive; return fuzzy_match_prefix_case_insensitive;
} }
public:
completer_t(wcstring c, completion_request_flags_t f) : flags(f), initial_cmd(std::move(c)) {}
bool empty() const { return completions.empty(); }
std::vector<completion_t> acquire_completions() { return std::move(completions); }
bool try_complete_variable(const wcstring &str); bool try_complete_variable(const wcstring &str);
bool try_complete_user(const wcstring &str); bool try_complete_user(const wcstring &str);
@ -351,6 +346,15 @@ class completer_t {
return result; return result;
} }
bool empty() const { return completions.empty(); }
public:
completer_t(wcstring c, completion_request_flags_t f) : cmd(std::move(c)), flags(f) {}
void perform();
std::vector<completion_t> acquire_completions() { return std::move(completions); }
}; };
// Callback when an autoloaded completion is removed. // Callback when an autoloaded completion is removed.
@ -851,20 +855,17 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop
if (this->type() == COMPLETE_DEFAULT) { if (this->type() == COMPLETE_DEFAULT) {
ASSERT_IS_MAIN_THREAD(); ASSERT_IS_MAIN_THREAD();
func(); func();
} } else if (this->type() == COMPLETE_AUTOSUGGEST) {
else if (this->type() == COMPLETE_AUTOSUGGEST) { iothread_perform_on_main([&]() { func(); });
iothread_perform_on_main([&]() { } else {
func();
});
}
else {
assert(false && "this->type() is unknown!"); assert(false && "this->type() is unknown!");
} }
}; };
// This was originally written as a static variable protected by a mutex that is updated only if `scmd.size() == 1` to // This was originally written as a static variable protected by a mutex that is updated only if
// prevent too many lookups, but it turns out that this is mainly only called when the user explicitly presses <TAB> // `scmd.size() == 1` to prevent too many lookups, but it turns out that this is mainly only
// after a command, so the overhead of the additional env lookup should be negligible. // called when the user explicitly presses <TAB> after a command, so the overhead of the
// additional env lookup should be negligible.
env_vars_snapshot_t completion_snapshot; env_vars_snapshot_t completion_snapshot;
// debug(0, L"\nThinking about looking up completions for %ls\n", cmd.c_str()); // debug(0, L"\nThinking about looking up completions for %ls\n", cmd.c_str());
@ -872,13 +873,17 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop
// Only reload environment variables if builtin_exists returned false, as an optimization // Only reload environment variables if builtin_exists returned false, as an optimization
if (head_exists == false) { if (head_exists == false) {
run_on_main_thread([&completion_snapshot]() { run_on_main_thread([&completion_snapshot]() {
completion_snapshot = std::move(env_vars_snapshot_t( (wchar_t const * const []) { L"fish_function_path", nullptr } )); completion_snapshot = std::move(
env_vars_snapshot_t((wchar_t const *const[]){L"fish_function_path", nullptr}));
}); });
head_exists = function_exists_no_autoload(cmd.c_str(), completion_snapshot); head_exists = function_exists_no_autoload(cmd.c_str(), completion_snapshot);
// While it may seem like first testing `path_get_path` before resorting to an env lookup may be faster, path_get_path can potentially // While it may seem like first testing `path_get_path` before resorting to an env lookup
// do a lot of FS/IO access, so env.get() + function_exists() should still be faster. // may be faster, path_get_path can potentially do a lot of FS/IO access, so env.get() +
head_exists = head_exists || path_get_path(cmd_orig, nullptr); //use cmd_orig here as it is potentially pathed // function_exists() should still be faster.
head_exists =
head_exists ||
path_get_path(cmd_orig, nullptr); // use cmd_orig here as it is potentially pathed
} }
if (!head_exists) { if (!head_exists) {
@ -887,11 +892,8 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop
// tools that do not exist. Applies to both manual completions ("cm<TAB>", "cmd <TAB>") // tools that do not exist. Applies to both manual completions ("cm<TAB>", "cmd <TAB>")
// and automatic completions ("gi" autosuggestion provider -> git) // and automatic completions ("gi" autosuggestion provider -> git)
debug(4, "Skipping completions for non-existent head\n"); debug(4, "Skipping completions for non-existent head\n");
} } else {
else { run_on_main_thread([&]() { complete_load(cmd, true); });
run_on_main_thread([&]() {
complete_load(cmd, true);
});
} }
// Make a list of lists of all options that we care about. // Make a list of lists of all options that we care about.
@ -1302,21 +1304,22 @@ static void walk_wrap_chain(const wcstring &command_line, source_range_t command
} }
} }
void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> *out_comps, /// Return the index of an argument from \p args containing the position \p pos, or none if none.
completion_request_flags_t flags) { static maybe_t<size_t> find_argument_containing_position(
// Determine the innermost subcommand. const std::vector<tnode_t<grammar::argument>> &args, size_t pos) {
const wchar_t *cmdsubst_begin, *cmdsubst_end; size_t idx = 0;
parse_util_cmdsubst_extent(cmd_with_subcmds.c_str(), cmd_with_subcmds.size(), &cmdsubst_begin, for (const auto &arg : args) {
&cmdsubst_end); if (arg.location_in_or_at_end_of_source_range(pos)) {
assert(cmdsubst_begin != NULL && cmdsubst_end != NULL && cmdsubst_end >= cmdsubst_begin); return idx;
const wcstring cmd = wcstring(cmdsubst_begin, cmdsubst_end - cmdsubst_begin); }
idx++;
// Make our completer. }
completer_t completer(cmd, flags); return none();
}
void completer_t::perform() {
wcstring current_command; wcstring current_command;
const size_t pos = cmd.size(); const size_t pos = cmd.size();
bool done = false;
bool use_command = 1; bool use_command = 1;
bool use_function = 1; bool use_function = 1;
bool use_builtin = 1; bool use_builtin = 1;
@ -1324,27 +1327,23 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> *out_c
// debug( 1, L"Complete '%ls'", cmd.c_str() ); // debug( 1, L"Complete '%ls'", cmd.c_str() );
const wchar_t *cmd_cstr = cmd.c_str(); const wchar_t *tok_begin = nullptr;
const wchar_t *tok_begin = nullptr, *prev_begin = nullptr, *prev_end = nullptr; parse_util_token_extent(cmd.c_str(), cmd.size(), &tok_begin, nullptr, nullptr, nullptr);
parse_util_token_extent(cmd_cstr, cmd.size(), &tok_begin, NULL, &prev_begin, &prev_end);
assert(tok_begin != nullptr); assert(tok_begin != nullptr);
// If we are completing a variable name or a tilde expansion user name, we do that and return. // If we are completing a variable name or a tilde expansion user name, we do that and return.
// No need for any other completions. // No need for any other completions.
const wcstring current_token = tok_begin;
// Unconditionally complete variables and processes. This is a little weird since we will // Unconditionally complete variables and processes. This is a little weird since we will
// happily complete variables even in e.g. command position, despite the fact that they are // happily complete variables even in e.g. command position, despite the fact that they are
// invalid there. */ // invalid there. */
if (!done) { const wcstring current_token = tok_begin;
done = completer.try_complete_variable(current_token) || if (try_complete_variable(current_token) || try_complete_user(current_token)) {
completer.try_complete_user(current_token); return;
} }
if (!done) {
parse_node_tree_t tree; parse_node_tree_t tree;
parse_tree_from_string(cmd, parse_flag_continue_after_error | parse_tree_from_string(cmd,
parse_flag_accept_incomplete_tokens | parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens |
parse_flag_include_comments, parse_flag_include_comments,
&tree, NULL); &tree, NULL);
@ -1355,8 +1354,7 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> *out_c
while (position_in_statement > 0 && cmd.at(position_in_statement - 1) == L' ') { while (position_in_statement > 0 && cmd.at(position_in_statement - 1) == L' ') {
position_in_statement--; position_in_statement--;
} }
auto plain_statement = auto plain_statement = tnode_t<grammar::plain_statement>::find_node_matching_source_location(
tnode_t<grammar::plain_statement>::find_node_matching_source_location(
&tree, position_in_statement, nullptr); &tree, position_in_statement, nullptr);
if (!plain_statement) { if (!plain_statement) {
// Not part of a plain statement. This could be e.g. a for loop header, case expression, // Not part of a plain statement. This could be e.g. a for loop header, case expression,
@ -1375,8 +1373,8 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> *out_c
} else if (pos > 0) { } else if (pos > 0) {
// If the previous character is in one of these types, we don't do file // If the previous character is in one of these types, we don't do file
// suggestions. // suggestions.
const parse_token_type_t bad_types[] = { const parse_token_type_t bad_types[] = {parse_token_type_pipe, parse_token_type_end,
parse_token_type_pipe, parse_token_type_end, parse_token_type_background, parse_token_type_background,
parse_special_type_comment}; parse_special_type_comment};
for (parse_token_type_t type : bad_types) { for (parse_token_type_t type : bad_types) {
if (tree.find_node_matching_source_location(type, pos - 1, NULL)) { if (tree.find_node_matching_source_location(type, pos - 1, NULL)) {
@ -1386,7 +1384,7 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> *out_c
} }
} }
} }
completer.complete_param_expand(current_token, do_file); complete_param_expand(current_token, do_file);
} else { } else {
assert(plain_statement && plain_statement.has_source()); assert(plain_statement && plain_statement.has_source());
@ -1425,28 +1423,20 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> *out_c
if (cmd_node.location_in_or_at_end_of_source_range(pos)) { if (cmd_node.location_in_or_at_end_of_source_range(pos)) {
// Complete command filename. // Complete command filename.
completer.complete_cmd(current_token, use_function, use_builtin, use_command, complete_cmd(current_token, use_function, use_builtin, use_command, use_implicit_cd);
use_implicit_cd);
} else { } else {
// Get all the arguments. // Get all the arguments.
auto all_arguments = plain_statement.descendants<grammar::argument>(); auto all_arguments = plain_statement.descendants<grammar::argument>();
// See whether we are in an argument. We may also be in a redirection, or nothing at // See whether we are in an argument. We may also be in a redirection, or nothing at
// all. // all.
maybe_t<size_t> matching_arg_index; maybe_t<size_t> matching_arg_index =
for (size_t i = 0; i < all_arguments.size(); i++) { find_argument_containing_position(all_arguments, position_in_statement);
tnode_t<grammar::argument> arg = all_arguments.at(i);
if (arg.location_in_or_at_end_of_source_range(position_in_statement)) {
matching_arg_index = i;
break;
}
}
bool had_ddash = false; bool had_ddash = false;
wcstring current_argument, previous_argument; wcstring current_argument, previous_argument;
if (matching_arg_index) { if (matching_arg_index) {
const wcstring matching_arg = const wcstring matching_arg = all_arguments.at(*matching_arg_index).get_source(cmd);
all_arguments.at(*matching_arg_index).get_source(cmd);
// If the cursor is in whitespace, then the "current" argument is empty and the // If the cursor is in whitespace, then the "current" argument is empty and the
// previous argument is the matching one. But if the cursor was in or at the end // previous argument is the matching one. But if the cursor was in or at the end
@ -1490,8 +1480,7 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> *out_c
// Try completing as an argument. // Try completing as an argument.
wcstring current_command_unescape, previous_argument_unescape, wcstring current_command_unescape, previous_argument_unescape,
current_argument_unescape; current_argument_unescape;
if (unescape_string(current_command, &current_command_unescape, if (unescape_string(current_command, &current_command_unescape, UNESCAPE_DEFAULT) &&
UNESCAPE_DEFAULT) &&
unescape_string(previous_argument, &previous_argument_unescape, unescape_string(previous_argument, &previous_argument_unescape,
UNESCAPE_DEFAULT) && UNESCAPE_DEFAULT) &&
unescape_string(current_argument, &current_argument_unescape, unescape_string(current_argument, &current_argument_unescape,
@ -1509,7 +1498,7 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> *out_c
bcst = make_unique<builtin_commandline_scoped_transient_t>(cmdline); bcst = make_unique<builtin_commandline_scoped_transient_t>(cmdline);
} }
// Now invoke any custom completions for this command. // Now invoke any custom completions for this command.
if (!completer.complete_param(cmd, previous_argument_unescape, if (!complete_param(cmd, previous_argument_unescape,
current_argument_unescape, !had_ddash)) { current_argument_unescape, !had_ddash)) {
do_file = false; do_file = false;
} }
@ -1520,8 +1509,7 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> *out_c
// Hack. If we're cd, handle it specially (issue #1059, others). // Hack. If we're cd, handle it specially (issue #1059, others).
handle_as_special_cd = (current_command_unescape == L"cd"); handle_as_special_cd = (current_command_unescape == L"cd");
// And if we're autosuggesting, and the token is empty, don't do file // And if we're autosuggesting, and the token is empty, don't do file suggestions.
// suggestions.
if ((flags & COMPLETION_REQUEST_AUTOSUGGESTION) && if ((flags & COMPLETION_REQUEST_AUTOSUGGESTION) &&
current_argument_unescape.empty()) { current_argument_unescape.empty()) {
do_file = false; do_file = false;
@ -1529,11 +1517,21 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> *out_c
} }
// This function wants the unescaped string. // This function wants the unescaped string.
completer.complete_param_expand(current_token, do_file, handle_as_special_cd); complete_param_expand(current_token, do_file, handle_as_special_cd);
} }
} }
} }
void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> *out_comps,
completion_request_flags_t flags) {
// Determine the innermost subcommand.
const wchar_t *cmdsubst_begin, *cmdsubst_end;
parse_util_cmdsubst_extent(cmd_with_subcmds.c_str(), cmd_with_subcmds.size(), &cmdsubst_begin,
&cmdsubst_end);
assert(cmdsubst_begin != NULL && cmdsubst_end != NULL && cmdsubst_end >= cmdsubst_begin);
wcstring cmd = wcstring(cmdsubst_begin, cmdsubst_end - cmdsubst_begin);
completer_t completer(std::move(cmd), flags);
completer.perform();
*out_comps = completer.acquire_completions(); *out_comps = completer.acquire_completions();
} }
@ -1597,7 +1595,8 @@ wcstring complete_print() {
// are the targets that they wrap. // are the targets that they wrap.
auto wrap_pairs = complete_get_wrap_pairs(); auto wrap_pairs = complete_get_wrap_pairs();
for (const auto &entry : wrap_pairs) { for (const auto &entry : wrap_pairs) {
append_format(out, L"complete --command %ls --wraps %ls\n", std::get<0>(entry).c_str(), std::get<1>(entry).c_str()); append_format(out, L"complete --command %ls --wraps %ls\n", std::get<0>(entry).c_str(),
std::get<1>(entry).c_str());
} }
return out; return out;
} }