Skip to content

Commit 5c19025

Browse files
authored
Merge pull request #860 from akinomyoga/_command_offset-offsetMismatch
fix(_comp_command_offset): fix offset mismatching for words vs COMP_WORDS (use words)
2 parents 3e18b30 + 06bfe23 commit 5c19025

File tree

8 files changed

+96
-18
lines changed

8 files changed

+96
-18
lines changed

bash_completion

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2206,6 +2206,30 @@ _known_hosts_real()
22062206
complete -F _known_hosts traceroute traceroute6 \
22072207
fping fping6 telnet rsh rlogin ftp dig drill mtr ssh-installkeys showmount
22082208

2209+
# Convert the word index in `words` to the index in `COMP_WORDS`.
2210+
# @param $1 Index in the array WORDS.
2211+
# @var[in,opt] words Words that contain reassmbled words.
2212+
# @var[in,opt] cword Current word index in WORDS.
2213+
# WORDS and CWORD, if any, are expected to be created by
2214+
# _comp__reassemble_words.
2215+
#
2216+
_comp__find_original_word()
2217+
{
2218+
ret=$1
2219+
2220+
# If CWORD or WORDS are undefined, we return the first argument without any
2221+
# processing.
2222+
[[ -v cword && -v words ]] || return 0
2223+
2224+
local reassembled_offset=$1 i=0 j
2225+
for ((j = 0; j < reassembled_offset; j++)); do
2226+
local word=${words[j]}
2227+
while [[ $word && i -lt ${#COMP_WORDS[@]} && $word == *"${COMP_WORDS[i]}"* ]]; do
2228+
word=${word#*"${COMP_WORDS[i++]}"}
2229+
done
2230+
done
2231+
ret=$i
2232+
}
22092233
# A meta-command completion function for commands like sudo(8), which need to
22102234
# first complete on a command, then complete according to that command's own
22112235
# completion definition.
@@ -2215,6 +2239,11 @@ _comp_command_offset()
22152239
# rewrite current completion context before invoking
22162240
# actual command completion
22172241

2242+
# obtain the word index in COMP_WORDS
2243+
local ret
2244+
_comp__find_original_word "$1"
2245+
local word_offset=$ret
2246+
22182247
# make changes to COMP_* local. Note that bash-4.3..5.0 have a
22192248
# bug that `local -a arr=("${arr[@]}")` fails. We instead first
22202249
# assign the values of `COMP_WORDS` to another array `comp_words`.
@@ -2224,7 +2253,7 @@ _comp_command_offset()
22242253

22252254
# find new first word position, then
22262255
# rewrite COMP_LINE and adjust COMP_POINT
2227-
local word_offset=$1 i tail
2256+
local i tail
22282257
for ((i = 0; i < word_offset; i++)); do
22292258
tail=${COMP_LINE#*"${COMP_WORDS[i]}"}
22302259
((COMP_POINT -= ${#COMP_LINE} - ${#tail}))
@@ -2244,7 +2273,6 @@ _comp_command_offset()
22442273
compopt -o filenames
22452274
_comp_compgen COMPREPLY -d -c -- "$cur"
22462275
else
2247-
local ret
22482276
_comp_dequote "${COMP_WORDS[0]}" || ret=${COMP_WORDS[0]}
22492277
local cmd=$ret compcmd=$ret
22502278
local cspec=$(complete -p "$cmd" 2>/dev/null)
@@ -2325,6 +2353,12 @@ _comp_command_offset()
23252353
#
23262354
_comp_command()
23272355
{
2356+
# We unset the shell variable `words` locally to tell
2357+
# `_comp_command_offset` that the index is intended to be that in
2358+
# `COMP_WORDS` instead of `words`.
2359+
local words
2360+
unset -v words
2361+
23282362
local offset i
23292363

23302364
# find actual offset, as position of the first non-option

bash_completion.d/000_bash_completion_compat.bash

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,17 @@ _cd()
245245
_comp_cmd_cd "$@"
246246
}
247247

248+
# @deprecated Use `_comp_command_offset` instead. Note that the new interface
249+
# `_comp_command_offset` is changed to receive an index in `words` instead of
250+
# that in `COMP_WORDS` as `_command_offset` did.
251+
_command_offset()
252+
{
253+
# We unset the shell variable `words` locally to tell
254+
# `_comp_command_offset` that the index is intended to be that in
255+
# `COMP_WORDS` instead of `words`.
256+
local words
257+
unset -v words
258+
_comp_command_offset "$@"
259+
}
260+
248261
# ex: filetype=sh

completions/ccache

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ _comp_cmd_ccache()
66
_comp_initialize -s -- "$@" || return
77

88
local i
9-
for ((i = 1; i <= COMP_CWORD; i++)); do
10-
if [[ ${COMP_WORDS[i]} != -* ]]; then
9+
for ((i = 1; i <= cword; i++)); do
10+
if [[ ${words[i]} != -* ]]; then
1111
_comp_command_offset $i
1212
return
1313
fi
14-
[[ ${COMP_WORDS[i]} == -*[oFM] ]] && ((i++))
14+
[[ ${words[i]} == -*[oFM] ]] && ((i++))
1515
done
1616

1717
local noargopts='!(-*|*[FMo]*)'

completions/find

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ _comp_cmd_find()
88
_comp_initialize -- "$@" || return
99

1010
local i
11-
for i in ${!words[*]}; do
11+
for ((i = 1; i < cword; i++)); do
1212
if [[ ${words[i]} == -@(exec|ok)?(dir) ]]; then
13-
((cword > i)) || break
1413
_comp_command_offset $((i + 1))
1514
return
1615
fi

completions/timeout

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ _comp_cmd_timeout()
66
_comp_initialize -s -- "$@" || return
77

88
local noargopts='!(-*|*[ks]*)'
9-
for ((i = 1; i <= COMP_CWORD; i++)); do
10-
if [[ ${COMP_WORDS[i]} != -* && ${COMP_WORDS[i - 1]} != = ]]; then
9+
for ((i = 1; i <= cword; i++)); do
10+
if [[ ${words[i]} != -* ]]; then
1111
if [[ $found ]]; then
1212
_comp_command_offset $i
1313
return
1414
fi
1515
found=set
1616
fi
1717
# shellcheck disable=SC2254
18-
[[ ${COMP_WORDS[i]} == -@(-kill-after|-signal|${noargopts}[ks]) ]] && ((i++))
18+
[[ ${words[i]} == -@(-kill-after|-signal|${noargopts}[ks]) ]] && ((i++))
1919
done
2020

2121
# shellcheck disable=SC2254

completions/valgrind

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,8 @@ _comp_cmd_valgrind()
66
_comp_initialize -s -- "$@" || return
77

88
local i
9-
# Note: intentionally using COMP_WORDS and COMP_CWORD instead of
10-
# words and cword here due to splitting on = causing index differences
11-
# (_comp_command_offset assumes the former).
12-
for ((i = 1; i <= COMP_CWORD; i++)); do
13-
if [[ ${COMP_WORDS[i]} != @([-=])* && ${COMP_WORDS[i - 1]} != = ]]; then
9+
for ((i = 1; i <= cword; i++)); do
10+
if [[ ${words[i]} != @([-=])* ]]; then
1411
_comp_command_offset $i
1512
return
1613
fi

completions/xvfb-run

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ _comp_cmd_xvfb_run()
77

88
local noargopts='!(-*|*[npsef]*)'
99
local i
10-
for ((i = 1; i <= COMP_CWORD; i++)); do
11-
if [[ ${COMP_WORDS[i]} != -* ]]; then
10+
for ((i = 1; i <= cword; i++)); do
11+
if [[ ${words[i]} != -* ]]; then
1212
_comp_command_offset $i
1313
return
1414
fi
1515
# shellcheck disable=SC2254
16-
[[ ${COMP_WORDS[i]} == -${noargopts}[npsef] ]] && ((i++))
16+
[[ ${words[i]} == -${noargopts}[npsef] ]] && ((i++))
1717
done
1818

1919
# shellcheck disable=SC2254

test/t/unit/test_unit_command_offset.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,38 @@ def test_space(self, bash, functions):
8989
bash_env.write_variable("word1", "a b c")
9090
bash_env.write_variable("word2", "d e f")
9191
assert assert_complete(bash, "meta cmd6 ") == ["a b c", "d e f"]
92+
93+
@pytest.fixture(scope="class")
94+
def find_original_word_functions(self, bash):
95+
assert_bash_exec(
96+
bash,
97+
"_comp_test_reassemble() {"
98+
" local IFS=$' \\t\\n' ret;"
99+
' COMP_LINE=$1; _comp_split COMP_WORDS "$2"; COMP_CWORD=$((${#COMP_WORDS[@]}-1));'
100+
" _comp__reassemble_words = words cword;"
101+
"}",
102+
)
103+
assert_bash_exec(
104+
bash,
105+
"_comp_test_1() {"
106+
' local COMP_WORDS COMP_LINE COMP_CWORD words cword ret; _comp_test_reassemble "$1" "$2";'
107+
' _comp__find_original_word "$3";'
108+
' echo "$ret";'
109+
"}",
110+
)
111+
112+
def test_find_original_word_1(self, bash, find_original_word_functions):
113+
result = assert_bash_exec(
114+
bash,
115+
'_comp_test_1 "sudo su do su do abc" "sudo su do su do abc" 3',
116+
want_output=True,
117+
).strip()
118+
assert result == "3"
119+
120+
def test_find_original_word_2(self, bash, find_original_word_functions):
121+
result = assert_bash_exec(
122+
bash,
123+
'_comp_test_1 "sudo --prefix=su su do abc" "sudo --prefix = su su do abc" 2',
124+
want_output=True,
125+
).strip()
126+
assert result == "4"

0 commit comments

Comments
 (0)