summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFreddy Vulto <fvulto@gmail.com>2010-02-07 15:21:44 +0100
committerFreddy Vulto <fvulto@gmail.com>2010-02-07 15:21:44 +0100
commit2cd91420d2e9a4ce571cbe4e4a90e512653be151 (patch)
tree1ed353ca15d0dfac28e4cb9ecf827fffe441a666
parentc72e20b42fe113b5496c3424598dbc5e41e1e644 (diff)
parentb529cee550fa20678ecd7c92ed575f3342db1d49 (diff)
downloadbash-completion-2cd91420d2e9a4ce571cbe4e4a90e512653be151.tar.gz
Merge branch 'fvu'
-rw-r--r--bash_completion136
-rw-r--r--test/lib/library.exp46
-rw-r--r--test/unit/_get_comp_words_by_ref.exp339
3 files changed, 510 insertions, 11 deletions
diff --git a/bash_completion b/bash_completion
index d59ef6f2..4dc09ea1 100644
--- a/bash_completion
+++ b/bash_completion
@@ -253,6 +253,142 @@ __reassemble_comp_words_by_ref() {
} # __reassemble_comp_words_by_ref()
+# @param $1 exclude 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 words Name of variable to return words to
+# @param $3 cword Name of variable to return cword to
+# @param $4 cur Name of variable to return current word to complete to
+# @see ___get_cword_at_cursor_by_ref()
+__get_cword_at_cursor_by_ref() {
+ # NOTE: The call to the main function ___get_cword_at_cursor_by_ref() is
+ # wrapped to make collisions with local variable names less likely.
+ local __words __cword __cur
+ ___get_cword_at_cursor_by_ref "$1" __words __cword __cur
+
+ eval $2=\( \"\${__words[@]}\" \)
+ eval $3=\$__cword
+ eval $4=\$__cur
+}
+
+
+# @param $1 exclude
+# @param $2 words Name of variable to return words to
+# @param $3 cword Name of variable to return cword to
+# @param $4 cur Name of variable to return current word to complete to
+# @note Do not call this function directly but call
+# `__get_cword_at_cursor_by_ref()' instead to make variable name collisions
+# less likely
+# @see __get_cword_at_cursor_by_ref()
+___get_cword_at_cursor_by_ref() {
+ local cword words
+ __reassemble_comp_words_by_ref "$1" words cword
+
+ local i
+ local cur="$COMP_LINE"
+ local index="$COMP_POINT"
+ for (( i = 0; i <= cword; ++i )); do
+ while [[
+ # 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--))
+ done
+
+ # Does found word matches cword?
+ if [[ "$i" -lt "$cword" ]]; then
+ # No, cword lies further;
+ local old_size="${#cur}"
+ cur="${cur#${words[i]}}"
+ local new_size="${#cur}"
+ index=$(( index - old_size + new_size ))
+ fi
+ done
+
+ if [[ "${words[cword]:0:${#cur}}" != "$cur" ]]; then
+ # We messed up. At least return the whole word so things keep working
+ eval $4=\"\${words[cword]}\"
+ else
+ eval $4=\"\${cur:0:\$index}\"
+ fi
+
+ eval $2=\( \"\${words[@]}\" \)
+ eval $3=\$cword
+}
+
+
+# Get the word to complete and optional previous words.
+# 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 --------> ^
+# Also one is able to cross over possible wordbreak characters.
+# Usage: _get_comp_words_by_ref [OPTIONS] VAR1 [VAR2 [VAR3]]
+# Example usage:
+#
+# $ _get_comp_words_by_ref -n : cur prev
+#
+# Options: -n EXCLUDE 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 -n option 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.
+# @see __get_comp_words_by_ref
+_get_comp_words_by_ref() {
+ # NOTE: The call to the main function __get_comp_words_by_ref() is wrapped
+ # to make collisions with local variable name less likely.
+ local __words __cword __cur __var __vars
+ __get_comp_words_by_ref __words __cword __cur __vars "$@"
+ set -- "${__vars[@]}"
+ eval $1=\$__cur
+ shift
+ for __var; do
+ ((__cword--))
+ [[ ${__words[__cword]} ]] && eval $__var=\${__words[__cword]}
+ done
+}
+
+
+# @param $1 words Name of variable to return words to
+# @param $2 cword Name of variable to return cword to
+# @param $3 cur Name of variable to return current word to complete to
+# @param $4 varnames Name of variable to return array of variable names to
+# @param $@ Arguments to _get_comp_words_by_ref()
+# @note Do not call this function directly but call `_get_comp_words_by_ref()'
+# instead to make variable name collisions less likely
+# @see _get_comp_words_by_ref()
+__get_comp_words_by_ref()
+{
+ local exclude flag i OPTIND=5 # Skip first four arguments
+ local cword words cur varnames=()
+ while getopts "n:" flag "$@"; do
+ case $flag in
+ n) exclude=$OPTARG ;;
+ esac
+ done
+ varnames=( ${!OPTIND} )
+ let "OPTIND += 1"
+ while [[ $# -ge $OPTIND ]]; do
+ varnames+=( ${!OPTIND} )
+ let "OPTIND += 1"
+ done
+
+ __get_cword_at_cursor_by_ref "$exclude" words cword cur
+
+ eval $1=\( \"\${words[@]}\" \)
+ eval $2=\$cword
+ eval $3=\$cur
+ eval $4=\( \"\${varnames[@]}\" \)
+}
+
+
# 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.
diff --git a/test/lib/library.exp b/test/lib/library.exp
index 3de310bf..0092f3fb 100644
--- a/test/lib/library.exp
+++ b/test/lib/library.exp
@@ -79,18 +79,22 @@ proc assert_bash_type {command} {
# @result boolean True if successful, False if not
proc assert_bash_list {expected cmd {test ""} {prompt /@} {size 20}} {
if {$test == ""} {set test "$cmd should show expected output"}
- send "$cmd\r"
- expect -ex "$cmd\r\n"
-
- if {[match_items $expected $test $prompt $size]} {
- expect {
- -re $prompt { pass "$test" }
- -re eof { unresolved "eof" }
- }; # expect
+ if {[llength $expected] == 0} {
+ assert_no_output $cmd $test $prompt
} else {
- fail "$test"
- }; # if
-}; # assert_bash_list()
+ send "$cmd\r"
+ expect -ex "$cmd\r\n"
+
+ if {[match_items $expected $test $prompt $size]} {
+ expect {
+ -re $prompt { pass "$test" }
+ -re eof { unresolved "eof" }
+ }
+ } else {
+ fail "$test"
+ }
+ }
+}
proc assert_bash_list_dir {expected cmd dir {test ""} {prompt /@} {size 20}} {
@@ -451,6 +455,26 @@ proc assert_no_complete {{cmd} {test ""}} {
}; # assert_no_complete()
+# Check that no output is generated on a certain command.
+# @param string $cmd The command to attempt to complete.
+# @param string $test Optional parameter with test name.
+# @param string $prompt (optional) Bash prompt. Default is "/@"
+proc assert_no_output {{cmd} {test ""} {prompt /@}} {
+ if {[string length $test] == 0} {
+ set test "$cmd shouldn't generate output"
+ }
+
+ send "$cmd\r"
+ expect -ex "$cmd"
+
+ expect {
+ -re "^\r\n$prompt$" { pass "$test" }
+ default { fail "$test" }
+ timeout { fail "$test" }
+ }
+}
+
+
# Source/run file with additional tests if completion for the specified command
# is installed in bash.
# @param string $command Command to check completion availability for.
diff --git a/test/unit/_get_comp_words_by_ref.exp b/test/unit/_get_comp_words_by_ref.exp
new file mode 100644
index 00000000..67862b3a
--- /dev/null
+++ b/test/unit/_get_comp_words_by_ref.exp
@@ -0,0 +1,339 @@
+proc setup {} {
+ assert_bash_exec {unset COMP_CWORD COMP_LINE COMP_POINT COMP_WORDS}
+ save_env
+}; # setup()
+
+
+proc teardown {} {
+ assert_bash_exec {unset COMP_CWORD COMP_LINE COMP_POINT COMP_WORDS cur prev prev2}
+ # Delete 'COMP_WORDBREAKS' occupying two lines
+ assert_env_unmodified {
+ /COMP_WORDBREAKS=/{N
+ d
+ }
+ }
+}; # teardown()
+
+
+setup
+
+
+set test "_get_comp_words_by_ref should run without errors"
+assert_bash_exec {_get_comp_words_by_ref cur > /dev/null} $test
+
+
+sync_after_int
+
+
+# See also ./lib/completions/alias.exp. Here `_get_cword' is actually tested
+# by moving the cursor left into the current word.
+
+
+set test "a b|"; # | = cursor position
+set cmd {COMP_WORDS=(a b); COMP_CWORD=1; COMP_LINE='a b'; COMP_POINT=3; _get_comp_words_by_ref cur prev; echo "$cur $prev"}
+assert_bash_list {"b a"} $cmd $test
+
+
+sync_after_int
+
+
+set test "a |"; # | = cursor position
+set cmd {COMP_WORDS=(a); COMP_CWORD=1; COMP_LINE='a '; COMP_POINT=2; _get_comp_words_by_ref cur prev; echo "$cur $prev"}
+assert_bash_list {" a"} $cmd $test
+
+
+sync_after_int
+
+
+set test "a b |"; # | = cursor position
+set cmd {COMP_WORDS=(a b ''); COMP_CWORD=2; COMP_LINE='a b '; COMP_POINT=4; _get_comp_words_by_ref cur prev prev2; echo "$cur $prev $prev2"}
+assert_bash_list {" b a"} $cmd $test
+
+
+sync_after_int
+
+
+set test "a b | with WORDBREAKS -= :"; # | = cursor position
+set cmd {COMP_WORDS=(a b ''); COMP_CWORD=2; COMP_LINE='a b '; COMP_POINT=4; _get_comp_words_by_ref -n : cur; printf %s "$cur"}
+assert_bash_list {} $cmd $test
+
+
+sync_after_int
+
+
+set test "a b|c"; # | = cursor position
+set cmd {COMP_WORDS=(a bc); COMP_CWORD=1; COMP_LINE='a bc'; COMP_POINT=3; _get_comp_words_by_ref cur prev; echo "$cur $prev"}
+assert_bash_list {"b a"} $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_comp_words_by_ref cur prev; echo "$cur $prev"}
+assert_bash_list {"b\\ c a"} $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_comp_words_by_ref cur prev; echo "$cur $prev"}
+assert_bash_list {"b\\ a"} $cmd $test
+
+
+sync_after_int
+
+
+set test {a "b\|}; #"# | = cursor position
+set cmd {COMP_WORDS=(a '"b\'); COMP_CWORD=1; COMP_LINE='a "b\'; COMP_POINT=5; _get_comp_words_by_ref cur prev; echo "$cur $prev"}
+assert_bash_list {"\"b\\ a"} $cmd $test
+
+
+sync_after_int
+
+
+set test {a 'b c|}; # | = cursor position
+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_comp_words_by_ref cur prev; echo "$cur $prev"}
+send "$cmd\r"
+expect -ex "$cmd\r\n"
+expect {
+ -ex "'b c a\r\n/@" { pass "$test" }
+ -ex "c b\r\n/@" {
+ 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|}; #"# | = cursor position
+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}
+assert_bash_exec $cmd
+set cmd {_get_comp_words_by_ref cur prev; echo "$cur $prev"};
+send "$cmd\r"
+expect -ex "$cmd\r\n"
+expect {
+ -ex "\"b c a\r\n/@" { pass "$test" }
+ -ex "c b\r\n/@" {
+ 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| with WORDBREAKS += :}; # | = cursor position
+if {[lindex $::BASH_VERSINFO 0] <= 3} {
+ set cmd {COMP_WORDS=(a "b:c"); COMP_CWORD=1}
+ set expected {"b:c a"}
+} else {
+ 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}
+# NOTE: Split-send cmd to prevent backspaces (\008) in output
+assert_bash_exec $cmd $test
+set cmd {_get_comp_words_by_ref cur prev; echo "$cur $prev"}
+assert_bash_list $expected $cmd $test
+
+
+sync_after_int
+
+
+set test {a b:c| with WORDBREAKS -= :}; # | = cursor position
+if {[lindex $::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}
+assert_bash_exec $cmd $test
+set cmd {_get_comp_words_by_ref -n : cur prev; echo "$cur $prev"}
+assert_bash_list {"b:c a"} $cmd $test
+
+
+sync_after_int
+
+
+set test {a b c:| with WORDBREAKS -= :}; # | = cursor position
+if {[lindex $::BASH_VERSINFO 0] <= 3} {
+ set cmd {COMP_WORDS=(a b c:); COMP_CWORD=2}
+} else {
+ set cmd {COMP_WORDS=(a b c :); COMP_CWORD=3}
+}; # if
+append cmd {; COMP_LINE='a b c:'; COMP_POINT=6}
+assert_bash_exec $cmd $test
+set cmd {_get_comp_words_by_ref -n : cur prev; echo "$cur $prev $prev2"}
+assert_bash_list {"c: b a"} $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}
+assert_bash_exec $cmd
+set cmd {_get_comp_words_by_ref -n : cur prev; echo "$cur $prev"}
+assert_bash_list {": a"} $cmd $test
+
+
+sync_after_int
+
+
+set test {a b::| with WORDBREAKS -= : should return b::}; # | = cursor position
+if {[lindex $::BASH_VERSINFO 0] <= 3} {
+ set cmd {COMP_WORDS=(a "b::"); COMP_CWORD=1}
+} else {
+ set cmd {COMP_WORDS=(a b ::); COMP_CWORD=2}
+}; # if
+append cmd {; COMP_LINE='a b::'; COMP_POINT=5}
+assert_bash_exec $cmd
+set cmd {_get_comp_words_by_ref -n : cur prev; echo "$cur $prev"}
+assert_bash_list {"b:: a"} $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
+set cmd {COMP_WORDS=(a -n); COMP_CWORD=1; COMP_LINE='a -n'; COMP_POINT=4}
+assert_bash_exec $cmd
+set cmd {_get_comp_words_by_ref cur; printf %s $cur}
+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}
+assert_bash_exec $cmd
+set cmd {_get_comp_words_by_ref cur prev; echo "$cur"}
+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 {[lindex $::BASH_VERSINFO] <= 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}
+assert_bash_exec $cmd
+set cmd {_get_comp_words_by_ref cur prev; echo "$cur"}
+assert_bash_list $expected $cmd $test
+
+
+sync_after_int
+
+
+set test {a *| should return *}; # | = cursor position
+set cmd {COMP_WORDS=(a \*); COMP_CWORD=1; COMP_LINE='a *'; COMP_POINT=4}
+assert_bash_exec $cmd
+set cmd {_get_comp_words_by_ref cur; echo "$cur"}
+assert_bash_list * $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=7}
+assert_bash_exec $cmd
+set cmd {_get_comp_words_by_ref cur; printf %s "$cur"}
+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 $(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}
+assert_bash_exec $cmd
+set cmd {_get_comp_words_by_ref cur; printf %s "$cur"}
+#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
+
+
+set test {a 'b&c| should return 'b&c}; # | = cursor position
+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=4}
+} else {
+ set cmd {COMP_WORDS=(a "'b&c"); COMP_CWORD=1}
+}; # if
+append cmd {; COMP_LINE="a 'b&c"; COMP_POINT=6}
+assert_bash_exec $cmd
+set cmd {_get_comp_words_by_ref cur prev; printf %s "$cur"}
+send "$cmd\r"
+expect -ex "$cmd\r\n"
+expect {
+ -ex "'b&c/@" { pass "$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
+
+
+teardown