Skip to content

Commit b44e661

Browse files
committed
fix(_command_offset): Add support for complete -C
1 parent 37a74da commit b44e661

File tree

3 files changed

+119
-21
lines changed

3 files changed

+119
-21
lines changed

bash_completion

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2274,33 +2274,83 @@ _command_offset()
22742274
# *something* for every command thrown at it ($cspec != empty)
22752275
_minimal
22762276
fi
2277-
elif [[ $cspec == *' -F '* ]]; then
2278-
# complete -F <function>
2277+
elif [[ $cspec == *\ -[CF]\ * ]]; then
2278+
if [[ $cspec == *' -F '* ]]; then
2279+
# complete -F <function>
22792280

2280-
# get function name
2281-
local func=${cspec#* -F }
2282-
func=${func%% *}
2281+
# get function name
2282+
local func=${cspec#* -F }
2283+
func=${func%% *}
22832284

2284-
if ((${#COMP_WORDS[@]} >= 2)); then
2285-
$func "$cmd" "${COMP_WORDS[-1]}" "${COMP_WORDS[-2]}"
2285+
if ((${#COMP_WORDS[@]} >= 2)); then
2286+
$func "$cmd" "${COMP_WORDS[-1]}" "${COMP_WORDS[-2]}"
2287+
else
2288+
$func "$cmd" "${COMP_WORDS[-1]}"
2289+
fi
2290+
2291+
# restart completion (once) if function exited with 124
2292+
if (($? == 124 && retry_count++ == 0)); then
2293+
# Note: When the completion function returns 124, the
2294+
# state of COMPREPLY is discarded.
2295+
COMPREPLY=()
2296+
2297+
cspec=$(complete -p "$compcmd" 2>/dev/null)
2298+
2299+
# Note: When completion spec is removed after 124, we
2300+
# do not generate any completions including the default
2301+
# ones. This is the behavior of the original Bash
2302+
# progcomp.
2303+
[[ $cspec ]] || break
2304+
2305+
continue
2306+
fi
22862307
else
2287-
$func "$cmd" "${COMP_WORDS[-1]}"
2288-
fi
2308+
# complete -C <command>
22892309

2290-
# restart completion (once) if function exited with 124
2291-
if (($? == 124 && retry_count++ == 0)); then
2292-
# Note: When the completion function returns 124, the state
2293-
# of COMPREPLY is discarded.
2294-
COMPREPLY=()
2310+
# get command name
2311+
local completer=${cspec#* -C \'}
22952312

2296-
cspec=$(complete -p "$compcmd" 2>/dev/null)
2313+
# completer commands are always single-quoted
2314+
if ! _comp_dequote "'$completer"; then
2315+
_minimal
2316+
break
2317+
fi
2318+
completer=${ret[0]}
2319+
2320+
local -a suggestions
2321+
2322+
local reset_monitor=$(shopt -po monitor) reset_lastpipe=$(shopt -p lastpipe)
2323+
set +o monitor
2324+
shopt -s lastpipe
2325+
2326+
(
2327+
export COMP_KEY COMP_LINE COMP_POINT COMP_TYPE
22972328

2298-
# Note: When completion spec is removed after 124, we do
2299-
# not generate any completions including the default ones.
2300-
# This is the behavior of the original Bash progcomp.
2301-
[[ $cspec ]] || break
2329+
if ((${#COMP_WORDS[@]} >= 2)); then
2330+
$completer "$cmd" "${COMP_WORDS[-1]}" \
2331+
"${COMP_WORDS[-2]}"
2332+
else
2333+
$completer "$cmd" "${COMP_WORDS[-1]}"
2334+
fi
2335+
) | mapfile suggestions
23022336

2303-
continue
2337+
$reset_monitor
2338+
$reset_lastpipe
2339+
2340+
local suggestion
2341+
local i=0
2342+
COMPREPLY=()
2343+
for suggestion in "${suggestions[@]}"; do
2344+
# `mapfile -t` was added in bash 4.4, so we have to
2345+
# strip trailing newlines manually
2346+
suggestion="${suggestion%$'\n'}"
2347+
2348+
COMPREPLY[i]+=${COMPREPLY[i]+$'\n'}$suggestion
2349+
2350+
if [[ $suggestion != *\\ ]]; then
2351+
((i++))
2352+
fi
2353+
done
23042354
fi
23052355

23062356
# restore initial compopts
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/bin/sh
2+
3+
case "${2-}" in
4+
b|ba|bar)
5+
echo bar
6+
;;
7+
cont1*)
8+
echo cont10
9+
echo cont11\\
10+
;;
11+
f|fo|foo)
12+
echo foo
13+
;;
14+
l)
15+
echo line\\
16+
echo two
17+
echo long
18+
;;
19+
li*)
20+
echo line\\
21+
echo two
22+
;;
23+
lo*)
24+
echo long
25+
;;
26+
*)
27+
echo bar
28+
echo foo
29+
;;
30+
esac

test/t/unit/test_unit_command_offset.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ def join(words):
1212

1313
@pytest.mark.bashcomp(
1414
cmd=None,
15-
ignore_env=r"^[+-](COMP(_(WORDS|CWORD|LINE|POINT)|REPLY)|cur|cword|words)=",
15+
cwd="_command_offset",
16+
ignore_env=r"^[+-](COMP(_(WORDS|CWORD|LINE|POINT)|REPLY)|cur|cword|words|ret)=",
1617
)
1718
class TestUnitCommandOffset:
1819
wordlist = sorted(["foo", "bar"])
@@ -40,6 +41,8 @@ def functions(self, bash):
4041
% {"idx": idx, "comp": comp},
4142
)
4243

44+
assert_bash_exec(bash, "complete -C ./completer cmd6")
45+
4346
def test_1(self, bash, functions):
4447
assert_complete(bash, 'cmd1 "/tmp/aaa bbb" ')
4548
assert_bash_exec(bash, "! complete -p aaa", want_output=None)
@@ -72,3 +75,18 @@ def test_2(self, bash, functions, cmd, expected_completion):
7275
cleared before the retry.
7376
"""
7477
assert assert_complete(bash, "meta %s " % cmd) == expected_completion
78+
79+
@pytest.mark.parametrize(
80+
"cmd,expected_completion",
81+
[
82+
("cmd6 ", wordlist),
83+
("cmd6 l", ["line\\^Jtwo", "long"]),
84+
("cmd6 lo", ["ng"]),
85+
("cmd6 line", ["\\^Jtwo"]),
86+
("cmd6 cont1", ["cont10", "cont11\\"]),
87+
],
88+
)
89+
def test_3(self, bash, functions, cmd, expected_completion):
90+
got = assert_complete(bash, f"cmd1 {cmd}")
91+
assert got == assert_complete(bash, cmd)
92+
assert got == expected_completion

0 commit comments

Comments
 (0)