Adopt completion_receiver_t more widely

This switches certain uses from just appending to a list to using
completion_receiver_t, in preparation for limiting how many completions
may be produced. Perhaps in time this could also be used for "streaming"
completions.
This commit is contained in:
ridiculousfish 2020-12-01 13:19:34 -08:00
parent 245f264c04
commit 48567c37de
6 changed files with 73 additions and 39 deletions

View File

@ -337,7 +337,7 @@ class completer_t {
const completion_request_flags_t flags;
/// The output completions.
completion_list_t completions;
completion_receiver_t completions;
/// Table of completions conditions that have already been tested and the corresponding test
/// results.
@ -440,7 +440,7 @@ class completer_t {
void perform_for_commandline(wcstring cmdline);
completion_list_t acquire_completions() { return std::move(completions); }
completion_list_t acquire_completions() { return completions.take(); }
};
// Autoloader for completions.
@ -636,8 +636,8 @@ void completer_t::complete_cmd_desc(const wcstring &str) {
}
bool skip = true;
for (const auto &c : completions) {
if (c.completion.empty() || (c.completion[c.completion.size() - 1] != L'/')) {
for (const auto &c : completions.get_list()) {
if (c.completion.empty() || (c.completion.back() != L'/')) {
skip = false;
break;
}
@ -688,7 +688,7 @@ void completer_t::complete_cmd_desc(const wcstring &str) {
// Then do a lookup on every completion and if a match is found, change to the new
// description.
for (auto &completion : completions) {
for (auto &completion : completions.get_list()) {
const wcstring &el = completion.completion;
auto new_desc_iter = lookup.find(el);
if (new_desc_iter != lookup.end()) completion.description = new_desc_iter->second;
@ -1073,7 +1073,7 @@ bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt,
// It's a match.
wcstring desc = o.localized_desc();
// Append a short-style option
append_completion(&this->completions, o.option, std::move(desc), 0);
this->completions.add(wcstring{o.option}, std::move(desc), 0);
}
// Check if the long style option matches.
@ -1116,12 +1116,11 @@ bool completer_t::complete_param(const wcstring &cmd_orig, const wcstring &popt,
// functions.
wcstring completion = format_string(L"%ls=", whole_opt.c_str() + offset);
// Append a long-style option with a mandatory trailing equal sign
append_completion(&this->completions, std::move(completion), C_(o.desc),
flags | COMPLETE_NO_SPACE);
this->completions.add(std::move(completion), C_(o.desc), flags | COMPLETE_NO_SPACE);
}
// Append a long-style option
append_completion(&this->completions, whole_opt.substr(offset), C_(o.desc), flags);
this->completions.add(whole_opt.substr(offset), C_(o.desc), flags);
}
}
@ -1175,8 +1174,7 @@ void completer_t::complete_param_expand(const wcstring &str, bool do_file,
for (completion_t &comp : local_completions) {
comp.prepend_token_prefix(prefix_with_sep);
}
this->completions.insert(this->completions.end(), local_completions.begin(),
local_completions.end());
this->completions.add_list(std::move(local_completions));
}
if (complete_from_start) {
@ -1239,7 +1237,7 @@ bool completer_t::complete_variable(const wcstring &str, size_t start_offset) {
}
// Append matching environment variables
append_completion(&this->completions, std::move(comp), std::move(desc), flags, *match);
this->completions.add(std::move(comp), std::move(desc), flags, *match);
res = true;
}
@ -1343,14 +1341,14 @@ bool completer_t::try_complete_user(const wcstring &str) {
if (std::wcsncmp(user_name, pw_name, name_len) == 0) {
wcstring desc = format_string(COMPLETE_USER_DESC, pw_name);
// Append a user name
append_completion(&this->completions, &pw_name[name_len], std::move(desc),
COMPLETE_NO_SPACE);
this->completions.add(&pw_name[name_len], std::move(desc), COMPLETE_NO_SPACE);
result = true;
} else if (wcsncasecmp(user_name, pw_name, name_len) == 0) {
wcstring name = format_string(L"~%ls", pw_name);
wcstring desc = format_string(COMPLETE_USER_DESC, pw_name);
// Append a user name
append_completion(&this->completions, std::move(name), std::move(desc),
this->completions.add(
std::move(name), std::move(desc),
COMPLETE_REPLACES_TOKEN | COMPLETE_DONT_ESCAPE | COMPLETE_NO_SPACE);
result = true;
}
@ -1517,7 +1515,7 @@ void completer_t::escape_opening_brackets(const wcstring &argument) {
// unescaped version.
wcstring unescaped_argument;
if (!unescape_string(argument, &unescaped_argument, UNESCAPE_INCOMPLETE)) return;
for (completion_t &comp : completions) {
for (completion_t &comp : completions.get_list()) {
if (comp.flags & COMPLETE_REPLACES_TOKEN) continue;
comp.flags |= COMPLETE_REPLACES_TOKEN;
if (comp.flags & COMPLETE_DONT_ESCAPE) {
@ -1546,7 +1544,7 @@ void completer_t::mark_completions_duplicating_arguments(const wcstring &cmd,
std::sort(arg_strs.begin(), arg_strs.end());
wcstring comp_str;
for (completion_t &comp : completions) {
for (completion_t &comp : completions.get_list()) {
comp_str = comp.completion;
if (!(comp.flags & COMPLETE_REPLACES_TOKEN)) {
comp_str.insert(0, prefix);

View File

@ -124,6 +124,10 @@ using completion_list_t = std::vector<completion_t>;
/// some conveniences.
class completion_receiver_t {
public:
/// Construct, perhaps acquiring a list if necessary.
completion_receiver_t() = default;
explicit completion_receiver_t(completion_list_t &&v) : completions_(std::move(v)) {}
/// Add a completion.
void add(completion_t &&comp);
@ -144,6 +148,21 @@ class completion_receiver_t {
/// useful to prevent allocations.
void clear() { completions_.clear(); }
/// \return whether our completion list is empty.
bool empty() const { return completions_.empty(); }
/// \return how many completions we have stored.
size_t size() const { return completions_.size(); }
/// \return a completion at an index.
completion_t &at(size_t idx) { return completions_.at(idx); }
const completion_t &at(size_t idx) const { return completions_.at(idx); }
/// \return the list of completions. Do not modify the size of the list via this function, as it
/// may exceed our completion limit.
const completion_list_t &get_list() const { return completions_; }
completion_list_t &get_list() { return completions_; }
/// \return the list of completions, clearing them.
completion_list_t take();

View File

@ -883,7 +883,7 @@ class expander_t {
: ctx(ctx), flags(flags), errors(errors) {}
public:
static expand_result_t expand_string(wcstring input, completion_list_t *out_completions,
static expand_result_t expand_string(wcstring input, completion_receiver_t *out_completions,
expand_flags_t flags, const operation_context_t &ctx,
parse_error_list_t *errors);
};
@ -1007,10 +1007,10 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_
}
result = expand_result_t::wildcard_no_match;
completion_list_t expanded;
completion_receiver_t expanded_recv;
for (const auto &effective_working_dir : effective_working_dirs) {
wildcard_expand_result_t expand_res = wildcard_expand_string(
path_to_expand, effective_working_dir, flags, ctx.cancel_checker, &expanded);
path_to_expand, effective_working_dir, flags, ctx.cancel_checker, &expanded_recv);
switch (expand_res) {
case wildcard_expand_result_t::match:
result = expand_result_t::ok;
@ -1023,6 +1023,7 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_
}
}
completion_list_t expanded = expanded_recv.take();
std::sort(expanded.begin(), expanded.end(),
[&](const completion_t &a, const completion_t &b) {
return wcsfilecmp_glob(a.completion.c_str(), b.completion.c_str()) < 0;
@ -1039,14 +1040,14 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_
return result;
}
expand_result_t expander_t::expand_string(wcstring input, completion_list_t *out_completions,
expand_result_t expander_t::expand_string(wcstring input, completion_receiver_t *out_completions,
expand_flags_t flags, const operation_context_t &ctx,
parse_error_list_t *errors) {
assert(((flags & expand_flag::skip_cmdsubst) || ctx.parser) &&
"Must have a parser if not skipping command substitutions");
// Early out. If we're not completing, and there's no magic in the input, we're done.
if (!(flags & expand_flag::for_completions) && expand_is_clean(input)) {
append_completion(out_completions, std::move(input));
out_completions->add(std::move(input));
return expand_result_t::ok;
}
@ -1100,7 +1101,7 @@ expand_result_t expander_t::expand_string(wcstring input, completion_list_t *out
if (!(flags & expand_flag::skip_home_directories)) {
unexpand_tildes(input, ctx.vars, &completions);
}
vec_append(*out_completions, std::move(completions));
out_completions->add_list(std::move(completions));
}
return total_result;
}
@ -1109,6 +1110,15 @@ expand_result_t expander_t::expand_string(wcstring input, completion_list_t *out
expand_result_t expand_string(wcstring input, completion_list_t *out_completions,
expand_flags_t flags, const operation_context_t &ctx,
parse_error_list_t *errors) {
completion_receiver_t recv(std::move(*out_completions));
auto res = expand_string(std::move(input), &recv, flags, ctx, errors);
*out_completions = recv.take();
return res;
}
expand_result_t expand_string(wcstring input, completion_receiver_t *out_completions,
expand_flags_t flags, const operation_context_t &ctx,
parse_error_list_t *errors) {
return expander_t::expand_string(std::move(input), out_completions, flags, ctx, errors);
}

View File

@ -69,6 +69,7 @@ using expand_flags_t = enum_set_t<expand_flag>;
class completion_t;
using completion_list_t = std::vector<completion_t>;
class completion_receiver_t;
enum : wchar_t {
/// Character representing a home directory.
@ -158,6 +159,11 @@ __warn_unused expand_result_t expand_string(wcstring input, completion_list_t *o
expand_flags_t flags, const operation_context_t &ctx,
parse_error_list_t *errors = nullptr);
/// Variant of string that inserts its results into a completion_receiver_t.
__warn_unused expand_result_t expand_string(wcstring input, completion_receiver_t *output,
expand_flags_t flags, const operation_context_t &ctx,
parse_error_list_t *errors = nullptr);
/// expand_one is identical to expand_string, except it will fail if in expands to more than one
/// string. This is used for expanding command names.
///

View File

@ -187,7 +187,7 @@ struct wc_complete_pack_t {
};
// Weirdly specific and non-reusable helper function that makes its one call site much clearer.
static bool has_prefix_match(const completion_list_t *comps, size_t first) {
static bool has_prefix_match(const completion_receiver_t *comps, size_t first) {
if (comps != nullptr) {
const size_t after_count = comps->size();
for (size_t j = first; j < after_count; j++) {
@ -209,7 +209,7 @@ static bool has_prefix_match(const completion_list_t *comps, size_t first) {
static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len,
const wchar_t *const wc, size_t wc_len,
const wc_complete_pack_t &params, complete_flags_t flags,
completion_list_t *out, bool is_first_call = false) {
completion_receiver_t *out, bool is_first_call = false) {
assert(str != nullptr);
assert(wc != nullptr);
@ -253,7 +253,7 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len,
// Note: out_completion may be empty if the completion really is empty, e.g. tab-completing
// 'foo' when a file 'foo' exists.
complete_flags_t local_flags = flags | (full_replacement ? COMPLETE_REPLACES_TOKEN : 0);
append_completion(out, out_completion, out_desc, local_flags, *match);
out->add(std::move(out_completion), std::move(out_desc), local_flags, *match);
return true;
} else if (next_wc_char_pos > 0) {
// The literal portion of a wildcard cannot be longer than the string itself,
@ -331,7 +331,7 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len,
bool wildcard_complete(const wcstring &str, const wchar_t *wc,
const std::function<wcstring(const wcstring &)> &desc_func,
completion_list_t *out, expand_flags_t expand_flags,
completion_receiver_t *out, expand_flags_t expand_flags,
complete_flags_t flags) {
// Note out may be NULL.
assert(wc != nullptr);
@ -449,7 +449,7 @@ static const wchar_t *file_get_desc(int lstat_res, const struct stat &lbuf, int
/// up. Note that the filename came from a readdir() call, so we know it exists.
static bool wildcard_test_flags_then_complete(const wcstring &filepath, const wcstring &filename,
const wchar_t *wc, expand_flags_t expand_flags,
completion_list_t *out) {
completion_receiver_t *out) {
// Check if it will match before stat().
if (!wildcard_complete(filename, wc, {}, nullptr, expand_flags, 0)) {
return false;
@ -526,7 +526,7 @@ class wildcard_expander_t {
// Flags controlling expansion.
const expand_flags_t flags;
// Resolved items get inserted into here. This is transient of course.
completion_list_t *resolved_completions;
completion_receiver_t *resolved_completions;
// Whether we have been interrupted.
bool did_interrupt{false};
// Whether we have successfully added any completions.
@ -567,11 +567,11 @@ class wildcard_expander_t {
return did_interrupt;
}
void add_expansion_result(const wcstring &result) {
void add_expansion_result(wcstring &&result) {
// This function is only for the non-completions case.
assert(!(this->flags & expand_flag::for_completions));
if (this->completion_set.insert(result).second) {
append_completion(this->resolved_completions, result);
this->resolved_completions->add(std::move(result));
this->did_add = true;
}
}
@ -691,7 +691,7 @@ class wildcard_expander_t {
public:
wildcard_expander_t(wcstring wd, expand_flags_t f, cancel_checker_t cancel_checker,
completion_list_t *r)
completion_receiver_t *r)
: cancel_checker(std::move(cancel_checker)),
working_directory(std::move(wd)),
flags(f),
@ -699,7 +699,7 @@ class wildcard_expander_t {
assert(resolved_completions != nullptr);
// Insert initial completions into our set to avoid duplicates.
for (const auto &resolved_completion : *resolved_completions) {
for (const auto &resolved_completion : resolved_completions->get_list()) {
this->completion_set.insert(resolved_completion.completion);
}
}
@ -724,7 +724,7 @@ void wildcard_expander_t::expand_trailing_slash(const wcstring &base_dir, const
// Trailing slash and not accepting incomplete, e.g. `echo /xyz/`. Insert this file if it
// exists.
if (waccess(base_dir, F_OK) == 0) {
this->add_expansion_result(base_dir);
this->add_expansion_result(wcstring{base_dir});
}
} else {
// Trailing slashes and accepting incomplete, e.g. `echo /xyz/<tab>`. Everything is added.
@ -961,7 +961,7 @@ wildcard_expand_result_t wildcard_expand_string(const wcstring &wc,
const wcstring &working_directory,
expand_flags_t flags,
const cancel_checker_t &cancel_checker,
completion_list_t *output) {
completion_receiver_t *output) {
assert(output != nullptr);
// Fuzzy matching only if we're doing completions.
assert(flags.get(expand_flag::for_completions) || !flags.get(expand_flag::fuzzy_match));

View File

@ -39,7 +39,7 @@ enum {
/// \param working_directory The working directory
/// \param flags flags for the search. Can be any combination of for_completions and
/// executables_only
/// \param out The list in which to put the output
/// \param output The list in which to put the output
///
enum class wildcard_expand_result_t {
no_match, /// The wildcard did not match.
@ -50,7 +50,7 @@ wildcard_expand_result_t wildcard_expand_string(const wcstring &wc,
const wcstring &working_directory,
expand_flags_t flags,
const cancel_checker_t &cancel_checker,
completion_list_t *out);
completion_receiver_t *output);
/// Test whether the given wildcard matches the string. Does not perform any I/O.
///
@ -69,6 +69,7 @@ bool wildcard_has(const wchar_t *, bool internal);
/// Test wildcard completion.
bool wildcard_complete(const wcstring &str, const wchar_t *wc, const description_func_t &desc_func,
completion_list_t *out, expand_flags_t expand_flags, complete_flags_t flags);
completion_receiver_t *out, expand_flags_t expand_flags,
complete_flags_t flags);
#endif