Skip to content

Commit

Permalink
fix(_comp_command_offset): Support complete -C
Browse files Browse the repository at this point in the history
  • Loading branch information
elyscape authored and scop committed Dec 26, 2023
1 parent 652e98f commit 80450ca
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 20 deletions.
81 changes: 62 additions & 19 deletions bash_completion
Expand Up @@ -2765,28 +2765,71 @@ _comp_command_offset()
# *something* for every command thrown at it ($cspec != empty)
_comp_complete_minimal "${args[@]}"
fi
elif [[ $cspec == *' -F '* ]]; then
# complete -F <function>

# get function name
local func=${cspec#* -F }
func=${func%% *}
$func "${args[@]}"

# restart completion (once) if function exited with 124
if (($? == 124 && retry_count++ == 0)); then
# Note: When the completion function returns 124, the state
# of COMPREPLY is discarded.
COMPREPLY=()
elif [[ $cspec == *\ -[CF]\ * ]]; then
if [[ $cspec == *' -F '* ]]; then
# complete -F <function>

# get function name
local func=${cspec#* -F }
func=${func%% *}
$func "${args[@]}"

# restart completion (once) if function exited with 124
if (($? == 124 && retry_count++ == 0)); then
# Note: When the completion function returns 124, the
# state of COMPREPLY is discarded.
COMPREPLY=()

cspec=$(complete -p "$compcmd" 2>/dev/null)

# Note: When completion spec is removed after 124, we
# do not generate any completions including the default
# ones. This is the behavior of the original Bash
# progcomp.
[[ $cspec ]] || break

continue
fi
else
# complete -C <command>

cspec=$(complete -p "$compcmd" 2>/dev/null)
# get command name
local completer=${cspec#* -C \'}

# Note: When completion spec is removed after 124, we do
# not generate any completions including the default ones.
# This is the behavior of the original Bash progcomp.
[[ $cspec ]] || break
# completer commands are always single-quoted
if ! _comp_dequote "'$completer"; then
_minimal "${args[@]}"
break
fi
completer=${REPLY[0]}

local -a suggestions

local IFS=$' \t\n'
local reset_monitor=$(shopt -po monitor) reset_lastpipe=$(shopt -p lastpipe) reset_noglob=$(shopt -po noglob)
set +o monitor
shopt -s lastpipe
set -o noglob

COMP_KEY="$COMP_KEY" COMP_LINE="$COMP_LINE" \
COMP_POINT="$COMP_POINT" COMP_TYPE="$COMP_TYPE" \
$completer "${args[@]}" | mapfile -t suggestions

continue
$reset_monitor
$reset_lastpipe
$reset_noglob
_comp_unlocal IFS

local suggestion
local i=0
COMPREPLY=()
for suggestion in "${suggestions[@]}"; do
COMPREPLY[i]+=${COMPREPLY[i]+$'\n'}$suggestion

if [[ $suggestion != *\\ ]]; then
((i++))
fi
done
fi

# restore initial compopts
Expand Down
30 changes: 30 additions & 0 deletions test/fixtures/_command_offset/completer
@@ -0,0 +1,30 @@
#!/bin/sh

case "${2-}" in
b|ba|bar)
echo bar
;;
cont1*)
echo cont10
echo cont11\\
;;
f|fo|foo)
echo foo
;;
l)
echo line\\
echo two
echo long
;;
li*)
echo line\\
echo two
;;
lo*)
echo long
;;
*)
echo bar
echo foo
;;
esac
20 changes: 19 additions & 1 deletion test/t/unit/test_unit_command_offset.py
Expand Up @@ -12,7 +12,8 @@ def join(words):

@pytest.mark.bashcomp(
cmd=None,
ignore_env=r"^[+-]COMPREPLY=",
cwd="_command_offset",
ignore_env=r"^[+-](COMPREPLY|REPLY)=",
)
class TestUnitCommandOffset:
wordlist = sorted(["foo", "bar"])
Expand Down Expand Up @@ -45,6 +46,8 @@ def functions(self, bash):
)
assert_bash_exec(bash, 'complete -W \'"$word1" "$word2"\' cmd6')

assert_bash_exec(bash, "complete -C ./completer cmd7")

def test_1(self, bash, functions):
assert_complete(bash, 'cmd1 "/tmp/aaa bbb" ')
assert_bash_exec(bash, "! complete -p aaa", want_output=None)
Expand Down Expand Up @@ -78,6 +81,21 @@ def test_2(self, bash, functions, cmd, expected_completion):
"""
assert assert_complete(bash, "meta %s " % cmd) == expected_completion

@pytest.mark.parametrize(
"cmd,expected_completion",
[
("cmd7 ", wordlist),
("cmd7 l", ["line\\^Jtwo", "long"]),
("cmd7 lo", ["ng"]),
("cmd7 line", ["\\^Jtwo"]),
("cmd7 cont1", ["cont10", "cont11\\"]),
],
)
def test_3(self, bash, functions, cmd, expected_completion):
got = assert_complete(bash, f"cmd1 {cmd}")
assert got == assert_complete(bash, cmd)
assert got == expected_completion

def test_cmd_quoted(self, bash, functions):
assert assert_complete(bash, "meta 'cmd2' ") == self.wordlist

Expand Down

0 comments on commit 80450ca

Please sign in to comment.