Split _get_cword into bash-3/4 versions
- Added code comments to _get_cword, __get_cword3 & __get_cword4 - (testsuite) Added tests for _get_cword - (testsuite) Bugfixes assert_bash_exec() && match_items() Bash-4 splits COMP_WORDS using characters from COMP_WORDBREAKS, but has a bug where quoted words are also splitted, see: http://www.mail-archive.com/bug-bash@gnu.org/msg06095.html __get_cword3 is used for bash-2/3 and __get_cword4 is used for bash-4. __get_cword4 handles well temporarily disabling of COMP_WORDBREAK characters, but fails quoted words (a 'b c) and subshells (a $(b c). See the expected failures when running the automated tests. __get_cword3 does a better job of returning quoted words. To run the automated tests on bash-3/4: $ ./runUnit _get_cword.exp [--tool_exec <path to bash-3/4 binary>]
This commit is contained in:
parent
fcf2bf91c9
commit
f733e71e1f
124
bash_completion
124
bash_completion
@ -233,43 +233,143 @@ dequote()
|
||||
eval echo "$1"
|
||||
}
|
||||
|
||||
# Get the word to complete
|
||||
# Get the word to complete.
|
||||
# This is nicer than ${COMP_WORDS[$COMP_CWORD]}, since it handles cases
|
||||
# where the user is completing in the middle of a word.
|
||||
# (For example, if the line is "ls foobar",
|
||||
# and the cursor is here --------> ^
|
||||
# it will complete just "foo", not "foobar", which is what the user wants.)
|
||||
#
|
||||
#
|
||||
# Accepts an optional parameter indicating which characters out of
|
||||
# $COMP_WORDBREAKS should NOT be considered word breaks. This is useful
|
||||
# for things like scp where we want to return host:path and not only path.
|
||||
# @param $1 string (optional) Characters out of $COMP_WORDBREAKS which should
|
||||
# NOT be considered word breaks. This is useful for things like scp where
|
||||
# we want to return host:path and not only path.
|
||||
# NOTE: This parameter only applies to bash-4.
|
||||
|
||||
_get_cword()
|
||||
{
|
||||
if [ -n "$bash4" ] ; then
|
||||
__get_cword4 "$@"
|
||||
else
|
||||
__get_cword3
|
||||
fi
|
||||
} # _get_cword()
|
||||
|
||||
|
||||
# Get the word to complete on bash-3, where words are not broken by
|
||||
# COMP_WORDBREAKS characters and the COMP_CWORD variables look like this, for
|
||||
# example:
|
||||
#
|
||||
# $ a b:c<TAB>
|
||||
# COMP_CWORD: 1
|
||||
# COMP_CWORDS:
|
||||
# 0: a
|
||||
# 1: b:c
|
||||
#
|
||||
# See also:
|
||||
# _get_cword, main routine
|
||||
# __get_cword4, bash-4 variant
|
||||
#
|
||||
__get_cword3()
|
||||
{
|
||||
if [[ "${#COMP_WORDS[COMP_CWORD]}" -eq 0 ]] || [[ "$COMP_POINT" == "${#COMP_LINE}" ]]; then
|
||||
printf "%s" "${COMP_WORDS[COMP_CWORD]}"
|
||||
else
|
||||
local i
|
||||
local cur="$COMP_LINE"
|
||||
local index="$COMP_POINT"
|
||||
for (( i = 0; i <= COMP_CWORD; ++i )); do
|
||||
while [[
|
||||
# Current COMP_WORD fits in $cur?
|
||||
"${#cur}" -ge ${#COMP_WORDS[i]} &&
|
||||
# $cur doesn't match COMP_WORD?
|
||||
"${cur:0:${#COMP_WORDS[i]}}" != "${COMP_WORDS[i]}"
|
||||
]]; do
|
||||
# Strip first character
|
||||
cur="${cur:1}"
|
||||
# Decrease cursor position
|
||||
index="$(( index - 1 ))"
|
||||
done
|
||||
|
||||
# Does found COMP_WORD matches COMP_CWORD?
|
||||
if [[ "$i" -lt "$COMP_CWORD" ]]; then
|
||||
# No, COMP_CWORD lies further;
|
||||
local old_size="${#cur}"
|
||||
cur="${cur#${COMP_WORDS[i]}}"
|
||||
local new_size="${#cur}"
|
||||
index="$(( index - old_size + new_size ))"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "${COMP_WORDS[COMP_CWORD]:0:${#cur}}" != "$cur" ]]; then
|
||||
# We messed up! At least return the whole word so things
|
||||
# keep working
|
||||
printf "%s" "${COMP_WORDS[COMP_CWORD]}"
|
||||
else
|
||||
printf "%s" "${cur:0:$index}"
|
||||
fi
|
||||
fi
|
||||
} # __get_cword3()
|
||||
|
||||
|
||||
# Get the word to complete on bash-4, where words are splitted by
|
||||
# COMP_WORDBREAKS characters (default is " \t\n\"'><=;|&(:") and the COMP_CWORD
|
||||
# variables look like this, for example:
|
||||
#
|
||||
# $ a b:c<TAB>
|
||||
# COMP_CWORD: 3
|
||||
# COMP_CWORDS:
|
||||
# 0: a
|
||||
# 1: b
|
||||
# 2: :
|
||||
# 3: c
|
||||
#
|
||||
# @oaram $1 string
|
||||
# $1 string (optional) Characters out of $COMP_WORDBREAKS which should
|
||||
# NOT be considered word breaks. This is useful for things like scp where
|
||||
# we want to return host:path and not only path.
|
||||
# See also:
|
||||
# _get_cword, main routine
|
||||
# __get_cword3, bash-3 variant
|
||||
#
|
||||
__get_cword4()
|
||||
{
|
||||
local i
|
||||
local LC_CTYPE=C
|
||||
local WORDBREAKS=${COMP_WORDBREAKS}
|
||||
if [ -n $1 ]; then
|
||||
local WORDBREAKS=$COMP_WORDBREAKS
|
||||
# Strip single quote (') and double quote (") from WORDBREAKS to
|
||||
# workaround a bug in bash-4.0, where quoted words are split
|
||||
# unintended, see:
|
||||
# http://www.mail-archive.com/bug-bash@gnu.org/msg06095.html
|
||||
# This fixes simple quoting (e.g. $ a "b<TAB> returns "b instead of b)
|
||||
# but still fails quoted spaces (e.g. $ a "b c<TAB> returns c instead
|
||||
# of "b c).
|
||||
WORDBREAKS=${WORDBREAKS//\"/}
|
||||
WORDBREAKS=${WORDBREAKS//\'/}
|
||||
if [ -n "$1" ]; then
|
||||
for (( i=0; i<${#1}; ++i )); do
|
||||
local char=${1:$i:1}
|
||||
WORDBREAKS=${WORDBREAKS//$char/}
|
||||
done
|
||||
fi
|
||||
local cur=${COMP_LINE:0:$COMP_POINT}
|
||||
local tmp="${cur}"
|
||||
local word_start=`expr "$tmp" : '.*['"${WORDBREAKS}"']'`
|
||||
local tmp=$cur
|
||||
local word_start=`expr "$tmp" : '.*['"$WORDBREAKS"']'`
|
||||
while [ "$word_start" -ge 2 ]; do
|
||||
# Get character before $word_start
|
||||
local char=${cur:$(( $word_start - 2 )):1}
|
||||
# If the WORDBREAK character isn't escaped, exit loop
|
||||
if [ "$char" != "\\" ]; then
|
||||
break
|
||||
fi
|
||||
# The WORDBREAK character is escaped;
|
||||
# Recalculate $word_start
|
||||
tmp=${COMP_LINE:0:$(( $word_start - 2 ))}
|
||||
word_start=`expr "$tmp" : '.*['"${WORDBREAKS}"']'`
|
||||
word_start=`expr "$tmp" : '.*['"$WORDBREAKS"']'`
|
||||
done
|
||||
|
||||
cur=${cur:$word_start}
|
||||
printf "%s" "$cur"
|
||||
}
|
||||
} # _get_cword4()
|
||||
|
||||
|
||||
# This function performs file and directory completion. It's better than
|
||||
# simply using 'compgen -f', because it honours spaces in filenames.
|
||||
|
@ -30,9 +30,6 @@ proc assert_bash_exec {{aCmd ""} {title ""} {prompt /@} {stdout ''}} {
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
set cmd "echo $?"
|
||||
send "$cmd\r"
|
||||
expect {
|
||||
@ -41,7 +38,7 @@ proc assert_bash_exec {{aCmd ""} {title ""} {prompt /@} {stdout ''}} {
|
||||
if {[info exists multipass_name]} {
|
||||
fail "ERROR executing bash command \"$title\""
|
||||
}; # if
|
||||
send_user "ERROR executing bash command \"$title\"\n$out"
|
||||
send_user "ERROR executing bash command \"$title\"\n$stdout"
|
||||
}
|
||||
}; # expect
|
||||
}; # assert_bash_exec()
|
||||
@ -368,8 +365,8 @@ proc match_items {items test {size 20}} {
|
||||
set expected ""
|
||||
for {set j 0} {$j < $size && $i + $j < [llength $items]} {incr j} {
|
||||
set item "[lindex $items [expr {$i + $j}]]"
|
||||
# Escape special regexp characters
|
||||
regsub -all {([\[\]\(\)\.\\\+\*])} $item {\\\1} item
|
||||
# Escape special regexp characters ([]().\*^$)
|
||||
regsub -all {([\[\]\(\)\.\\\+\*\^\$])} $item {\\\1} item
|
||||
append expected $item
|
||||
if {[llength $items] > 1} {append expected {\s+}};
|
||||
}; # for
|
||||
|
@ -19,6 +19,11 @@ proc teardown {} {
|
||||
setup
|
||||
|
||||
|
||||
# Retrieve bash major version number
|
||||
set bash_versinfo_0 {}
|
||||
assert_bash_exec {printf "%s" "${BASH_VERSINFO[0]}"} "" /@ bash_versinfo_0
|
||||
|
||||
|
||||
set test "_get_cword should run without errors"
|
||||
assert_bash_exec {_get_cword > /dev/null} $test
|
||||
|
||||
@ -35,66 +40,113 @@ set cmd {COMP_WORDS=(a b); COMP_CWORD=1; COMP_LINE='a b'; COMP_POINT=3; _get_cwo
|
||||
assert_bash_list b $cmd $test
|
||||
|
||||
|
||||
sync_after_int
|
||||
|
||||
|
||||
set test "a | should return nothing"; # | = cursor position
|
||||
set cmd {COMP_WORDS=(a); COMP_CWORD=1; COMP_LINE='a '; COMP_POINT=2; _get_cword}
|
||||
send "$cmd\r"
|
||||
expect -ex "$cmd\r\n/@" {pass "$test"}
|
||||
|
||||
|
||||
sync_after_int
|
||||
|
||||
|
||||
set test "a b|c should return b"; # | = cursor position
|
||||
set cmd {COMP_WORDS=(a bc); COMP_CWORD=1; COMP_LINE='a bc'; COMP_POINT=3; _get_cword}
|
||||
assert_bash_list b $cmd $test
|
||||
|
||||
|
||||
sync_after_int
|
||||
|
||||
|
||||
set test {a b\ c| should return b\ c}; # | = cursor position
|
||||
set cmd {COMP_WORDS=(a 'b\ c'); COMP_CWORD=1; COMP_LINE='a b\ c'; COMP_POINT=6; _get_cword}
|
||||
assert_bash_list {{"b\\ c"}} $cmd $test
|
||||
|
||||
|
||||
sync_after_int
|
||||
|
||||
|
||||
set test {a b\| c should return b\ }; # | = cursor position
|
||||
set cmd {COMP_WORDS=(a 'b\ c'); COMP_CWORD=1; COMP_LINE='a b\ c'; COMP_POINT=4; _get_cword}
|
||||
assert_bash_list {{"b\\"}} $cmd $test
|
||||
|
||||
|
||||
set test {a "b\| should return b\ }; # | = cursor position
|
||||
sync_after_int
|
||||
|
||||
|
||||
set test {a "b\| should return "b\ }; # | = cursor position
|
||||
set cmd {COMP_WORDS=(a '"b\'); COMP_CWORD=1; COMP_LINE='a "b\'; COMP_POINT=5; _get_cword}
|
||||
assert_bash_list {{"b\\"}} $cmd $test
|
||||
assert_bash_list {{"\"b\\"}} $cmd $test
|
||||
|
||||
|
||||
sync_after_int
|
||||
|
||||
|
||||
# See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=474094 for useful ideas
|
||||
# to make this test pass.
|
||||
set test {a 'b c| should return b c}; # | = cursor position
|
||||
set cmd {COMP_WORDS=(a \' b c); COMP_CWORD=1; COMP_LINE=a\ \'b\ c; COMP_POINT=6; _get_cword}
|
||||
set test {a 'b c| should return 'b c}; # | = cursor position
|
||||
if {$bash_versinfo_0 <= 3} {
|
||||
set cmd {COMP_WORDS=(a "'b c"); COMP_CWORD=1}
|
||||
} else {
|
||||
set cmd {COMP_WORDS=(a "'" b c); COMP_CWORD=3}
|
||||
}; # if
|
||||
append cmd {; COMP_LINE="a 'b c"; COMP_POINT=6; _get_cword}
|
||||
send "$cmd\r"
|
||||
expect -ex "$cmd\r\n"
|
||||
expect {
|
||||
-ex "b c/@" { pass "$test" }
|
||||
-ex "'b c/@" { pass "$test" }
|
||||
-ex "c/@" { xfail "$test" }
|
||||
}; # expect
|
||||
|
||||
|
||||
sync_after_int
|
||||
|
||||
|
||||
# See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=474094 for useful ideas
|
||||
# to make this test pass.
|
||||
set test {a "b c| should return b c}; # | = cursor position
|
||||
set cmd {COMP_WORDS=(a \" b c); COMP_CWORD=3; COMP_LINE=a\ \"b\ c; COMP_POINT=6; _get_cword}
|
||||
set test {a "b c| should return "b c}; # | = cursor position
|
||||
set cmd {COMP_WORDS=(a "\"b c"); COMP_CWORD=1; COMP_LINE="a \"b c"; COMP_POINT=6; _get_cword};
|
||||
send "$cmd\r"
|
||||
expect -ex "$cmd\r\n"
|
||||
expect {
|
||||
-ex "b c/@" { pass "$test" }
|
||||
-ex "\"b c/@" { pass "$test" }
|
||||
-ex "c/@" { xfail "$test" }
|
||||
}; # expect
|
||||
|
||||
|
||||
set test {a b:c| should return c}; # | = cursor position
|
||||
set cmd {COMP_WORDS=(a b : c); COMP_CWORD=3; COMP_LINE='a b:c'; COMP_POINT=5; _get_cword}
|
||||
assert_bash_list c $cmd $test
|
||||
sync_after_int
|
||||
|
||||
|
||||
set test {a b:c| should return b:c (bash-3) or c (bash-4)}; # | = cursor position
|
||||
if {$bash_versinfo_0 <= 3} {
|
||||
set cmd {COMP_WORDS=(a "b:c"); COMP_CWORD=1}
|
||||
set expected b:c
|
||||
} else {
|
||||
set cmd {COMP_WORDS=(a b : c); COMP_CWORD=3}
|
||||
set expected c
|
||||
}; # if
|
||||
append cmd {; COMP_LINE='a b:c'; COMP_POINT=5; _get_cword}
|
||||
assert_bash_list $expected $cmd $test
|
||||
|
||||
|
||||
sync_after_int
|
||||
|
||||
|
||||
set test {a b:c| with WORDBREAKS -= : should return b:c}; # | = cursor position
|
||||
set cmd {COMP_WORDS=(a b : c); COMP_CWORD=3; COMP_LINE='a b:c'; COMP_POINT=5; _get_cword :}
|
||||
if {$bash_versinfo_0 <= 3} {
|
||||
set cmd {COMP_WORDS=(a "b:c"); COMP_CWORD=1}
|
||||
} else {
|
||||
set cmd {COMP_WORDS=(a b : c); COMP_CWORD=3}
|
||||
}; # if
|
||||
append cmd {; COMP_LINE='a b:c'; COMP_POINT=5; _get_cword :}
|
||||
assert_bash_list b:c $cmd $test
|
||||
|
||||
|
||||
sync_after_int
|
||||
|
||||
|
||||
# This test makes sure `_get_cword' doesn't use `echo' to return it's value,
|
||||
# because -n might be interpreted by `echo' and thus will not be returned.
|
||||
set test "a -n| should return -n"; # | = cursor position
|
||||
@ -102,14 +154,30 @@ set cmd {COMP_WORDS=(a -n); COMP_CWORD=1; COMP_LINE='a -n'; COMP_POINT=4; _get_c
|
||||
assert_bash_list -n $cmd $test
|
||||
|
||||
|
||||
sync_after_int
|
||||
|
||||
|
||||
set test {a b>c| should return c}; # | = cursor position
|
||||
set cmd {COMP_WORDS=(a b \> c); COMP_CWORD=3; COMP_LINE='a b>c'; COMP_POINT=5; _get_cword}
|
||||
assert_bash_list c $cmd $test
|
||||
|
||||
|
||||
set test {a b=c| should return c}; # | = cursor position
|
||||
set cmd {COMP_WORDS=(a b = c); COMP_CWORD=3; COMP_LINE='a b=c'; COMP_POINT=5; _get_cword}
|
||||
assert_bash_list c $cmd $test
|
||||
sync_after_int
|
||||
|
||||
|
||||
set test {a b=c| should return b=c (bash-3) or c (bash-4)}; # | = cursor position
|
||||
if {$bash_versinfo_0 <= 3} {
|
||||
set cmd {COMP_WORDS=(a "b=c"); COMP_CWORD=1}
|
||||
set expected b=c
|
||||
} else {
|
||||
set cmd {COMP_WORDS=(a b = c); COMP_CWORD=3}
|
||||
set expected c
|
||||
}; # if
|
||||
append cmd {; COMP_LINE='a b=c'; COMP_POINT=5; _get_cword}
|
||||
assert_bash_list $expected $cmd $test
|
||||
|
||||
|
||||
sync_after_int
|
||||
|
||||
|
||||
set test {a *| should return *}; # | = cursor position
|
||||
@ -117,14 +185,37 @@ set cmd {COMP_WORDS=(a \*); COMP_CWORD=1; COMP_LINE='a *'; COMP_POINT=4; _get_cw
|
||||
assert_bash_list * $cmd $test
|
||||
|
||||
|
||||
set test {a $(b c| should return c}; # | = cursor position
|
||||
sync_after_int
|
||||
|
||||
|
||||
set test {a $(b c| should return $(b c}; # | = cursor position
|
||||
set cmd {COMP_WORDS=(a '$(b c'); COMP_CWORD=1; COMP_LINE='a $(b c'; COMP_POINT=7; _get_cword}
|
||||
assert_bash_list c $cmd $test
|
||||
#assert_bash_list {{$(b\ c}} $cmd $test
|
||||
send "$cmd\r"
|
||||
expect -ex "$cmd\r\n"
|
||||
expect {
|
||||
-ex "\$(b c/@" { pass "$test" }
|
||||
# Expected failure on bash-4
|
||||
-ex "c/@" { xfail "$test" }
|
||||
}; # expect
|
||||
|
||||
|
||||
set test {a $(b c\ d| should return c\ d}; # | = cursor position
|
||||
sync_after_int
|
||||
|
||||
|
||||
set test {a $(b c\ d| should return $(b c\ d}; # | = cursor position
|
||||
set cmd {COMP_WORDS=(a '$(b c\ d'); COMP_CWORD=1; COMP_LINE='a $(b c\ d'; COMP_POINT=10; _get_cword}
|
||||
assert_bash_list c $cmd $test
|
||||
#assert_bash_list {{$(b\ c\\\ d}} $cmd $test
|
||||
send "$cmd\r"
|
||||
expect -ex "$cmd\r\n"
|
||||
expect {
|
||||
-ex "\$(b c\\ d/@" { pass "$test" }
|
||||
# Expected failure on bash-4
|
||||
-ex "c\\ d/@" { xfail "$test" }
|
||||
}; # expect
|
||||
|
||||
|
||||
sync_after_int
|
||||
|
||||
|
||||
teardown
|
||||
|
Loading…
x
Reference in New Issue
Block a user