mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-14 14:03:58 +00:00
Slightly refactor completion calculation
Move more stuff into completer_t
This commit is contained in:
parent
3175ccf266
commit
459c01df76
1 changed files with 242 additions and 243 deletions
153
src/complete.cpp
153
src/complete.cpp
|
@ -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, ¤t_command_unescape,
|
if (unescape_string(current_command, ¤t_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, ¤t_argument_unescape,
|
unescape_string(current_argument, ¤t_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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue