diff --git a/src/complete.cpp b/src/complete.cpp index 1128c149c..6ff4d864a 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -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,15 +1341,15 @@ 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), - COMPLETE_REPLACES_TOKEN | COMPLETE_DONT_ESCAPE | COMPLETE_NO_SPACE); + 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); diff --git a/src/complete.h b/src/complete.h index f5d6f7cb0..6ec605f53 100644 --- a/src/complete.h +++ b/src/complete.h @@ -124,6 +124,10 @@ using completion_list_t = std::vector; /// 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(); diff --git a/src/expand.cpp b/src/expand.cpp index 886b26bf7..d14915511 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -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); } diff --git a/src/expand.h b/src/expand.h index 67c0ead0e..037b9b5cc 100644 --- a/src/expand.h +++ b/src/expand.h @@ -69,6 +69,7 @@ using expand_flags_t = enum_set_t; class completion_t; using completion_list_t = std::vector; +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. /// diff --git a/src/wildcard.cpp b/src/wildcard.cpp index db3405a0a..306cf6ab3 100644 --- a/src/wildcard.cpp +++ b/src/wildcard.cpp @@ -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 ¶ms, 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 &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/`. 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)); diff --git a/src/wildcard.h b/src/wildcard.h index dc087afda..268953e88 100644 --- a/src/wildcard.h +++ b/src/wildcard.h @@ -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