diff --git a/src/complete.h b/src/complete.h index 5593ee1e8..02d5a76ea 100644 --- a/src/complete.h +++ b/src/complete.h @@ -124,12 +124,13 @@ using completion_list_t = std::vector; /// some conveniences. class completion_receiver_t { public: - /// The default limit on expansions. + /// The default maximum number of items that something may expand to. static constexpr size_t k_default_expansion_limit = 512 * 1024; - /// Construct with a limit. + /// Construct as empty, with a limit. explicit completion_receiver_t(size_t limit = k_default_expansion_limit) : limit_(limit) {} + /// Acquire an existing list, with a limit. explicit completion_receiver_t(completion_list_t &&v, size_t limit = k_default_expansion_limit) : completions_(std::move(v)), limit_(limit) {} diff --git a/src/expand.cpp b/src/expand.cpp index 6f3c94021..ca1a2809f 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -1037,20 +1037,18 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_ result = expand_result_t::wildcard_no_match; completion_receiver_t expanded_recv = out->subreceiver(); for (const auto &effective_working_dir : effective_working_dirs) { - wildcard_expand_result_t expand_res = wildcard_expand_string( + wildcard_result_t expand_res = wildcard_expand_string( path_to_expand, effective_working_dir, flags, ctx.cancel_checker, &expanded_recv); switch (expand_res) { - case wildcard_expand_result_t::match: + case wildcard_result_t::match: result = expand_result_t::ok; break; - case wildcard_expand_result_t::no_match: - break; - case wildcard_expand_result_t::overflow: - result = expand_result_t::error; - break; - case wildcard_expand_result_t::cancel: - result = expand_result_t::cancel; + case wildcard_result_t::no_match: break; + case wildcard_result_t::overflow: + return append_overflow_error(errors); + case wildcard_result_t::cancel: + return expand_result_t::cancel; } } diff --git a/src/wildcard.cpp b/src/wildcard.cpp index 4ab4a9f9f..f5d101b63 100644 --- a/src/wildcard.cpp +++ b/src/wildcard.cpp @@ -206,17 +206,19 @@ static bool has_prefix_match(const completion_receiver_t *comps, size_t first) { /// /// We ignore ANY_STRING_RECURSIVE here. The consequence is that you cannot tab complete ** /// wildcards. This is historic behavior. -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_receiver_t *out, bool is_first_call = false) { +static wildcard_result_t 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_receiver_t *out, + bool is_first_call = false) { assert(str != nullptr); assert(wc != nullptr); // Maybe early out for hidden files. We require that the wildcard match these exactly (i.e. a // dot); ANY_STRING not allowed. if (is_first_call && str[0] == L'.' && wc[0] != L'.') { - return false; + return wildcard_result_t::no_match; } // Locate the next wildcard character position, e.g. ANY_CHAR or ANY_STRING. @@ -226,17 +228,17 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len, if (next_wc_char_pos == wcstring::npos) { // Try matching. maybe_t match = string_fuzzy_match_string(wc, str); - if (!match) return false; + if (!match) return wildcard_result_t::no_match; // If we're not allowing fuzzy match, then we require a prefix match. bool needs_prefix_match = !(params.expand_flags & expand_flag::fuzzy_match); if (needs_prefix_match && !match->is_exact_or_prefix()) { - return false; + return wildcard_result_t::no_match; } // The match was successful. If the string is not requested we're done. if (out == nullptr) { - return true; + return wildcard_result_t::match; } // Wildcard complete. @@ -254,14 +256,14 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len, // 'foo' when a file 'foo' exists. complete_flags_t local_flags = flags | (full_replacement ? COMPLETE_REPLACES_TOKEN : 0); if (!out->add(std::move(out_completion), std::move(out_desc), local_flags, *match)) { - return false; + return wildcard_result_t::overflow; } - return true; + return wildcard_result_t::match; } else if (next_wc_char_pos > 0) { // The literal portion of a wildcard cannot be longer than the string itself, // e.g. `abc*` can never match a string that is only two characters long. if (next_wc_char_pos >= str_len) { - return false; + return wildcard_result_t::no_match; } // Here we have a non-wildcard prefix. Note that we don't do fuzzy matching for stuff before @@ -278,7 +280,7 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len, wc + next_wc_char_pos, wc_len - next_wc_char_pos, params, flags | COMPLETE_REPLACES_TOKEN, out); } - return false; // no match + return wildcard_result_t::no_match; } // Our first character is a wildcard. @@ -286,7 +288,7 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len, switch (wc[0]) { case ANY_CHAR: { if (str[0] == L'\0') { - return false; + return wildcard_result_t::no_match; } return wildcard_complete_internal(str + 1, str_len - 1, wc + 1, wc_len - 1, params, flags, out); @@ -305,23 +307,30 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len, bool has_match = false; for (size_t i = 0; str[i] != L'\0'; i++) { const size_t before_count = out ? out->size() : 0; - if (wildcard_complete_internal(str + i, str_len - i, wc + 1, wc_len - 1, params, - flags, out)) { - // We found a match. - has_match = true; - - // If out is NULL, we don't care about the actual matches. If out is not - // NULL but we have a prefix match, stop there. - if (out == nullptr || has_prefix_match(out, before_count)) { + auto submatch_res = wildcard_complete_internal(str + i, str_len - i, wc + 1, + wc_len - 1, params, flags, out); + switch (submatch_res) { + case wildcard_result_t::no_match: break; - } + case wildcard_result_t::match: + has_match = true; + // If out is NULL, we don't care about the actual matches. If out is not + // NULL but we have a prefix match, stop there. + if (out == nullptr || has_prefix_match(out, before_count)) { + return wildcard_result_t::match; + } + break; + case wildcard_result_t::cancel: + case wildcard_result_t::overflow: + // Note early return. + return submatch_res; } } - return has_match; + return has_match ? wildcard_result_t::match : wildcard_result_t::no_match; } case ANY_STRING_RECURSIVE: { // We don't even try with this one. - return false; + return wildcard_result_t::no_match; } default: { DIE("unreachable code reached"); @@ -331,10 +340,10 @@ static bool wildcard_complete_internal(const wchar_t *const str, size_t str_len, DIE("unreachable code reached"); } -bool wildcard_complete(const wcstring &str, const wchar_t *wc, - const std::function &desc_func, - completion_receiver_t *out, expand_flags_t expand_flags, - complete_flags_t flags) { +wildcard_result_t wildcard_complete(const wcstring &str, const wchar_t *wc, + const std::function &desc_func, + completion_receiver_t *out, expand_flags_t expand_flags, + complete_flags_t flags) { // Note out may be NULL. assert(wc != nullptr); wc_complete_pack_t params(str, desc_func, expand_flags); @@ -453,7 +462,7 @@ static bool wildcard_test_flags_then_complete(const wcstring &filepath, const wc const wchar_t *wc, expand_flags_t expand_flags, completion_receiver_t *out) { // Check if it will match before stat(). - if (!wildcard_complete(filename, wc, {}, nullptr, expand_flags, 0)) { + if (wildcard_complete(filename, wc, {}, nullptr, expand_flags, 0) != wildcard_result_t::match) { return false; } @@ -511,9 +520,10 @@ static bool wildcard_test_flags_then_complete(const wcstring &filepath, const wc auto desc_func = const_desc(desc); if (is_directory) { return wildcard_complete(filename + L'/', wc, desc_func, out, expand_flags, - COMPLETE_NO_SPACE); + COMPLETE_NO_SPACE) == wildcard_result_t::match; } - return wildcard_complete(filename, wc, desc_func, out, expand_flags, 0); + return wildcard_complete(filename, wc, desc_func, out, expand_flags, 0) == + wildcard_result_t::match; } class wildcard_expander_t { @@ -712,11 +722,13 @@ class wildcard_expander_t { // Do wildcard expansion. This is recursive. void expand(const wcstring &base_dir, const wchar_t *wc, const wcstring &prefix); - wildcard_expand_result_t status_code() const { + wildcard_result_t status_code() const { if (this->did_interrupt) { - return wildcard_expand_result_t::cancel; + return wildcard_result_t::cancel; + } else if (this->did_overflow) { + return wildcard_result_t::overflow; } - return this->did_add ? wildcard_expand_result_t::match : wildcard_expand_result_t::no_match; + return this->did_add ? wildcard_result_t::match : wildcard_result_t::no_match; } }; @@ -962,11 +974,10 @@ void wildcard_expander_t::expand(const wcstring &base_dir, const wchar_t *wc, } } -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_receiver_t *output) { +wildcard_result_t wildcard_expand_string(const wcstring &wc, const wcstring &working_directory, + expand_flags_t flags, + const cancel_checker_t &cancel_checker, + 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)); @@ -983,7 +994,7 @@ wildcard_expand_result_t wildcard_expand_string(const wcstring &wc, // embedded nulls are never allowed in a filename, so we just check for them and return 0 (no // matches) if there is an embedded null. if (wc.find(L'\0') != wcstring::npos) { - return wildcard_expand_result_t::no_match; + return wildcard_result_t::no_match; } // Compute the prefix and base dir. The prefix is what we prepend for filesystem operations diff --git a/src/wildcard.h b/src/wildcard.h index 17dc21983..2c1cf055d 100644 --- a/src/wildcard.h +++ b/src/wildcard.h @@ -41,17 +41,16 @@ enum { /// executables_only /// \param output The list in which to put the output /// -enum class wildcard_expand_result_t { +enum class wildcard_result_t { no_match, /// The wildcard did not match. match, /// The wildcard did match. cancel, /// Expansion was cancelled (e.g. control-C). overflow, /// Expansion produced too many results. }; -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_receiver_t *output); +wildcard_result_t wildcard_expand_string(const wcstring &wc, const wcstring &working_directory, + expand_flags_t flags, + const cancel_checker_t &cancel_checker, + completion_receiver_t *output); /// Test whether the given wildcard matches the string. Does not perform any I/O. /// @@ -69,8 +68,8 @@ bool wildcard_has(const wcstring &, bool internal); 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_receiver_t *out, expand_flags_t expand_flags, - complete_flags_t flags); +wildcard_result_t wildcard_complete(const wcstring &str, const wchar_t *wc, + const description_func_t &desc_func, completion_receiver_t *out, + expand_flags_t expand_flags, complete_flags_t flags); #endif