From b28eae8be9dfff290cddadf8c54f43cacad49cc5 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 12 Sep 2022 15:33:29 -0700 Subject: [PATCH] Allow custom completions to have leading dots By default, fish does not complete files that have leading dots, unless the wildcard itself has a leading dot. However this also affected completions; for example `git add` would not offer `.gitlab-ci.yml` because it has a leading dot. Relax this for custom completions. Default file expansion still suppresses leading dots, but now custom completions can create leading-dot completions and they will be offered. Fixes #3707. (cherry picked from commit b7de768c73f0a9b0a097b83db1f60726c3a9661a) --- CHANGELOG.rst | 1 + src/complete.cpp | 19 +++++++++++++------ src/expand.h | 4 ++++ src/wildcard.cpp | 3 ++- tests/checks/complete.fish | 16 ++++++++++++++++ 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c030f1fbd..bf649bbf3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -49,6 +49,7 @@ Interactive improvements - A longstanding issue where items in the pager would sometimes display without proper formatting has been fixed (:issue:`9617`). - The :kbd:`Alt` +\ :kbd:`l` binding, which lists the directory of the token under the cursor, correctly expands tilde (``~``) to the home directory (:issue:`9954`). - Various fish utilities that use an external pager will now try a selection of common pagers if the :envvar:`PAGER` environment variable is not set, or write the output to the screen without a pager if there is not one available (:issue:`10074`) +- Command-specific tab completions may now offer results whose first character is a period. For example, it is now possible to tab-complete ``git add`` for files with leading periods. The default file completions hide these files, unless the token itself has a leading period (:issue:`3707`). Improved prompts ^^^^^^^^^^^^^^^^ diff --git a/src/complete.cpp b/src/complete.cpp index aaac08145..08542c42f 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -366,7 +366,8 @@ class completer_t { bool conditions_test(const wcstring_list_t &conditions); void complete_strings(const wcstring &wc_escaped, const description_func_t &desc_func, - const completion_list_t &possible_comp, complete_flags_t flags); + const completion_list_t &possible_comp, complete_flags_t flags, + expand_flags_t extra_expand_flags = {}); expand_flags_t expand_flags() const { expand_flags_t result{}; @@ -510,12 +511,16 @@ static void parse_cmd_string(const wcstring &str, wcstring *path, wcstring *cmd, /// @param possible_comp /// the list of possible completions to iterate over /// @param flags -/// The flags +/// The flags controlling completion +/// @param extra_expand_flags +/// Additional flags controlling expansion. void completer_t::complete_strings(const wcstring &wc_escaped, const description_func_t &desc_func, - const completion_list_t &possible_comp, complete_flags_t flags) { + const completion_list_t &possible_comp, complete_flags_t flags, + expand_flags_t extra_expand_flags) { wcstring tmp = wc_escaped; if (!expand_one(tmp, - this->expand_flags() | expand_flag::skip_cmdsubst | expand_flag::skip_wildcards, + this->expand_flags() | extra_expand_flags | expand_flag::skip_cmdsubst | + expand_flag::skip_wildcards, ctx)) return; @@ -525,7 +530,7 @@ void completer_t::complete_strings(const wcstring &wc_escaped, const description const wcstring &comp_str = comp.completion; if (!comp_str.empty()) { wildcard_complete(comp_str, wc.c_str(), desc_func, &this->completions, - this->expand_flags(), flags); + this->expand_flags() | extra_expand_flags, flags); } } } @@ -730,7 +735,9 @@ void completer_t::complete_from_args(const wcstring &str, const wcstring &args, ctx.parser->set_last_statuses(status); } - this->complete_strings(escape_string(str), const_desc(desc), possible_comp, flags); + // Allow leading dots - see #3707. + this->complete_strings(escape_string(str), const_desc(desc), possible_comp, flags, + expand_flag::allow_nonliteral_leading_dot); } static size_t leading_dash_count(const wchar_t *str) { diff --git a/src/expand.h b/src/expand.h index 22f7a37b9..e35693f2a 100644 --- a/src/expand.h +++ b/src/expand.h @@ -45,6 +45,10 @@ enum class expand_flag { /// Disallow directory abbreviations like /u/l/b for /usr/local/bin. Only applicable if /// fuzzy_match is set. no_fuzzy_directories, + /// Allows matching a leading dot even if the wildcard does not contain one. + /// By default, wildcards only match a leading dot literally; this is why e.g. '*' does not + /// match hidden files. + allow_nonliteral_leading_dot, /// Do expansions specifically to support cd. This means using CDPATH as a list of potential /// working directories, and to use logical instead of physical paths. special_for_cd, diff --git a/src/wildcard.cpp b/src/wildcard.cpp index eb0bd44a5..2683cfa1b 100644 --- a/src/wildcard.cpp +++ b/src/wildcard.cpp @@ -191,7 +191,8 @@ static wildcard_result_t wildcard_complete_internal(const wchar_t *const str, si // 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'.') { + if (is_first_call && !params.expand_flags.get(expand_flag::allow_nonliteral_leading_dot) && + str[0] == L'.' && wc[0] != L'.') { return wildcard_result_t::no_match; } diff --git a/tests/checks/complete.fish b/tests/checks/complete.fish index be13fb0d1..5fc76c9ed 100644 --- a/tests/checks/complete.fish +++ b/tests/checks/complete.fish @@ -531,6 +531,22 @@ begin # CHECK: Empty completions end +rm -$f $tmpdir/* + +# Leading dots are not completed for default file completion, +# but may be for custom command (e.g. git add). +function dotty +end +function notty +end +complete -c dotty --no-files -a '(echo .a*)' +touch .abc .def +complete -C'notty ' +echo "Should be nothing" +# CHECK: Should be nothing +complete -C'dotty ' +# CHECK: .abc + rm -r $tmpdir complete -C'complete --command=mktemp' | string replace -rf '=mktemp\t.*' '=mktemp'