From f16dd8049850a30cb298c2c450bf02f1e4451d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 10 Apr 2023 22:09:54 +0300 Subject: [PATCH] feat(__load_completion): check that a completion was actually set On successful return, at least the full path to the command in question should have a completion set. If the command invoked was given without a path, the pathless one should have one set, too. --- bash_completion | 28 ++++++++++++++++--- .../share/bash-completion/completions/cmd1 | 1 + .../share/bash-completion/completions/cmd2 | 1 + .../userdir1/completions/cmd1 | 1 + .../userdir2/completions/cmd2 | 1 + test/t/unit/test_unit_load_completion.py | 14 ++++++++++ 6 files changed, 42 insertions(+), 4 deletions(-) diff --git a/bash_completion b/bash_completion index 74c5f060301..134bd3d1f8f 100644 --- a/bash_completion +++ b/bash_completion @@ -2509,7 +2509,7 @@ __load_completion() fi # Try to resolve real path to $cmd, and make it absolute - local ret realcmd= + local ret realcmd="" origcmd=$cmd if _comp_realcommand "$cmd"; then realcmd=$ret cmd=${realcmd%/*}/$cmdname # basename could be an alias @@ -2551,7 +2551,8 @@ __load_completion() _comp_split -F : paths "${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" dirs+=("${paths[@]/%//bash-completion/completions}") - # For loading 3rd party completions wrapped in shopt reset + # Set up default $IFS in case loaded completions depend on it, + # as well as for $compspec invocation below. local IFS=$' \t\n' # Look up and source @@ -2573,8 +2574,27 @@ __load_completion() [[ $compfile == */.?(.) ]] || echo "bash_completion: $compfile: is a directory" >&2 elif [[ -e $compfile ]] && . "$compfile" "$cmd" "$@"; then - [[ $backslash ]] && $(complete -p "$cmdname") "\\$cmdname" - return 0 + # At least $cmd is expected to have a completion set when + # we return successfully; see if it already does + if compspec=$(complete -p "$cmd" 2>/dev/null); then + local -a extspecs=() + # $cmd is the case in which we do backslash processing + [[ $backslash ]] && extspecs+=("$backslash$cmd") + # If invoked without path, that one should be set, too + # ...but let's not overwrite an existing one, if any + [[ $origcmd != */* ]] && + ! complete -p "$origcmd" &>/dev/null && + extspecs+=("$origcmd") + ((${#extspecs[*]} != 0)) && $compspec "${extspecs[@]}" + return 0 + fi + # If not, see if we got one for $cmdname + if compspec=$(complete -p "$cmdname" 2>/dev/null); then + # Use that for $cmd too, if we have a full path to it + [[ $cmd == /* ]] && $compspec "$cmd" + return 0 + fi + # Nothing expected was set, continue lookup fi done done diff --git a/test/fixtures/__load_completion/prefix1/share/bash-completion/completions/cmd1 b/test/fixtures/__load_completion/prefix1/share/bash-completion/completions/cmd1 index e2f434e7b47..378a6e3fd3f 100644 --- a/test/fixtures/__load_completion/prefix1/share/bash-completion/completions/cmd1 +++ b/test/fixtures/__load_completion/prefix1/share/bash-completion/completions/cmd1 @@ -1 +1,2 @@ echo 'cmd1: sourced from prefix1' +complete -C true "$1" diff --git a/test/fixtures/__load_completion/prefix1/share/bash-completion/completions/cmd2 b/test/fixtures/__load_completion/prefix1/share/bash-completion/completions/cmd2 index 8549e626e3a..167ad624ecd 100644 --- a/test/fixtures/__load_completion/prefix1/share/bash-completion/completions/cmd2 +++ b/test/fixtures/__load_completion/prefix1/share/bash-completion/completions/cmd2 @@ -1 +1,2 @@ echo 'cmd2: sourced from prefix1' +complete -C true "$1" diff --git a/test/fixtures/__load_completion/userdir1/completions/cmd1 b/test/fixtures/__load_completion/userdir1/completions/cmd1 index 5e88e908e19..b26bf1fe393 100644 --- a/test/fixtures/__load_completion/userdir1/completions/cmd1 +++ b/test/fixtures/__load_completion/userdir1/completions/cmd1 @@ -1 +1,2 @@ echo 'cmd1: sourced from userdir1' +complete -C true "$1" diff --git a/test/fixtures/__load_completion/userdir2/completions/cmd2 b/test/fixtures/__load_completion/userdir2/completions/cmd2 index 135d084a23f..667989bb687 100644 --- a/test/fixtures/__load_completion/userdir2/completions/cmd2 +++ b/test/fixtures/__load_completion/userdir2/completions/cmd2 @@ -1 +1,2 @@ echo 'cmd2: sourced from userdir2' +complete -C true "$1" diff --git a/test/t/unit/test_unit_load_completion.py b/test/t/unit/test_unit_load_completion.py index be22a1ea78c..1a6a798dc2a 100644 --- a/test/t/unit/test_unit_load_completion.py +++ b/test/t/unit/test_unit_load_completion.py @@ -37,12 +37,26 @@ def test_PATH_1(self, bash): bash, "__load_completion cmd2", want_output=True ) assert output.strip() == "cmd2: sourced from prefix1" + output = assert_bash_exec( + bash, "complete -p cmd2", want_output=True + ) + assert " cmd2" in output + output = assert_bash_exec( + bash, 'complete -p "$PWD/prefix1/sbin/cmd2"', want_output=True + ) + assert "/prefix1/sbin/cmd2" in output def test_cmd_path_1(self, bash): + assert_bash_exec(bash, "complete -r cmd1 || :", want_output=None) output = assert_bash_exec( bash, "__load_completion prefix1/bin/cmd1", want_output=True ) assert output.strip() == "cmd1: sourced from prefix1" + output = assert_bash_exec( + bash, 'complete -p "$PWD/prefix1/bin/cmd1"', want_output=True + ) + assert "/prefix1/bin/cmd1" in output + assert_bash_exec(bash, "! complete -p cmd1", want_output=None) output = assert_bash_exec( bash, "__load_completion prefix1/sbin/cmd2", want_output=True )