From 0e7a0cf04bd9dbb190e977323ff16a24c6e172e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABlan?= Date: Wed, 13 Nov 2024 21:32:19 +0100 Subject: [PATCH] =?UTF-8?q?feat(=5Fcomp=5Fcompgen=5Ffiledir,=5Fcomp=5Fcomp?= =?UTF-8?q?gen=5Ffiledir=5Fxspec):=20don=E2=80=99t=20suggest=20.=20and=20.?= =?UTF-8?q?.=20(#1230)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(_comp_compgen_filedir{,_xspec}): do not generate "." and ".." Currently, "." and ".." are generated as completions when the current path segment starts with a dot. However, this impedes the completion of any dotfiles when the current path segments is "." because the word "." already matches the generated ".". When the user attempts the completion after inputting ".", the user is likely to intend to complete a dotfile name as ".dotfile" (rather than to complete just a slash as "./" because the user could just press "/" in such a case). In this patch, we do not generate "." and ".." unless the user has explicitly input "..". The behavioral changes are summarized below. Other possibilities of the detailed behaviors are commented in the "[ Note: ... ]" sections. If necessary, they can be reconsidered and adjusted in later commits. * cmd .[TAB] When the current directory has no dotfiles (i.e., filenames starting with "."), it completed a slash. Nothing will happen after this patch. [ Note: as another option, we might generate "." only when no dotfiles are found. However, that might be annoying when the user tries to probe the existence of the dotfiles by pressing TAB, where the user does not expect the insertion of a slash. ] When the current directory has dotfiles, nothing happened. After this patch, this will insert the common prefix of the dotfiles. Note that both "." and ".." are ignored in determining the common prefix. * cmd ..[TAB] When the current directory has no files starting with "..", this completes a slash to form "../". The behavior will not be changed by this patch. [ Note: as another option, we might disable ".." at all to be consistent with the case of ".". However, the files starting with ".." are unlikely, and the user is less likely to probe the existence of the files starting with ".." by pressing TAB after "..". For this reason, we generate ".." even if it would prevent completion of the common prefix of the files. ] When the current directory has files starting with "..", nothing happens with this. The behavior will not be changed by this patch. [ Note: as another option, we might generate ".." only when there are no files starting with "..", but we here assume that the user may want to complete a slash as ".." even when there are files starting with "..". ] References: https://github.com/scop/bash-completion/pull/364 https://github.com/scop/bash-completion/pull/1230 Co-authored-by: Koichi Murase Co-authored-by: Yedaya Katsman --- bash_completion | 16 ++++++ test/fixtures/_filedir/.dotfile1 | 0 test/fixtures/_filedir/.dotfile2 | 0 .../dotdot/..2016_02_01_15_04_05.123456 | 0 test/fixtures/_filedir/dotdot/..data | 1 + .../_filedir/dotdot/..folder/.nothing_here | 0 test/t/unit/test_unit_compgen_filedir.py | 50 ++++++++++++++++++- test/t/unit/test_unit_expand_glob.py | 5 +- 8 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/_filedir/.dotfile1 create mode 100644 test/fixtures/_filedir/.dotfile2 create mode 100644 test/fixtures/_filedir/dotdot/..2016_02_01_15_04_05.123456 create mode 120000 test/fixtures/_filedir/dotdot/..data create mode 100644 test/fixtures/_filedir/dotdot/..folder/.nothing_here diff --git a/bash_completion b/bash_completion index 1b1c8bef742..66d5e4fa31c 100644 --- a/bash_completion +++ b/bash_completion @@ -1142,6 +1142,15 @@ _comp_compgen_filedir() -f ${_plusdirs+"${_plusdirs[@]}"} fi + if ((${#toks[@]} != 0)); then + # Remove . and .. (as well as */. and */..) from suggestions, unless + # .. or */.. was typed explicitly by the user (for users who use + # tab-completion to append a slash after '..') + if [[ $cur != ?(*/).. ]]; then + _comp_compgen -Rv toks -- -X '?(*/)@(.|..)' -W '"${toks[@]}"' + fi + fi + if ((${#toks[@]} != 0)); then # 2>/dev/null for direct invocation, e.g. in the _comp_compgen_filedir # unit test @@ -3042,6 +3051,13 @@ _comp_compgen_filedir_xspec() ((${#toks[@]})) || return 1 + # Remove . and .. (as well as */. and */..) from suggestions, unless .. or + # */.. was typed explicitly by the user (for users who use tab-completion + # to append a slash after '..') + if [[ $cur != ?(*/).. ]]; then + _comp_compgen -Rv toks -- -X '?(*/)@(.|..)' -W '"${toks[@]}"' || return 1 + fi + compopt -o filenames _comp_compgen -RU toks -- -W '"${toks[@]}"' } diff --git a/test/fixtures/_filedir/.dotfile1 b/test/fixtures/_filedir/.dotfile1 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/fixtures/_filedir/.dotfile2 b/test/fixtures/_filedir/.dotfile2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/fixtures/_filedir/dotdot/..2016_02_01_15_04_05.123456 b/test/fixtures/_filedir/dotdot/..2016_02_01_15_04_05.123456 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/fixtures/_filedir/dotdot/..data b/test/fixtures/_filedir/dotdot/..data new file mode 120000 index 00000000000..80557a61c9c --- /dev/null +++ b/test/fixtures/_filedir/dotdot/..data @@ -0,0 +1 @@ +..2016_02_01_15_04_05.123456 \ No newline at end of file diff --git a/test/fixtures/_filedir/dotdot/..folder/.nothing_here b/test/fixtures/_filedir/dotdot/..folder/.nothing_here new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/t/unit/test_unit_compgen_filedir.py b/test/t/unit/test_unit_compgen_filedir.py index 1e3844faa48..aa18868c76d 100644 --- a/test/t/unit/test_unit_compgen_filedir.py +++ b/test/t/unit/test_unit_compgen_filedir.py @@ -6,7 +6,7 @@ import pytest -from conftest import assert_bash_exec, assert_complete +from conftest import assert_bash_exec, assert_complete, bash_env_saved @pytest.mark.bashcomp(cmd=None, ignore_env=r"^\+COMPREPLY=") @@ -59,7 +59,9 @@ def utf8_ctype(self, bash): return lc_ctype def test_1(self, bash): - assert_bash_exec(bash, "_comp_compgen_filedir >/dev/null") + with bash_env_saved(bash) as bash_env: + bash_env.write_variable("cur", "") + assert_bash_exec(bash, 'cur="" _comp_compgen_filedir >/dev/null') @pytest.mark.parametrize("funcname", "f f2".split()) def test_2(self, bash, functions, funcname): @@ -233,3 +235,47 @@ def test_26(self, bash, functions, funcname): def test_27(self, bash, functions, funcname, utf8_ctype): completion = assert_complete(bash, "%s aé/" % funcname, cwd="_filedir") assert completion == "g" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_28_dot_1(self, bash, functions, funcname): + """Exclude . and .. when the completion is attempted for '.[TAB]'""" + completion = assert_complete(bash, r"%s ." % funcname, cwd="_filedir") + assert completion == [".dotfile1", ".dotfile2"] + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_28_dot_2(self, bash, functions, funcname): + """Exclude . and .. when the completion is attempted for 'dir/.[TAB]'""" + completion = assert_complete(bash, r"%s _filedir/." % funcname) + assert completion == [".dotfile1", ".dotfile2"] + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_28_dot_3(self, bash, functions, funcname): + """Include . when the completion is attempted for '..[TAB]'""" + completion = assert_complete(bash, r"%s .." % funcname, cwd="_filedir") + assert completion == "/" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_28_dot_4(self, bash, functions, funcname): + """Include . when the completion is attempted for '..[TAB]'""" + completion = assert_complete(bash, r"%s _filedir/.." % funcname) + assert completion == "/" + + @pytest.mark.parametrize("funcname", "f f2".split()) + def test_29_dotdot(self, bash, functions, funcname): + """Complete files starting with "..". + + These types of files are used by the go kubernetes atomic writer [0], + and presumably other types of systems, and we want to make sure they + will be completed correctly. + + [0] https://pkg.go.dev/k8s.io/kubernetes/pkg/volume/util#AtomicWriter.Write + """ + completion = assert_complete( + bash, r"%s .." % funcname, cwd="_filedir/dotdot/" + ) + assert completion == [ + "../", + "..2016_02_01_15_04_05.123456", + "..data", + "..folder/", + ] diff --git a/test/t/unit/test_unit_expand_glob.py b/test/t/unit/test_unit_expand_glob.py index 64d04a7d6fa..7c1bcde3f9c 100644 --- a/test/t/unit/test_unit_expand_glob.py +++ b/test/t/unit/test_unit_expand_glob.py @@ -22,7 +22,10 @@ def functions(self, bash): def test_match_all(self, bash, functions): output = assert_bash_exec(bash, "__tester '*'", want_output=True) - assert output.strip() == "" + assert ( + output.strip() + == "" + ) def test_match_pattern(self, bash, functions): output = assert_bash_exec(bash, "__tester 'a*'", want_output=True)