[Bash-completion-commits] [SCM] bash-completion branch, master, updated. 08c587848368a54e4d3e813bf37dad4be6fe10b7

Freddy Vulto fvulto at gmail.com
Sun Dec 6 22:18:15 UTC 2009


The following commit has been merged in the master branch:
commit 08c587848368a54e4d3e813bf37dad4be6fe10b7
Author: Freddy Vulto <fvulto at gmail.com>
Date:   Sun Dec 6 23:16:31 2009 +0100

    Merged __get_cword3 & __get_cword4 to _get_cword
    Actually enhanced __get_cword3 to _get_cword, and removed __get_cword4.
    __get_cword4 could handle chars to exclude from COMP_WORDBREAKS, but
    failed with partial quoted arguments (e.g. "a 'b c|", | = cursor
    position).  This was no problem till bash-4.0.35, because bash < 4.0.35
    also returned partial quoted arguments incorrectly.  See also:
    http://www.mail-archive.com/bug-bash@gnu.org/msg06094.html
    
    Now that bash-4.0.35 returns quoted arguments ok, __get_cword3 is
    enhanced to also handle chars to exclude from COMP_WORDBREAKS.  Because
    __get_cword3 also handles partial quoted arguments correctly, this makes
    __get_cword3 suitable for bash-4 as well.

diff --git a/bash_completion b/bash_completion
index 9732010..a885069 100644
--- a/bash_completion
+++ b/bash_completion
@@ -208,178 +208,129 @@ dequote()
     eval echo "$1" 2> /dev/null
 }
 
