[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