mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-05 17:48:45 +00:00
Factor custom completion information into custom_arg_data_t
When completing and walking a wrap chain, we pass around a lot of information. Factor this together into a new struct custom_arg_data_t which reduces the number of parameters needed.
This commit is contained in:
parent
5cadea0173
commit
757dda43ac
1 changed files with 77 additions and 56 deletions
133
src/complete.cpp
133
src/complete.cpp
|
@ -377,17 +377,34 @@ class completer_t {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A set tracking the wrapped commands which we have seen.
|
// Bag of data to support expanding a command's arguments using custom completions, including
|
||||||
using wrap_chain_visited_set_t = std::set<wcstring>;
|
// the wrap chain.
|
||||||
|
struct custom_arg_data_t {
|
||||||
|
// The unescaped argument before the argument which is being completed, or empty if none.
|
||||||
|
wcstring previous_argument{};
|
||||||
|
|
||||||
void complete_custom(const wcstring &cmd, const wcstring &cmdline,
|
// The unescaped argument which is being completed, or empty if none.
|
||||||
const wcstring &previous_argument, const wcstring ¤t_argument,
|
wcstring current_argument{};
|
||||||
bool had_ddash, size_t depth, bool *do_file);
|
|
||||||
|
|
||||||
void walk_wrap_chain(const wcstring &command_line, source_range_t command_range,
|
// Whether a -- has been encountered, which suppresses options.
|
||||||
const wcstring &previous_argument_unescape,
|
bool had_ddash{false};
|
||||||
const wcstring ¤t_argument, bool had_ddash,
|
|
||||||
wrap_chain_visited_set_t *visited, size_t depth, bool *do_file);
|
// Whether to perform file completions.
|
||||||
|
// This is an "out" parameter of the wrap chain walk: if any wrapped command suppresses file
|
||||||
|
// completions this gets set to false.
|
||||||
|
bool do_file{true};
|
||||||
|
|
||||||
|
// Depth in the wrap chain.
|
||||||
|
size_t wrap_depth{0};
|
||||||
|
|
||||||
|
// The set of wrapped commands which we have visited, and so should not be explored again.
|
||||||
|
std::set<wcstring> visited_wrapped_commands{};
|
||||||
|
};
|
||||||
|
|
||||||
|
void complete_custom(const wcstring &cmd, const wcstring &cmdline, custom_arg_data_t *arg_data);
|
||||||
|
|
||||||
|
void walk_wrap_chain(const wcstring &cmd, const wcstring &cmdline, source_range_t cmd_range,
|
||||||
|
custom_arg_data_t *arg_data);
|
||||||
|
|
||||||
bool empty() const { return completions.empty(); }
|
bool empty() const { return completions.empty(); }
|
||||||
|
|
||||||
|
@ -1328,16 +1345,15 @@ bool completer_t::try_complete_user(const wcstring &str) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Complete a command by invoking user-specified completions.
|
||||||
void completer_t::complete_custom(const wcstring &cmd, const wcstring &cmdline,
|
void completer_t::complete_custom(const wcstring &cmd, const wcstring &cmdline,
|
||||||
const wcstring &previous_argument,
|
custom_arg_data_t *ad) {
|
||||||
const wcstring ¤t_argument, bool had_ddash, size_t depth,
|
|
||||||
bool *do_file) {
|
|
||||||
bool is_autosuggest = this->type() == COMPLETE_AUTOSUGGEST;
|
bool is_autosuggest = this->type() == COMPLETE_AUTOSUGGEST;
|
||||||
// Perhaps set a transient commandline so that custom completions
|
// Perhaps set a transient commandline so that custom completions
|
||||||
// buitin_commandline will refer to the wrapped command. But not if
|
// buitin_commandline will refer to the wrapped command. But not if
|
||||||
// we're doing autosuggestions.
|
// we're doing autosuggestions.
|
||||||
maybe_t<cleanup_t> remove_transient{};
|
maybe_t<cleanup_t> remove_transient{};
|
||||||
bool wants_transient = depth > 0 && !is_autosuggest;
|
bool wants_transient = ad->wrap_depth > 0 && !is_autosuggest;
|
||||||
if (wants_transient) {
|
if (wants_transient) {
|
||||||
ctx.parser->libdata().transient_commandlines.push_back(cmdline);
|
ctx.parser->libdata().transient_commandlines.push_back(cmdline);
|
||||||
remove_transient.emplace([&] { ctx.parser->libdata().transient_commandlines.pop_back(); });
|
remove_transient.emplace([&] { ctx.parser->libdata().transient_commandlines.pop_back(); });
|
||||||
|
@ -1383,35 +1399,36 @@ void completer_t::complete_custom(const wcstring &cmd, const wcstring &cmdline,
|
||||||
cleanup_t remove_transient(
|
cleanup_t remove_transient(
|
||||||
[&] { ctx.parser->libdata().transient_commandlines.pop_back(); });
|
[&] { ctx.parser->libdata().transient_commandlines.pop_back(); });
|
||||||
perform_for_command(std::move(unaliased_cmd));
|
perform_for_command(std::move(unaliased_cmd));
|
||||||
*do_file = false;
|
ad->do_file = false;
|
||||||
} else if (!complete_param(cmd, previous_argument, current_argument,
|
} else if (!complete_param(
|
||||||
!had_ddash)) { // Invoke any custom completions for this command.
|
cmd, ad->previous_argument, ad->current_argument,
|
||||||
*do_file = false;
|
!ad->had_ddash)) { // Invoke any custom completions for this command.
|
||||||
|
ad->do_file = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given a command line \p command_line and the range of the command itself within the
|
// Invoke command-specific completions given by \p arg_data.
|
||||||
// command line as \p command_range, invoke command-specific completions.
|
|
||||||
// Then, for each target wrapped by the given command, update the command
|
// Then, for each target wrapped by the given command, update the command
|
||||||
// line with that target and invoke this recursively.
|
// line with that target and invoke this recursively.
|
||||||
void completer_t::walk_wrap_chain(const wcstring &command_line, source_range_t command_range,
|
// The command whose completions to use is given by \p cmd. The full command line is given by \p
|
||||||
const wcstring &previous_argument,
|
// cmdline and the command's range in it is given by \p cmdrange. Note: the command range
|
||||||
const wcstring ¤t_argument, bool had_ddash,
|
// may have a different length than the command itself, because the command is unescaped (i.e.
|
||||||
wrap_chain_visited_set_t *visited, size_t depth, bool *do_file) {
|
// quotes removed).
|
||||||
|
void completer_t::walk_wrap_chain(const wcstring &cmd, const wcstring &cmdline,
|
||||||
|
source_range_t cmdrange, custom_arg_data_t *ad) {
|
||||||
// Limit our recursion depth. This prevents cycles in the wrap chain graph from overflowing.
|
// Limit our recursion depth. This prevents cycles in the wrap chain graph from overflowing.
|
||||||
if (depth > 24) return;
|
if (ad->wrap_depth > 24) return;
|
||||||
if (ctx.cancel_checker()) return;
|
if (ctx.cancel_checker()) return;
|
||||||
|
|
||||||
// Extract command from the command line and invoke the receiver with it.
|
// Extract command from the command line and invoke the receiver with it.
|
||||||
wcstring command{command_line, command_range.start, command_range.length};
|
complete_custom(cmd, cmdline, ad);
|
||||||
complete_custom(command, command_line, previous_argument, current_argument, had_ddash, depth,
|
|
||||||
do_file);
|
|
||||||
|
|
||||||
wcstring_list_t targets = complete_get_wrap_targets(command);
|
wcstring_list_t targets = complete_get_wrap_targets(cmd);
|
||||||
|
scoped_push<size_t> saved_depth(&ad->wrap_depth, ad->wrap_depth + 1);
|
||||||
for (const wcstring &wt : targets) {
|
for (const wcstring &wt : targets) {
|
||||||
// Construct a fake command line containing the wrap target.
|
// Construct a fake command line containing the wrap target.
|
||||||
wcstring faux_commandline = command_line;
|
wcstring faux_commandline = cmdline;
|
||||||
faux_commandline.replace(command_range.start, command_range.length, wt);
|
faux_commandline.replace(cmdrange.start, cmdrange.length, wt);
|
||||||
|
|
||||||
// Try to extract the command from the faux commandline.
|
// Try to extract the command from the faux commandline.
|
||||||
// We do this by simply getting the first token. This is a hack; for example one might
|
// We do this by simply getting the first token. This is a hack; for example one might
|
||||||
|
@ -1420,15 +1437,14 @@ void completer_t::walk_wrap_chain(const wcstring &command_line, source_range_t c
|
||||||
wcstring wrapped_command = tok_first(wt);
|
wcstring wrapped_command = tok_first(wt);
|
||||||
if (wrapped_command.empty()) continue;
|
if (wrapped_command.empty()) continue;
|
||||||
|
|
||||||
size_t where = faux_commandline.find(wrapped_command, command_range.start);
|
size_t where = faux_commandline.find(wrapped_command, cmdrange.start);
|
||||||
assert(where != wcstring::npos &&
|
assert(where != wcstring::npos &&
|
||||||
"Should have found the wrapped command since we inserted it");
|
"Should have found the wrapped command since we inserted it");
|
||||||
|
|
||||||
// Recurse with our new command and command line, if we have not seen this wrap before.
|
// Recurse with our new command and command line, if we have not seen this wrap before.
|
||||||
if (visited->insert(wrapped_command).second) {
|
if (ad->visited_wrapped_commands.insert(wrapped_command).second) {
|
||||||
source_range_t faux_source_range{uint32_t(where), uint32_t(wrapped_command.size())};
|
source_range_t faux_source_range{uint32_t(where), uint32_t(wrapped_command.size())};
|
||||||
walk_wrap_chain(faux_commandline, faux_source_range, previous_argument,
|
walk_wrap_chain(wrapped_command, faux_commandline, faux_source_range, ad);
|
||||||
current_argument, had_ddash, visited, depth + 1, do_file);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1520,8 +1536,9 @@ void completer_t::perform_for_command(wcstring cmd) {
|
||||||
}};
|
}};
|
||||||
|
|
||||||
const size_t cursor_pos = cmd.size();
|
const size_t cursor_pos = cmd.size();
|
||||||
|
const bool is_autosuggest = (flags & completion_request_t::autosuggestion);
|
||||||
|
|
||||||
// Find the plain statement to operate on. The cursor may be past it (#1261), so backtrack
|
// Find the process to operate on. The cursor may be past it (#1261), so backtrack
|
||||||
// until we know we're no longer in a space. But the space may actually be part of the
|
// until we know we're no longer in a space. But the space may actually be part of the
|
||||||
// argument (#2477).
|
// argument (#2477).
|
||||||
size_t position_in_statement = cursor_pos;
|
size_t position_in_statement = cursor_pos;
|
||||||
|
@ -1534,14 +1551,15 @@ void completer_t::perform_for_command(wcstring cmd) {
|
||||||
parse_util_process_extent(cmd.c_str(), position_in_statement, nullptr, nullptr, &tokens);
|
parse_util_process_extent(cmd.c_str(), position_in_statement, nullptr, nullptr, &tokens);
|
||||||
|
|
||||||
// Hack: fix autosuggestion by removing prefixing "and"s #6249.
|
// Hack: fix autosuggestion by removing prefixing "and"s #6249.
|
||||||
if (flags & completion_request_t::autosuggestion) {
|
if (is_autosuggest) {
|
||||||
while (!tokens.empty() && parser_keywords_is_subcommand(tokens.front().get_source(cmd)))
|
while (!tokens.empty() && parser_keywords_is_subcommand(tokens.front().get_source(cmd)))
|
||||||
tokens.erase(tokens.begin());
|
tokens.erase(tokens.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty process (cursor is after one of ;, &, |, \n, &&, || modulo whitespace).
|
// Empty process (cursor is after one of ;, &, |, \n, &&, || modulo whitespace).
|
||||||
if (tokens.empty()) {
|
if (tokens.empty()) {
|
||||||
// Don't autosuggest anything based on the empty string (generalizes #1631).
|
// Don't autosuggest anything based on the empty string (generalizes #1631).
|
||||||
if (flags & completion_request_t::autosuggestion) return;
|
if (is_autosuggest) return;
|
||||||
|
|
||||||
complete_cmd(L"");
|
complete_cmd(L"");
|
||||||
complete_abbr(L"");
|
complete_abbr(L"");
|
||||||
|
@ -1615,30 +1633,33 @@ void completer_t::perform_for_command(wcstring cmd) {
|
||||||
do_file = true;
|
do_file = true;
|
||||||
} else {
|
} else {
|
||||||
// Try completing as an argument.
|
// Try completing as an argument.
|
||||||
wcstring current_command = cmd_tok.get_source(cmd), current_command_unescape,
|
custom_arg_data_t arg_data{};
|
||||||
previous_argument_unescape, current_argument_unescape;
|
arg_data.had_ddash = had_ddash;
|
||||||
if (unescape_string(current_command, ¤t_command_unescape, UNESCAPE_DEFAULT) &&
|
|
||||||
unescape_string(previous_argument, &previous_argument_unescape, UNESCAPE_DEFAULT) &&
|
assert(cmd_tok.offset < std::numeric_limits<uint32_t>::max());
|
||||||
unescape_string(current_argument, ¤t_argument_unescape, UNESCAPE_INCOMPLETE)) {
|
assert(cmd_tok.length < std::numeric_limits<uint32_t>::max());
|
||||||
|
source_range_t command_range = {static_cast<uint32_t>(cmd_tok.offset),
|
||||||
|
static_cast<uint32_t>(cmd_tok.length)};
|
||||||
|
|
||||||
|
wcstring unesc_command;
|
||||||
|
bool unescaped =
|
||||||
|
unescape_string(cmd_tok.get_source(cmd), &unesc_command, UNESCAPE_DEFAULT) &&
|
||||||
|
unescape_string(previous_argument, &arg_data.previous_argument, UNESCAPE_DEFAULT) &&
|
||||||
|
unescape_string(current_argument, &arg_data.current_argument, UNESCAPE_INCOMPLETE);
|
||||||
|
if (unescaped) {
|
||||||
// Have to walk over the command and its entire wrap chain. If any command
|
// Have to walk over the command and its entire wrap chain. If any command
|
||||||
// disables do_file, then they all do.
|
// disables do_file, then they all do.
|
||||||
do_file = true;
|
walk_wrap_chain(unesc_command, cmd, command_range, &arg_data);
|
||||||
assert(cmd_tok.offset < std::numeric_limits<uint32_t>::max());
|
do_file = arg_data.do_file;
|
||||||
assert(cmd_tok.length < std::numeric_limits<uint32_t>::max());
|
|
||||||
source_range_t range = {static_cast<uint32_t>(cmd_tok.offset),
|
// If we're autosuggesting, and the token is empty, don't do file suggestions.
|
||||||
static_cast<uint32_t>(cmd_tok.length)};
|
if (is_autosuggest && arg_data.current_argument.empty()) {
|
||||||
wrap_chain_visited_set_t visited;
|
do_file = false;
|
||||||
walk_wrap_chain(cmd, range, previous_argument_unescape, current_argument_unescape,
|
}
|
||||||
had_ddash, &visited, 0, &do_file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 = (unesc_command == L"cd");
|
||||||
|
|
||||||
// And if we're autosuggesting, and the token is empty, don't do file suggestions.
|
|
||||||
if ((flags & completion_request_t::autosuggestion) && current_argument_unescape.empty()) {
|
|
||||||
do_file = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function wants the unescaped string.
|
// This function wants the unescaped string.
|
||||||
|
|
Loading…
Reference in a new issue