[Bash-completion-commits] [SCM] bash-completion branch, master, updated. f733e71e1f8d63c072a402346d8162f9c6b63ae2
Freddy Vulto
fvulto at gmail.com
Sun Sep 20 12:13:25 UTC 2009
The following commit has been merged in the master branch:
commit f733e71e1f8d63c072a402346d8162f9c6b63ae2
Author: Freddy Vulto <fvulto at gmail.com>
Date: Sun Sep 20 14:11:26 2009 +0200
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>]
diff --git a/bash_completion b/bash_completion
index 9623ffa..3b88f13 100644
--- a/bash_completion
+++ b/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.)
+# @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
#
-# 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.
-_get_cword()
+# 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.
diff --git a/test/lib/library.exp b/test/lib/library.exp
index 107a9ab..8964dd2 100644
--- a/test/lib/library.exp
+++ b/test/lib/library.exp
@@ -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
diff --git a/test/unit/_get_cword.exp b/test/unit/_get_cword.exp
index f685ebf..94b18b5 100644
--- a/test/unit/_get_cword.exp
+++ b/test/unit/_get_cword.exp
@@ -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
+
+
+sync_after_int
-set test {a $(b c\ d| should return c\ d}; # | = cursor position
+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
--
bash-completion
More information about the Bash-completion-commits
mailing list