mirror of
https://github.com/fish-shell/fish-shell
synced 2024-11-10 23:24:39 +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;
|
||||
}
|
||||
|
||||
// A set tracking the wrapped commands which we have seen.
|
||||
using wrap_chain_visited_set_t = std::set<wcstring>;
|
||||
// Bag of data to support expanding a command's arguments using custom completions, including
|
||||
// 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,
|
||||
const wcstring &previous_argument, const wcstring ¤t_argument,
|
||||
bool had_ddash, size_t depth, bool *do_file);
|
||||
// The unescaped argument which is being completed, or empty if none.
|
||||
wcstring current_argument{};
|
||||
|
||||
void walk_wrap_chain(const wcstring &command_line, source_range_t command_range,
|
||||
const wcstring &previous_argument_unescape,
|
||||
const wcstring ¤t_argument, bool had_ddash,
|
||||
wrap_chain_visited_set_t *visited, size_t depth, bool *do_file);
|
||||
// Whether a -- has been encountered, which suppresses options.
|
||||
bool had_ddash{false};
|
||||
|
||||
// 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(); }
|
||||
|
||||
|
@ -1328,16 +1345,15 @@ bool completer_t::try_complete_user(const wcstring &str) {
|
|||
#endif
|
||||
}
|
||||
|
||||
// Complete a command by invoking user-specified completions.
|
||||
void completer_t::complete_custom(const wcstring &cmd, const wcstring &cmdline,
|
||||
const wcstring &previous_argument,
|
||||
const wcstring ¤t_argument, bool had_ddash, size_t depth,
|
||||
bool *do_file) {
|
||||
custom_arg_data_t *ad) {
|
||||
bool is_autosuggest = this->type() == COMPLETE_AUTOSUGGEST;
|
||||
// Perhaps set a transient commandline so that custom completions
|
||||
// buitin_commandline will refer to the wrapped command. But not if
|
||||
// we're doing autosuggestions.
|
||||
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) {
|
||||
ctx.parser->libdata().transient_commandlines.push_back(cmdline);
|
||||
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(
|
||||
[&] { ctx.parser->libdata().transient_commandlines.pop_back(); });
|
||||
perform_for_command(std::move(unaliased_cmd));
|
||||
*do_file = false;
|
||||
} else if (!complete_param(cmd, previous_argument, current_argument,
|
||||
!had_ddash)) { // Invoke any custom completions for this command.
|
||||
*do_file = false;
|
||||
ad->do_file = false;
|
||||
} else if (!complete_param(
|
||||
cmd, ad->previous_argument, ad->current_argument,
|
||||
!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
|
||||
// command line as \p command_range, invoke command-specific completions.
|
||||
// Invoke command-specific completions given by \p arg_data.
|
||||
// Then, for each target wrapped by the given command, update the command
|
||||
// line with that target and invoke this recursively.
|
||||
void completer_t::walk_wrap_chain(const wcstring &command_line, source_range_t command_range,
|
||||
const wcstring &previous_argument,
|
||||
const wcstring ¤t_argument, bool had_ddash,
|
||||
wrap_chain_visited_set_t *visited, size_t depth, bool *do_file) {
|
||||
// The command whose completions to use is given by \p cmd. The full command line is given by \p
|
||||
// cmdline and the command's range in it is given by \p cmdrange. Note: the command range
|
||||
// may have a different length than the command itself, because the command is unescaped (i.e.
|
||||
// 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.
|
||||
if (depth > 24) return;
|
||||
if (ad->wrap_depth > 24) return;
|
||||
if (ctx.cancel_checker()) return;
|
||||
|
||||
// Extract command from the command line and invoke the receiver with it.
|
||||
wcstring command{command_line, command_range.start, command_range.length};
|
||||
complete_custom(command, command_line, previous_argument, current_argument, had_ddash, depth,
|
||||
do_file);
|
||||
complete_custom(cmd, cmdline, ad);
|
||||
|
||||
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) {
|
||||
// Construct a fake command line containing the wrap target.
|
||||
wcstring faux_commandline = command_line;
|
||||
faux_commandline.replace(command_range.start, command_range.length, wt);
|
||||
wcstring faux_commandline = cmdline;
|
||||
faux_commandline.replace(cmdrange.start, cmdrange.length, wt);
|
||||
|
||||
// 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
|
||||
|
@ -1420,15 +1437,14 @@ void completer_t::walk_wrap_chain(const wcstring &command_line, source_range_t c
|
|||
wcstring wrapped_command = tok_first(wt);
|
||||
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 &&
|
||||
"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.
|
||||
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())};
|
||||
walk_wrap_chain(faux_commandline, faux_source_range, previous_argument,
|
||||
current_argument, had_ddash, visited, depth + 1, do_file);
|
||||
walk_wrap_chain(wrapped_command, faux_commandline, faux_source_range, ad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1520,8 +1536,9 @@ void completer_t::perform_for_command(wcstring cmd) {
|
|||
}};
|
||||
|
||||
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
|
||||
// argument (#2477).
|
||||
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);
|
||||
|
||||
// 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)))
|
||||
tokens.erase(tokens.begin());
|
||||
}
|
||||
|
||||
// Empty process (cursor is after one of ;, &, |, \n, &&, || modulo whitespace).
|
||||
if (tokens.empty()) {
|
||||
// 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_abbr(L"");
|
||||
|
@ -1615,30 +1633,33 @@ void completer_t::perform_for_command(wcstring cmd) {
|
|||
do_file = true;
|
||||
} else {
|
||||
// Try completing as an argument.
|
||||
wcstring current_command = cmd_tok.get_source(cmd), current_command_unescape,
|
||||
previous_argument_unescape, current_argument_unescape;
|
||||
if (unescape_string(current_command, ¤t_command_unescape, UNESCAPE_DEFAULT) &&
|
||||
unescape_string(previous_argument, &previous_argument_unescape, UNESCAPE_DEFAULT) &&
|
||||
unescape_string(current_argument, ¤t_argument_unescape, UNESCAPE_INCOMPLETE)) {
|
||||
custom_arg_data_t arg_data{};
|
||||
arg_data.had_ddash = had_ddash;
|
||||
|
||||
assert(cmd_tok.offset < std::numeric_limits<uint32_t>::max());
|
||||
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
|
||||
// disables do_file, then they all do.
|
||||
do_file = true;
|
||||
assert(cmd_tok.offset < std::numeric_limits<uint32_t>::max());
|
||||
assert(cmd_tok.length < std::numeric_limits<uint32_t>::max());
|
||||
source_range_t range = {static_cast<uint32_t>(cmd_tok.offset),
|
||||
static_cast<uint32_t>(cmd_tok.length)};
|
||||
wrap_chain_visited_set_t visited;
|
||||
walk_wrap_chain(cmd, range, previous_argument_unescape, current_argument_unescape,
|
||||
had_ddash, &visited, 0, &do_file);
|
||||
walk_wrap_chain(unesc_command, cmd, command_range, &arg_data);
|
||||
do_file = arg_data.do_file;
|
||||
|
||||
// If we're autosuggesting, and the token is empty, don't do file suggestions.
|
||||
if (is_autosuggest && arg_data.current_argument.empty()) {
|
||||
do_file = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Hack. If we're cd, handle it specially (issue #1059, others).
|
||||
handle_as_special_cd = (current_command_unescape == 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;
|
||||
}
|
||||
handle_as_special_cd = (unesc_command == L"cd");
|
||||
}
|
||||
|
||||
// This function wants the unescaped string.
|
||||
|
|
Loading…
Reference in a new issue