Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 62 additions & 19 deletions bash_completion
Original file line number Diff line number Diff line change
Expand Up @@ -2764,28 +2764,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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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