-# 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 [ ${BASH_VERSINFO[0]} -ge 4 ] ; then
-        __get_cword4 "$@"
+# Reassemble command line words, excluding specified characters from the
+# list of word completion separators (COMP_WORDBREAKS).
+# @param $1 chars  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, so we would pass the
+#     colon (:) as $1 here.
+# @param $2 words  Name of variable to return words to
+# @param $3 cword  Name of variable to return cword to
+#
+__reassemble_comp_words_by_ref() {
+    local exclude i j ref
+    # On bash-3, `COMP_WORDBREAKS' is empty which is ok; no additional
+    # word breaking is done on bash-3.
+    local wordbreaks="$COMP_WORDBREAKS"
+    # Exclude word separator characters?
+    if [[ $1 ]]; then
+        # Yes, exclude word separator characters;
+        # Exclude only those characters, which were really included
+        exclude="${1//[^$COMP_WORDBREAKS]}"
+    fi
+        
+    # Are characters excluded which were former included?
+    if [[ $exclude ]]; then
+        # Yes, list of word completion separators has shrunk;
+        # Re-assemble words to complete
+        for (( i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do
+            # Is current word not word 0 (the command itself) and is word of
+            # length 1 and is word newly excluded from being word separator?
+            while [[ $i -gt 0 && ${#COMP_WORDS[$i]} == 1 && ${COMP_WORDS[$i]//[^$exclude]} ]]; do
+                [ $j -ge 2 ] && ((j--))
+                # Append word separator to current word
+                ref="$2[$j]"
+                eval $2[$j]=\""${!ref}${COMP_WORDS[$i]}"\"
+                # Indicate new cword
+                [ $i = $COMP_CWORD ] && eval $3=$j
+                # Indicate next word if available, else end *both* while and for loop
+                (( $i < ${#COMP_WORDS[@]} - 1)) && ((i++)) || break 2
+            done
+            # Append word to current word
+            ref="$2[$j]"
+            eval $2[$j]=\""${!ref}${COMP_WORDS[$i]}"\"
+            # Indicate new cword
+            [ $i = $COMP_CWORD ] && eval $3=$j
+        done
     else
-        __get_cword3 "$2"
+        # No, list of word completions separators hasn't changed;
+        eval $2=\( \"\${COMP_WORDS[@]}\" \)
+        eval $3=$COMP_CWORD
     fi
-} # _get_cword()
+} # __reassemble_comp_words_by_ref()
 
-# Get word previous to the current word;
-# Accepts the same arguments as _get_cword()
-#
-# This is a good alternative to `prev=${COMP_WORDS[COMP_CWORD-1]}' because bash4
-# will properly return the previous word with respect to any given exclusions to
-# COMP_WORDBREAKS.
-_get_pword() { _get_cword "${@:-}" 1; }
 
-# 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 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 -------->   ^
+# @param $1 string  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, so we would pass the
+#     colon (:) as $1 in this case.  Bash-3 doesn't do word splitting, so this
+#     ensures we get the same word on both bash-3 and bash-4.
+# @param $2 integer  Index number of word to return, negatively offset to the
+#     current word (default is 0, previous is 1), respecting the exclusions
+#     given at $1.  For example, `__get_cword4 "=:" 1' returns the word left of
+#     the current word, respecting the exclusions "=:".
 #
-[ ${BASH_VERSINFO[0]} -lt 4 ] &&
-__get_cword3()
+_get_cword()
 {
-    # return previous word offset by $1
-    if [[ ${1//[^0-9]/} ]]; then
-        printf "%s" "${COMP_WORDS[COMP_CWORD-$1]}"
-    elif [[ "${#COMP_WORDS[COMP_CWORD]}" -eq 0 ]] || [[ "$COMP_POINT" == "${#COMP_LINE}" ]]; then
-        printf "%s" "${COMP_WORDS[COMP_CWORD]}"
+    local cword words
+    __reassemble_comp_words_by_ref "$1" words cword
+
+    # return previous word offset by $2
+    if [[ ${2//[^0-9]/} ]]; then
+        printf "%s" "${words[cword-$2]}"
+    elif [[ "${#words[cword]}" -eq 0 ]] || [[ "$COMP_POINT" == "${#COMP_LINE}" ]]; then
+        printf "%s" "${words[cword]}"
     else
         local i
         local cur="$COMP_LINE"
         local index="$COMP_POINT"
-        for (( i = 0; i <= COMP_CWORD; ++i )); do
+        for (( i = 0; i <= 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]}"
+                # Current word fits in $cur?
+                "${#cur}" -ge ${#words[i]} &&
+                # $cur doesn't match cword?
+                "${cur:0:${#words[i]}}" != "${words[i]}"
                 ]]; do
                 # Strip first character
                 cur="${cur:1}"
                 # Decrease cursor position
-                index="$(( index - 1 ))"
+                ((index--))
             done
 
-            # Does found COMP_WORD matches COMP_CWORD?
-            if [[ "$i" -lt "$COMP_CWORD" ]]; then
-                # No, COMP_CWORD lies further;
+            # Does found word matches cword?
+            if [[ "$i" -lt "$cword" ]]; then
+                # No, cword lies further;
                 local old_size="${#cur}"
-                cur="${cur#${COMP_WORDS[i]}}"
+                cur="${cur#${words[i]}}"
                 local new_size="${#cur}"
-                index="$(( index - old_size + new_size ))"
+                index=$(( index - old_size + new_size ))
             fi
         done
 
-        if [[ "${COMP_WORDS[COMP_CWORD]:0:${#cur}}" != "$cur" ]]; then
+        if [[ "${words[cword]:0:${#cur}}" != "$cur" ]]; then
             # We messed up! At least return the whole word so things
             # keep working
-            printf "%s" "${COMP_WORDS[COMP_CWORD]}"
+            printf "%s" "${words[cword]}"
         else
             printf "%s" "${cur:0:$index}"
         fi
     fi
-} # __get_cword3()
+} # _get_cword()
 
 
-# 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
-#
-# @param $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.
-# @param $2 integer
-# $2 integer (optional) Return word according to $COMP_WORDBREAKS, negatively
-#     offset by the value. For example, `__get_cword4 "=:" -1' returns the word
-#     left of the current word, respecting the exclusions given at $1
-# See also:
-# _get_cword, main routine
-# __get_cword3, bash-3 variant
+# Get word previous to the current word.
+# This is a good alternative to `prev=${COMP_WORDS[COMP_CWORD-1]}' because bash4
+# will properly return the previous word with respect to any given exclusions to
+# COMP_WORDBREAKS.
+# @see _get_cword()
 #
-[ ${BASH_VERSINFO[0]} -ge 4 ] && {
-# return index of first occuring break character in $1; return 0 if none
-__break_index() {
-    if [[ $1 == *[$WORDBREAKS]* ]]; then
-        local w="${1%[$WORDBREAKS]*}"
-        echo $((${#w}+1))
-    else
-        echo 0
-    fi
-} # __break_index()
-
-# return the index of the start of the last word in $@
-__word_start() {
-    local buf="$@"
-    local start="$(__break_index "$buf")"
-    while [[ $start -ge 2 ]]; do
-        # Get character before $start
-        local char="${cur:$(( start - 2 )):1}"
-        # If the WORDBREAK character isn't escaped, exit loop
-        [[ $char != \\ ]] && break
-        # The WORDBREAK character is escaped; recalculate $start
-        buf="${COMP_LINE:0:$(( start - 2 ))}"
-        start=$(__break_index "$buf")
-    done
-    echo $start
-} # __word_start()
-
-__get_cword4()
-{
-    local exclude="$1" n_idx="${2:-0}"
-    local i
-    local LC_CTYPE=C
-    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//[\"\']/}"
-    if [[ $exclude ]]; then
-        for (( i=0; i<${#exclude}; ++i )); do
-            local char="${exclude:$i:1}"
-            WORDBREAKS="${WORDBREAKS//$char/}"
-        done
-    fi
-    local cur="${COMP_LINE:0:$COMP_POINT}"
-    local tmp="$cur"
-
-    # calculate current word, negatively offset by n_idx
-    cur="${tmp:$(__word_start "$tmp")}"
-    while [[ $n_idx -gt 0 ]]; do
-        local tmp="${tmp%[$WORDBREAKS]$cur}"    # truncate passed string
-        local cur="${tmp:$(__word_start "$tmp")}" # then recalculate
-        ((--n_idx))
-    done
-    printf "%s" "$cur"
-} # __get_cword4()
-} # [ ${BASH_VERSINFO[0]} -ge 4 ]
+_get_pword() { _get_cword "${@:-}" 1; }
 
 
 # If the word-to-complete contains a colon (:), left-trim COMPREPLY items with
@@ -388,10 +339,17 @@ __get_cword4()
 # colons are always completed as entire words if the word to complete contains
 # a colon.  This function fixes this, by removing the colon-containing-prefix
 # from COMPREPLY items.
+# The preferred solution is to remove the colon (:) from COMP_WORDBREAKS in
+# your .bashrc:
+#
+#    # Remove colon (:) from list of word completion separators
+#    COMP_WORDBREAKS=${COMP_WORDBREAKS//:}
+#
 # See also: Bash FAQ - E13) Why does filename completion misbehave if a colon
 # appears in the filename? - http://tiswww.case.edu/php/chet/bash/FAQ
 # @param $1 current word to complete (cur)
 # @modifies global array $COMPREPLY
+#
 __ltrim_colon_completions() {
     # If word-to-complete contains a colon,
     # and bash-version < 4,
diff --git a/test/lib/library.sh b/test/lib/library.sh
index 581ce4b..476cea3 100644
--- a/test/lib/library.sh
+++ b/test/lib/library.sh
@@ -1,10 +1,19 @@
 # Bash library for bash-completion DejaGnu testsuite
 
 
+# @param $1  Char to add to $COMP_WORDBREAKS
+# @see remove_comp_wordbreak_char()
+add_comp_wordbreak_char() {
+    if [ ${BASH_VERSINFO[0]} -ge 4 ]; then
+        [[ "${COMP_WORDBREAKS//[^$1]}" ]] || COMP_WORDBREAKS=$COMP_WORDBREAKS$1
+    fi
+} # add_comp_wordbreak_char()
+
+
 # Diff environment files to detect if environment is unmodified
 # @param $1  File 1
 # @param $2  File 2
-# @param $1  Additional sed script
+# @param $3  Additional sed script
 diff_env() {
 	diff "$1" "$2" | sed -e "
 	/^[0-9,]\{1,\}[acd]/d  # Remove diff line indicators
@@ -19,9 +28,9 @@ diff_env() {
 # Unset variable after outputting.
 # @param $1  Name of array variable to process
 echo_array() {
-	local IFS=$'\n'
-	eval printf "%s" \"\${$1[*]}\" | sort
-}
+    local name=$1[@]
+    printf "%s\n" "${!name}" | sort
+} # echo_array()
 
 
 # Check if current bash version meets specified minimum
@@ -43,6 +52,16 @@ is_bash_version_minimal() {
 	]]
 } # is_bash_version_minimal()
 
+
+# @param $1  Char to remove from $COMP_WORDBREAKS
+# @see add_comp_wordbreak_char()
+remove_comp_wordbreak_char() {
+    if [ ${BASH_VERSINFO[0]} -ge 4 ]; then
+        COMP_WORDBREAKS=${COMP_WORDBREAKS//$1}
+    fi
+} # remove_comp_wordbreak_char()
+
+
 # Local variables:
 # mode: shell-script
 # sh-basic-offset: 4
diff --git a/test/unit/_get_cword.exp b/test/unit/_get_cword.exp
index 1339324..eaa62cb 100644
--- a/test/unit/_get_cword.exp
+++ b/test/unit/_get_cword.exp
@@ -79,47 +79,68 @@ 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
-if {[lindex $::BASH_VERSINFO 0] <= 3} {
-    set cmd {COMP_WORDS=(a "'b c"); COMP_CWORD=1}
-} else {
+if {
+    [lindex $::BASH_VERSINFO 0] == 4 && 
+    [lindex $::BASH_VERSINFO 1] == 0 &&
+    [lindex $::BASH_VERSINFO 2] < 35
+} {
     set cmd {COMP_WORDS=(a "'" b c); COMP_CWORD=3}
+} else {
+    set cmd {COMP_WORDS=(a "'b c"); COMP_CWORD=1}
 }; # 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 "c/@" { xfail "$test" }
+    -ex "c/@" { 
+        if {
+            [lindex $::BASH_VERSINFO 0] == 4 &&
+            [lindex $::BASH_VERSINFO 1] == 0 &&
+            [lindex $::BASH_VERSINFO 2] < 35
+        } {xfail "$test"} {fail "$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=1; COMP_LINE="a \"b c"; COMP_POINT=6; _get_cword};
+if {
+    [lindex $::BASH_VERSINFO 0] == 4 && 
+    [lindex $::BASH_VERSINFO 1] == 0 &&
+    [lindex $::BASH_VERSINFO 2] < 35
+} {
+    set cmd {COMP_WORDS=(a "\"" b c); COMP_CWORD=3}
+} else {
+    set cmd {COMP_WORDS=(a "\"b c"); COMP_CWORD=1}
+}; # 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 "c/@" { xfail "$test" }
+    -ex "c/@" {
+        if {
+            [lindex $::BASH_VERSINFO 0] == 4 &&
+            [lindex $::BASH_VERSINFO 1] == 0 &&
+            [lindex $::BASH_VERSINFO 2] < 35
+        } {xfail "$test"} {fail "$test"}
+    }
 }; # expect
 
 
 sync_after_int
 
 
-set test {a b:c| should return b:c (bash-3) or c (bash-4)};  # | = cursor position
+set test {a b:c| with WORDBREAKS += : should return b:c (bash-3) or c (bash-4)};  # | = cursor position
 if {[lindex $::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 cmd {add_comp_wordbreak_char :; COMP_WORDS=(a b : c); COMP_CWORD=3}
     set expected c
 }; # if
 append cmd {; COMP_LINE='a b:c'; COMP_POINT=5; _get_cword}
@@ -142,6 +163,14 @@ assert_bash_list b:c $cmd $test
 sync_after_int
 
 
+set test {a :| with WORDBREAKS -= : should return :};  # | = cursor position
+set cmd {COMP_WORDS=(a :); COMP_CWORD=1; COMP_LINE='a :'; COMP_POINT=3; _get_cword :}
+assert_bash_list : $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

-- 
bash-completion



More information about the Bash-completion-commits mailing list