Skip to content

Commit 874c503

Browse files
committed
fix(_comp_count_args): check optarg correctly
When the current implementation checks an option argument, it tests whether the previous word matches $2 (i.e., a pattern of options taking an option argument). This implementation has multiple issues: * When the options taking an option argument does not start with `-`, the option is counted as an independent argument. For example, `ssh` completion passes `@(-c|[-+]o)` as $2, but `+o` is counted as an argument with the current implementation. * An option argument that looks like an option taking an option argument can prevent the next word counted as an argument. For example, when `cmd -o -o arg` is processed with $2 being `-o`, the second `-o` should be treated as an option argument and `arg` should be counted as an argument. However, with the current implementation, `arg` is not counted because the previous word `-o` matches the pattern. * When `cmd -o -- -x` is processed with $2 being `-o`, `--` should be treated as an option argument so should lose its special meaning, and `-x` is still treated as an option. However, with the current implementation, `--` does not lose its special meaning, so `-x` is unexpectedly treated as an argument. * Also, with the current implementation, when $2 is not specified, an argument after an empty word is not counted as an argument because $2 matches the empty word, (though Readline usually do not store an empty word in COMP_WORDS). This patch fixes those issues by changing how options taking an option argument are processed.
1 parent 76eea74 commit 874c503

File tree

2 files changed

+41
-2
lines changed

2 files changed

+41
-2
lines changed

bash_completion

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2193,8 +2193,9 @@ _comp_count_args()
21932193
ret=1
21942194
for ((i = 1; i < cword; i++)); do
21952195
# shellcheck disable=SC2053
2196-
if [[ (${words[i]} != -?* || ${3-} && ${words[i]} == ${3-}) &&
2197-
${words[i - 1]} != ${2-} ]]; then
2196+
if [[ ${2-} && ${words[i]} == ${2-} ]]; then
2197+
((i++))
2198+
elif [[ ${words[i]} != -?* || ${3-} && ${words[i]} == ${3-} ]]; then
21982199
((ret++))
21992200
elif [[ ${words[i]} == -- ]]; then
22002201
((ret += cword - i - 1))

test/t/unit/test_unit_count_args.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,41 @@ def test_12_exclude_optarg_3(self, bash):
111111
bash, "(a -o -x -y c)", 4, "a -o -x -y c", 11, arg='"" "-o" "-x"'
112112
)
113113
assert output == "1"
114+
115+
def test_13_plus_option_optarg(self, bash):
116+
"""When +o is specified to be an option taking an option argument, it should not be counted as an argument"""
117+
output = self._test(
118+
bash, "(a +o b c)", 3, "a +o b c", 7, arg='"" "+o"'
119+
)
120+
assert output == "1"
121+
122+
def test_14_no_optarg_chain_1(self, bash):
123+
"""an option argument should not take another option argument"""
124+
output = self._test(
125+
bash, "(a -o -o -o -o c)", 5, "a -o -o -o -o c", 14, arg='"" "-o"'
126+
)
127+
assert output == "1"
128+
129+
def test_14_no_optarg_chain_2(self, bash):
130+
"""an option argument should not take another option argument"""
131+
output = self._test(
132+
bash,
133+
"(a -o -o b -o -o c)",
134+
6,
135+
"a -o -o b -o -o c",
136+
16,
137+
arg='"" "-o"',
138+
)
139+
assert output == "2"
140+
141+
def test_15_double_hyphen_optarg(self, bash):
142+
"""-- should lose its meaning when it is an option argument"""
143+
output = self._test(
144+
bash, "(a -o -- -b -c d)", 5, "a -o -- -b -c d", 14, arg='"" "-o"'
145+
)
146+
assert output == "1"
147+
148+
def test_16_empty_word(self, bash):
149+
"""An empty word should not take an option argument"""
150+
output = self._test(bash, "(a '' x '' y d)", 5, "a x y d", 8)
151+
assert output == "5"

0 commit comments

Comments
 (0)