From c9c98da36edeb9305e24e7ea2d8ccf8b0934ce70 Mon Sep 17 00:00:00 2001 From: Freddy Vulto Date: Thu, 24 Dec 2009 10:00:29 +0100 Subject: [PATCH] Fixed `quote_readline'. This fixes completing filenames containing single quote (') on bash-4. Also added emulation of `-o filenames' to _filedir. Added tests for _filedir. Fixed array assignment within __reassemble_comp_words_by_ref(). --- bash_completion | 133 ++++++++++++++---- test/fixtures/_filedir/a b/i | 0 "test/fixtures/_filedir/a\"b/d" | 0 test/fixtures/_filedir/a$b/h | 0 test/fixtures/_filedir/a&b/f | 0 test/fixtures/_filedir/a'b/c | 0 "test/fixtures/_filedir/a\\b/g" | 0 test/fixtures/_filedir/ab/e | 0 test/fixtures/compgen/a'b/c | 0 test/fixtures/compgen/t1.txt | 121 +++++++++++++++++ test/fixtures/compgen/t2.txt | 121 +++++++++++++++++ test/fixtures/compgen/t3.txt | 121 +++++++++++++++++ test/lib/completions/screen.exp | 5 + test/unit/_filedir.exp | 234 ++++++++++++++++++++++++++++++++ test/unit/_get_cword.exp | 28 ++++ test/unit/compgen.exp | 52 +++++++ 16 files changed, 788 insertions(+), 27 deletions(-) create mode 100644 test/fixtures/_filedir/a b/i create mode 100644 "test/fixtures/_filedir/a\"b/d" create mode 100644 test/fixtures/_filedir/a$b/h create mode 100644 test/fixtures/_filedir/a&b/f create mode 100644 test/fixtures/_filedir/a'b/c create mode 100644 "test/fixtures/_filedir/a\\b/g" create mode 100644 test/fixtures/_filedir/ab/e create mode 100644 test/fixtures/compgen/a'b/c create mode 100644 test/fixtures/compgen/t1.txt create mode 100644 test/fixtures/compgen/t2.txt create mode 100644 test/fixtures/compgen/t3.txt create mode 100644 test/unit/_filedir.exp create mode 100644 test/unit/compgen.exp diff --git a/bash_completion b/bash_completion index 1b103d71..d6374f7c 100644 --- a/bash_completion +++ b/bash_completion @@ -188,19 +188,14 @@ quote() echo \'${1//\'/\'\\\'\'}\' #'# Help vim syntax highlighting } -# This function quotes the argument in a way so that readline dequoting -# results in the original argument +# @see _quote_readline_by_ref() quote_readline() { - if [ ${BASH_VERSINFO[0]} -ge 4 ]; then - # This function isn't really necessary on bash 4 - # See: http://lists.gnu.org/archive/html/bug-bash/2009-03/msg00155.html - echo "${1}" - return - fi - local t="${1//\\/\\\\}" - echo \'${t//\'/\'\\\'\'}\' #'# Help vim syntax highlighting -} + local quoted + _quote_readline_by_ref "$1" ret + printf %s "$ret" +} # quote_readline() + # This function shell-dequotes the argument dequote() @@ -224,8 +219,6 @@ __reassemble_comp_words_by_ref() { if [[ $1 ]]; then # Yes, exclude word separator characters; # Exclude only those characters, which were really included - # NOTE: On bash-3, `COMP_WORDBREAKS' is empty which is ok; no - # additional word breaking is done on bash-3. exclude="${1//[^$COMP_WORDBREAKS]}" fi @@ -240,7 +233,7 @@ __reassemble_comp_words_by_ref() { [ $j -ge 2 ] && ((j--)) # Append word separator to current word ref="$2[$j]" - eval $2[$j]=\""${!ref}${COMP_WORDS[$i]}"\" + 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 @@ -248,7 +241,7 @@ __reassemble_comp_words_by_ref() { done # Append word to current word ref="$2[$j]" - eval $2[$j]=\""${!ref}${COMP_WORDS[$i]}"\" + eval $2[$j]=\${!ref}\${COMP_WORDS[i]} # Indicate new cword [ $i = $COMP_CWORD ] && eval $3=$j done @@ -295,7 +288,7 @@ _get_cword() "${#cur}" -ge ${#words[i]} && # $cur doesn't match cword? "${cur:0:${#words[i]}}" != "${words[i]}" - ]]; do + ]]; do # Strip first character cur="${cur:1}" # Decrease cursor position @@ -369,6 +362,46 @@ __ltrim_colon_completions() { } # __ltrim_colon_completions() +# This function quotes the argument in a way so that readline dequoting +# results in the original argument. This is necessary for at least +# `compgen' which requires its arguments quoted/escaped: +# +# $ ls "a'b/" +# c +# $ compgen -f "a'b/" # Wrong, doesn't return output +# $ compgen -f "a\'b/" # Good (bash-4) +# a\'b/c +# $ compgen -f "a\\\\\'b/" # Good (bash-3) +# a\'b/c +# +# See also: http://lists.gnu.org/archive/html/bug-bash/2009-03/msg00155.html +# @param $1 Argument to quote +# @param $2 Name of variable to return result to +_quote_readline_by_ref() +{ + # If bash <= 3 and argument starts with single quote ('), double-escape +# if [[ ${BASH_VERSINFO[0]} -le 3 && ${1:0:1} == "'" ]]; then +# local t +# printf -v t %q "${1:1}" +# printf -v $2 %q "$t" +# else +# printf -v $2 %q "$1" +# fi + if [[ ${1:0:1} == "'" ]]; then + # Quote word, leaving out first character + printf -v $2 %q "${1:1}" + if [[ ${BASH_VERSINFO[0]} -le 3 ]]; then + # Double-quote word on bash-3 + printf -v $2 %q ${!2} + fi + elif [[ ${BASH_VERSINFO[0]} -le 3 && ${1:0:1} == '"' ]]; then + printf -v $2 %q "${1:1}" + else + printf -v $2 %q "$1" + fi +} # _quote_readline_by_ref() + + # This function performs file and directory completion. It's better than # simply using 'compgen -f', because it honours spaces in filenames. # If passed -d, it completes only on directories. If passed anything else, @@ -376,12 +409,12 @@ __ltrim_colon_completions() { # _filedir() { - local IFS=$'\t\n' xspec + local i IFS=$'\t\n' xspec - _expand || return 0 + __expand_tilde_by_ref cur local -a toks - local tmp + local quoted tmp # TODO: I've removed a "[ -n $tmp ] &&" before `printf '%s\n' $tmp', # and everything works again. If this bug @@ -393,28 +426,74 @@ _filedir() # because quotes-in-comments-in-a-subshell cause errors on # bash-3.1. See also: # http://www.mail-archive.com/bug-bash@gnu.org/msg01667.html + _quote_readline_by_ref "$cur" quoted toks=( ${toks[@]-} $( - compgen -d -- "$(quote_readline "$cur")" | { + compgen -d -- "$quoted" | { while read -r tmp; do printf '%s\n' $tmp done } )) + # On bash-3, special characters need to be escaped extra. This is + # unless the first character is a single quote ('). If the single + # quote appears further down the string, bash default completion also + # fails, e.g.: + # + # $ ls 'a&b/' + # f + # $ foo 'a&b/ # Becomes: foo 'a&b/f' + # $ foo a'&b/ # Nothing happens + # if [[ "$1" != -d ]]; then xspec=${1:+"!*.$1"} - toks=( ${toks[@]-} $( - compgen -f -X "$xspec" -- "$(quote_readline "$cur")" | { - while read -r tmp; do - [ -n $tmp ] && printf '%s\n' $tmp + if [[ ${cur:0:1} == "'" && ${BASH_VERSINFO[0]} -ge 4 ]]; then + toks=( ${toks[@]-} $( + eval compgen -f -X \"\$xspec\" -- $quoted + ) ) + else + toks=( ${toks[@]-} $( + compgen -f -X "$xspec" -- $quoted + ) ) + fi + if [ ${#toks[@]} -ne 0 ]; then + # If `compopt' is available, set `-o filenames' + compopt &>/dev/null && compopt -o filenames || + # No, `compopt' isn't available; + # Is `-o filenames' set? + [[ "$(complete -p ${COMP_WORDS[0]})" == *"-o filenames"* ]] || { + # No, `-o filenames' isn't set; + # Emulate `-o filenames' + # NOTE: A side-effect of emulating `-o filenames' is that backslash escape + # characters are visible within the list of presented completions, e.g. + # the completions look like: + # + # $ foo a + # a\ b/ a\$b/ + # + # whereas with `-o filenames' active the completions look like: + # + # $ ls a + # a b/ a$b/ + # + for ((i=0; i < ${#toks[@]}; i++)); do + # If directory exists, append slash (/) + if [[ ${cur:0:1} != "'" ]]; then + [[ -d ${toks[i]} ]] && toks[i]="${toks[i]}"/ + if [[ ${cur:0:1} == '"' ]]; then + toks[i]=${toks[i]//\\/\\\\} + toks[i]=${toks[i]//\"/\\\"} + toks[i]=${toks[i]//\$/\\\$} + else + toks[i]=$(printf %q ${toks[i]}) + fi + fi done } - )) + fi fi COMPREPLY=( "${COMPREPLY[@]}" "${toks[@]}" ) - [ ${#COMPREPLY[@]} -ne 0 ] && type compopt &>/dev/null && \ - compopt -o filenames } # _filedir() diff --git a/test/fixtures/_filedir/a b/i b/test/fixtures/_filedir/a b/i new file mode 100644 index 00000000..e69de29b diff --git "a/test/fixtures/_filedir/a\"b/d" "b/test/fixtures/_filedir/a\"b/d" new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/_filedir/a$b/h b/test/fixtures/_filedir/a$b/h new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/_filedir/a&b/f b/test/fixtures/_filedir/a&b/f new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/_filedir/a'b/c b/test/fixtures/_filedir/a'b/c new file mode 100644 index 00000000..e69de29b diff --git "a/test/fixtures/_filedir/a\\b/g" "b/test/fixtures/_filedir/a\\b/g" new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/_filedir/ab/e b/test/fixtures/_filedir/ab/e new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/compgen/a'b/c b/test/fixtures/compgen/a'b/c new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/compgen/t1.txt b/test/fixtures/compgen/t1.txt new file mode 100644 index 00000000..322a14d8 --- /dev/null +++ b/test/fixtures/compgen/t1.txt @@ -0,0 +1,121 @@ +BASH=/bin/bash +BASH_ARGC=() +BASH_ARGV=() +BASH_LINENO=() +BASH_SOURCE=() +BASH_VERSINFO=([0]="3" [1]="2" [2]="39" [3]="1" [4]="release" [5]="i486-pc-linux-gnu") +BASH_VERSION='3.2.39(1)-release' +CDPL_DIRS=([0]="/home/freddy/proj") +CDPM_DIRS= +CDP_DIRS=([0]="/home/freddy/proj" [1]="") +COLUMNS=130 +COMP_CACHE=/home/freddy/.bash_completion_lib.d/cache~ +COMP_DIR=/etc/bash_completion_lib +COMP_PATH=/home/freddy/.bash_completion_lib.d:/etc/bash_completion_lib +COMP_RESTRICT_BY_EXTENSION=0 +COMP_VERSION=bash_completion_lib-1.3.1 +DIRSTACK=() +EDITOR=/usr/bin/vim +EUID=1000 +GPGKEY=10A575C3 +GPG_AGENT_INFO=/tmp/gpg-Pg6JXR/S.gpg-agent:4129:1 +GPG_TTY=/dev/pts/0 +GREP_OPTIONS='--exclude '\''distrib/*'\'' --exclude tags' +GROUPS=() +HISTCONTROL=ignoreboth +HISTFILE=/home/freddy/.bash_history +HISTFILESIZE=500 +HISTIGNORE=exit +HISTSIZE=500 +HOME=/home/freddy +HOSTNAME=blondy +HOSTTYPE=i486 +IFS=$' \t\n' +LANG=en_US +LANGUAGE=en_NL:en_US:en_GB:en +LINES=49 +LOGNAME=freddy +MACHTYPE=i486-pc-linux-gnu +MAIL=/var/mail/freddy +MAILCHECK=60 +OLDPWD=/home/freddy/.bash_completion_lib.d +OPTERR=1 +OPTIND=1 +OSTYPE=linux-gnu +PATH=/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games:/home/freddy/proj/rc/bin +PIPESTATUS=([0]="0") +PPID=29352 +PS1=$'\\[\E[0;34m\\]\\!\\[\E[0m\\]\\[\E[1;32m\\]$(stoppedjobs)\\[\E[0m\\]:\\u@\\h:\\w> \\[\E[m\\]' +PS2='> ' +PS4='+ ' +PWD=/home/freddy/proj/bashCompletion/bash-completion.git/test/fixtures/compgen +SHELL=/bin/bash +SHELLOPTS=braceexpand:hashall:histexpand:interactive-comments:monitor:vi +SHLVL=1 +SSH_AUTH_SOCK=/tmp/ssh-xhQbo29352/agent.29352 +SSH_CLIENT='192.168.123.143 37670 4822' +SSH_CONNECTION='192.168.123.143 37670 192.168.123.8 4822' +SSH_TTY=/dev/pts/0 +TERM=xterm +UID=1000 +USER=freddy +VIM=/home/freddy/.vim +VIMRUNTIME=/usr/share/vim/vimcurrent +_=GPG_AGENT_INFO +bash205='3.2.39(1)-release' +bash205b='3.2.39(1)-release' +bash3='3.2.39(1)-release' +cdots () +{ + [ -d "$1$2" ] && cd "$1$2" || eval cd "$1$2" +} +comp_load () +{ + local cmd=${COMP_WORDS[0]} dir globs OLDIFS=$IFS; + IFS=:; + local -a aPaths=($COMP_PATH); + IFS=' +'; + globs=($( + for dir in "${aPaths[@]}"; do + echo \"$dir\"/complete\*/\*.$cmd + echo \"$dir\"/complete\*/$cmd\! + echo \"$dir\"/complete\*/$cmd + done + )); + IFS=$OLDIFS; + if ! declare -F comp_include >&/dev/null; then + for dir in "${aPaths[@]}"; + do + [ -r "$dir/include/comp_include" ] && . "$dir/include/comp_include" && break; + done; + fi; + comp_include comp_load_init; + comp_load_init; + local script="$(eval find "${globs[@]}" 2> /dev/null | head -1)"; + local link comp=${script##*/}; + [[ ${comp: -1:1} == ! ]] || { + link=${comp#*.}; + comp=${comp%.$link} + }; + local path=${script%/*}; + [ "$script" -a -r "$path/$comp" ] && . "$path/$comp" && declare -F _$comp >&/dev/null && { + [ ${COMP_INSTALL:-1} -eq 0 ] || _comp_install $comp "$path" + } && _$comp $link; + comp_load_deinit +} +nameTerminal () +{ + [ "${TERM:0:5}" = "xterm" ] && local ansiNrTab=0; + [ "$TERM" = "rxvt" ] && local ansiNrTab=61; + [ "$TERM" = "konsole" ] && local ansiNrTab=30 ansiNrWindow=0; + [ $ansiNrTab ] && echo -n ''"]$ansiNrTab;$1"''; + [ $ansiNrWindow -a "$2" ] && echo -n ''"]$ansiNrWindow;$2"'' +} +stoppedjobs () +{ + if [ "$(jobs -s)" ]; then + echo -n "%"; + jobs -s | wc -l; + fi +} diff --git a/test/fixtures/compgen/t2.txt b/test/fixtures/compgen/t2.txt new file mode 100644 index 00000000..371ab2b1 --- /dev/null +++ b/test/fixtures/compgen/t2.txt @@ -0,0 +1,121 @@ +BASH=/bin/bash +BASH_ARGC=() +BASH_ARGV=() +BASH_LINENO=() +BASH_SOURCE=() +BASH_VERSINFO=([0]="3" [1]="2" [2]="39" [3]="1" [4]="release" [5]="i486-pc-linux-gnu") +BASH_VERSION='3.2.39(1)-release' +CDPL_DIRS=([0]="/home/freddy/proj") +CDPM_DIRS= +CDP_DIRS=([0]="/home/freddy/proj" [1]="") +COLUMNS=130 +COMP_CACHE=/home/freddy/.bash_completion_lib.d/cache~ +COMP_DIR=/etc/bash_completion_lib +COMP_PATH=/home/freddy/.bash_completion_lib.d:/etc/bash_completion_lib +COMP_RESTRICT_BY_EXTENSION=0 +COMP_VERSION=bash_completion_lib-1.3.1 +DIRSTACK=() +EDITOR=/usr/bin/vim +EUID=1000 +GPGKEY=10A575C3 +GPG_AGENT_INFO=/tmp/gpg-Pg6JXR/S.gpg-agent:4129:1 +GPG_TTY=/dev/pts/0 +GREP_OPTIONS='--exclude '\''distrib/*'\'' --exclude tags' +GROUPS=() +HISTCONTROL=ignoreboth +HISTFILE=/home/freddy/.bash_history +HISTFILESIZE=500 +HISTIGNORE=exit +HISTSIZE=500 +HOME=/home/freddy +HOSTNAME=blondy +HOSTTYPE=i486 +IFS=$' \t\n' +LANG=en_US +LANGUAGE=en_NL:en_US:en_GB:en +LINES=49 +LOGNAME=freddy +MACHTYPE=i486-pc-linux-gnu +MAIL=/var/mail/freddy +MAILCHECK=60 +OLDPWD=/home/freddy/.bash_completion_lib.d +OPTERR=1 +OPTIND=1 +OSTYPE=linux-gnu +PATH=/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games:/home/freddy/proj/rc/bin +PIPESTATUS=([0]="0") +PPID=29352 +PS1=$'\\[\E[0;34m\\]\\!\\[\E[0m\\]\\[\E[1;32m\\]$(stoppedjobs)\\[\E[0m\\]:\\u@\\h:\\w> \\[\E[m\\]' +PS2='> ' +PS4='+ ' +PWD=/home/freddy/proj/bashCompletion/bash-completion.git/test/fixtures/compgen +SHELL=/bin/bash +SHELLOPTS=braceexpand:hashall:histexpand:interactive-comments:monitor:vi +SHLVL=1 +SSH_AUTH_SOCK=/tmp/ssh-xhQbo29352/agent.29352 +SSH_CLIENT='192.168.123.143 37670 4822' +SSH_CONNECTION='192.168.123.143 37670 192.168.123.8 4822' +SSH_TTY=/dev/pts/0 +TERM=xterm +UID=1000 +USER=freddy +VIM=/home/freddy/.vim +VIMRUNTIME=/usr/share/vim/vimcurrent +_='a\\\'\''b/' +bash205='3.2.39(1)-release' +bash205b='3.2.39(1)-release' +bash3='3.2.39(1)-release' +cdots () +{ + [ -d "$1$2" ] && cd "$1$2" || eval cd "$1$2" +} +comp_load () +{ + local cmd=${COMP_WORDS[0]} dir globs OLDIFS=$IFS; + IFS=:; + local -a aPaths=($COMP_PATH); + IFS=' +'; + globs=($( + for dir in "${aPaths[@]}"; do + echo \"$dir\"/complete\*/\*.$cmd + echo \"$dir\"/complete\*/$cmd\! + echo \"$dir\"/complete\*/$cmd + done + )); + IFS=$OLDIFS; + if ! declare -F comp_include >&/dev/null; then + for dir in "${aPaths[@]}"; + do + [ -r "$dir/include/comp_include" ] && . "$dir/include/comp_include" && break; + done; + fi; + comp_include comp_load_init; + comp_load_init; + local script="$(eval find "${globs[@]}" 2> /dev/null | head -1)"; + local link comp=${script##*/}; + [[ ${comp: -1:1} == ! ]] || { + link=${comp#*.}; + comp=${comp%.$link} + }; + local path=${script%/*}; + [ "$script" -a -r "$path/$comp" ] && . "$path/$comp" && declare -F _$comp >&/dev/null && { + [ ${COMP_INSTALL:-1} -eq 0 ] || _comp_install $comp "$path" + } && _$comp $link; + comp_load_deinit +} +nameTerminal () +{ + [ "${TERM:0:5}" = "xterm" ] && local ansiNrTab=0; + [ "$TERM" = "rxvt" ] && local ansiNrTab=61; + [ "$TERM" = "konsole" ] && local ansiNrTab=30 ansiNrWindow=0; + [ $ansiNrTab ] && echo -n ''"]$ansiNrTab;$1"''; + [ $ansiNrWindow -a "$2" ] && echo -n ''"]$ansiNrWindow;$2"'' +} +stoppedjobs () +{ + if [ "$(jobs -s)" ]; then + echo -n "%"; + jobs -s | wc -l; + fi +} diff --git a/test/fixtures/compgen/t3.txt b/test/fixtures/compgen/t3.txt new file mode 100644 index 00000000..371ab2b1 --- /dev/null +++ b/test/fixtures/compgen/t3.txt @@ -0,0 +1,121 @@ +BASH=/bin/bash +BASH_ARGC=() +BASH_ARGV=() +BASH_LINENO=() +BASH_SOURCE=() +BASH_VERSINFO=([0]="3" [1]="2" [2]="39" [3]="1" [4]="release" [5]="i486-pc-linux-gnu") +BASH_VERSION='3.2.39(1)-release' +CDPL_DIRS=([0]="/home/freddy/proj") +CDPM_DIRS= +CDP_DIRS=([0]="/home/freddy/proj" [1]="") +COLUMNS=130 +COMP_CACHE=/home/freddy/.bash_completion_lib.d/cache~ +COMP_DIR=/etc/bash_completion_lib +COMP_PATH=/home/freddy/.bash_completion_lib.d:/etc/bash_completion_lib +COMP_RESTRICT_BY_EXTENSION=0 +COMP_VERSION=bash_completion_lib-1.3.1 +DIRSTACK=() +EDITOR=/usr/bin/vim +EUID=1000 +GPGKEY=10A575C3 +GPG_AGENT_INFO=/tmp/gpg-Pg6JXR/S.gpg-agent:4129:1 +GPG_TTY=/dev/pts/0 +GREP_OPTIONS='--exclude '\''distrib/*'\'' --exclude tags' +GROUPS=() +HISTCONTROL=ignoreboth +HISTFILE=/home/freddy/.bash_history +HISTFILESIZE=500 +HISTIGNORE=exit +HISTSIZE=500 +HOME=/home/freddy +HOSTNAME=blondy +HOSTTYPE=i486 +IFS=$' \t\n' +LANG=en_US +LANGUAGE=en_NL:en_US:en_GB:en +LINES=49 +LOGNAME=freddy +MACHTYPE=i486-pc-linux-gnu +MAIL=/var/mail/freddy +MAILCHECK=60 +OLDPWD=/home/freddy/.bash_completion_lib.d +OPTERR=1 +OPTIND=1 +OSTYPE=linux-gnu +PATH=/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games:/home/freddy/proj/rc/bin +PIPESTATUS=([0]="0") +PPID=29352 +PS1=$'\\[\E[0;34m\\]\\!\\[\E[0m\\]\\[\E[1;32m\\]$(stoppedjobs)\\[\E[0m\\]:\\u@\\h:\\w> \\[\E[m\\]' +PS2='> ' +PS4='+ ' +PWD=/home/freddy/proj/bashCompletion/bash-completion.git/test/fixtures/compgen +SHELL=/bin/bash +SHELLOPTS=braceexpand:hashall:histexpand:interactive-comments:monitor:vi +SHLVL=1 +SSH_AUTH_SOCK=/tmp/ssh-xhQbo29352/agent.29352 +SSH_CLIENT='192.168.123.143 37670 4822' +SSH_CONNECTION='192.168.123.143 37670 192.168.123.8 4822' +SSH_TTY=/dev/pts/0 +TERM=xterm +UID=1000 +USER=freddy +VIM=/home/freddy/.vim +VIMRUNTIME=/usr/share/vim/vimcurrent +_='a\\\'\''b/' +bash205='3.2.39(1)-release' +bash205b='3.2.39(1)-release' +bash3='3.2.39(1)-release' +cdots () +{ + [ -d "$1$2" ] && cd "$1$2" || eval cd "$1$2" +} +comp_load () +{ + local cmd=${COMP_WORDS[0]} dir globs OLDIFS=$IFS; + IFS=:; + local -a aPaths=($COMP_PATH); + IFS=' +'; + globs=($( + for dir in "${aPaths[@]}"; do + echo \"$dir\"/complete\*/\*.$cmd + echo \"$dir\"/complete\*/$cmd\! + echo \"$dir\"/complete\*/$cmd + done + )); + IFS=$OLDIFS; + if ! declare -F comp_include >&/dev/null; then + for dir in "${aPaths[@]}"; + do + [ -r "$dir/include/comp_include" ] && . "$dir/include/comp_include" && break; + done; + fi; + comp_include comp_load_init; + comp_load_init; + local script="$(eval find "${globs[@]}" 2> /dev/null | head -1)"; + local link comp=${script##*/}; + [[ ${comp: -1:1} == ! ]] || { + link=${comp#*.}; + comp=${comp%.$link} + }; + local path=${script%/*}; + [ "$script" -a -r "$path/$comp" ] && . "$path/$comp" && declare -F _$comp >&/dev/null && { + [ ${COMP_INSTALL:-1} -eq 0 ] || _comp_install $comp "$path" + } && _$comp $link; + comp_load_deinit +} +nameTerminal () +{ + [ "${TERM:0:5}" = "xterm" ] && local ansiNrTab=0; + [ "$TERM" = "rxvt" ] && local ansiNrTab=61; + [ "$TERM" = "konsole" ] && local ansiNrTab=30 ansiNrWindow=0; + [ $ansiNrTab ] && echo -n ''"]$ansiNrTab;$1"''; + [ $ansiNrWindow -a "$2" ] && echo -n ''"]$ansiNrWindow;$2"'' +} +stoppedjobs () +{ + if [ "$(jobs -s)" ]; then + echo -n "%"; + jobs -s | wc -l; + fi +} diff --git a/test/lib/completions/screen.exp b/test/lib/completions/screen.exp index a9d239ea..4bd5ad0a 100644 --- a/test/lib/completions/screen.exp +++ b/test/lib/completions/screen.exp @@ -33,6 +33,11 @@ expect { -re "\r\nbar\\s+bar bar.d\\s+foo\\s+foo.d" { if {[lindex $BASH_VERSINFO 0] < 4} {xfail "$test"} {fail "$test"} } + -re "\r\nbar\\s+bar\\\\ bar.d/\\s+foo\\s+foo.d/" { + # On bash-3, the space in `bar bar.d' is escaped with a backslash + # as a side-effect of emulating `-o filenames'. + if {[lindex $BASH_VERSINFO 0] <= 3} {pass "$test"} {fail "$test"} + } -re $prompt { unresolved "$test at prompt" } default { unresolved "$test" } }; # expect diff --git a/test/unit/_filedir.exp b/test/unit/_filedir.exp new file mode 100644 index 00000000..c5dd877e --- /dev/null +++ b/test/unit/_filedir.exp @@ -0,0 +1,234 @@ +proc setup {} { + assert_bash_exec {unset COMPREPLY cur} + assert_bash_exec {unset -f _f} + save_env + # Declare bash completion function `_f' + assert_bash_exec { \ + _f() { local cur=$(_get_cword); unset COMPREPLY; _filedir; }; \ + complete -F _f f \ + } + # Declare bash completion function `_f2' with `-o filenames' active. + assert_bash_exec { \ + complete -F _f -o filenames f2 \ + } + # Create directory `a*b' + # NOTE: directory `a*b' isn't included in Git, because a directory + # containing an asterisk (*) causes troubles on Cygwin/Windows + assert_bash_exec {(cd fixtures/_filedir && [ ! -d a\*b ] && mkdir a\*b && touch a\*b/j || true)} +}; # setup() + + +proc teardown {} { + assert_bash_exec {(cd fixtures/_filedir && rm -- a\*b/j && rmdir a\*b/ || true)} + assert_bash_exec {unset COMPREPLY cur} + assert_bash_exec {unset -f _f} + assert_bash_exec {complete -r f} + assert_env_unmodified { /OLDPWD/d } +}; # teardown() + + +setup + + +set test "_filedir should run without errors" +assert_bash_exec {_filedir > /dev/null} $test + + +sync_after_int + + +foreach name {f f2} { + + set test "completing $name ab/ should return e" + set cmd "$name ab/" + assert_complete_dir e $cmd "fixtures/_filedir" + + + sync_after_int + + + set test "completing $name a\\ b/ should return i" + set cmd "$name a\\ b/" + assert_complete_dir i $cmd "fixtures/_filedir" + + + sync_after_int + + + set test "completing $name a\\\'b/ should return i" + set cmd "$name a\\\'b/" + assert_complete_dir c $cmd "fixtures/_filedir" + + + sync_after_int + + + set test "completing $name a\\\"b/ should return i"; #" + set cmd "$name a\\\"b/"; #" + assert_complete_dir d $cmd "fixtures/_filedir" + + + sync_after_int + + + set test "completing $name a\\\$b/ should return h" + set cmd "$name a\\\$b/" + assert_complete_dir "\b\b\b\b\b$::TESTDIR/fixtures/_filedir/a\\\\\$b/h" $cmd "fixtures/_filedir" + + + sync_after_int + + + set test "completing $name a\\\\b/ should return g" + set cmd "$name a\\\\b/" + assert_complete_dir g $cmd "fixtures/_filedir" + + + sync_after_int + + + set test "completing $name a\\&b/ should return f" + set cmd "$name a\\&b/" + assert_complete_dir f $cmd "fixtures/_filedir" + + + sync_after_int + + + set test "completing $name a\$ should return a\\\$b/" + set cmd "$name a\$" + assert_complete_dir "\b\\\\\$b/" $cmd "fixtures/_filedir" + + + sync_after_int + + + # NOTE: Bash versions 4.0.0 up to 4.0.34 contain a bug when completing quoted + # words, so tests within this if aren't executed for these bash versions. + if {! ( + [lindex $::BASH_VERSINFO 0] == 4 && + [lindex $::BASH_VERSINFO 1] == 0 && + [lindex $::BASH_VERSINFO 2] < 35 + )} { + set test "completing $name 'ab/ should return e" + set cmd "$name 'ab/" + assert_complete_dir {e'} $cmd "fixtures/_filedir" + + + sync_after_int + + + set test "completing $name 'a b/ should return i" + set cmd "$name 'a b/" + assert_complete_dir {i'} $cmd "fixtures/_filedir" + + + sync_after_int + + + set test "completing $name 'a\"b/ should return d"; #" + set cmd "$name 'a\"b/"; #" + assert_complete_dir {d'} $cmd "fixtures/_filedir" + + + sync_after_int + + + set test "completing $name 'a\$b/ should return h" + set cmd "$name 'a\$b/" + if {[lindex $::BASH_VERSINFO 0] == 4} { + assert_complete_dir {h'} $cmd "fixtures/_filedir" + } else { + assert_complete_dir "\b\b\b\b$::TESTDIR/fixtures/_filedir/a\$b/h'" $cmd "fixtures/_filedir" + }; # if + + + + sync_after_int + + + set test "completing $name 'a\\b/ should return g" + set cmd "$name 'a\\b/" + assert_complete_dir {g'} $cmd "fixtures/_filedir" + + + sync_after_int + + + set test "completing $name 'a&b/ should return f" + set cmd "$name 'a&b/" + assert_complete_dir {f'} $cmd "fixtures/_filedir" + + + sync_after_int + + + set test "completing $name \"ab/ should return e"; #" + set cmd "$name \"ab/"; #" + assert_complete_dir {e"} $cmd "fixtures/_filedir"; #" + + + sync_after_int + + + set test "completing $name \"a b/ should return i"; #" + set cmd "$name \"a b/"; #" + assert_complete_dir {i"} $cmd "fixtures/_filedir"; #" + + + sync_after_int + + + set test "completing $name \"a'b/ should return c"; #" + set cmd "$name \"a'b/"; #" + assert_complete_dir {c"} $cmd "fixtures/_filedir"; #" + + + sync_after_int + + + set test "completing $name \"a\\\"b/ should return d"; #" + set cmd "$name \"a\\\"b/"; #" + assert_complete_dir {d"} $cmd "fixtures/_filedir"; #" + + + sync_after_int + + + set test "completing $name \"a\\\$b/ should return h"; #" + set cmd "$name \"a\\\$b/"; #" + assert_complete_dir "\b\b\b\b\b$::TESTDIR/fixtures/_filedir/a\\\\\$b/h\\\"" $cmd "fixtures/_filedir" + + + sync_after_int + + + set test "completing $name \"a\\b/ should return e"; #" + set cmd "$name \"a\\b/"; #" + assert_complete_dir "\b\b\bb/e\\\"" $cmd "fixtures/_filedir" + + + sync_after_int + + + set test "completing $name \"a\\\\b/ should return g"; #" + set cmd "$name \"a\\\\b/"; #" + assert_complete_dir {g"} $cmd "fixtures/_filedir"; #" + + + sync_after_int + + + set test "completing $name \"a&b/ should return f"; #" + set cmd "$name \"a&b/"; #" + assert_complete_dir {f"} $cmd "fixtures/_filedir"; #" + + + sync_after_int + + + }; # if +}; # for + + +teardown diff --git a/test/unit/_get_cword.exp b/test/unit/_get_cword.exp index 3481b4a5..7e38e9ed 100644 --- a/test/unit/_get_cword.exp +++ b/test/unit/_get_cword.exp @@ -253,4 +253,32 @@ 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; _get_cword} +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 diff --git a/test/unit/compgen.exp b/test/unit/compgen.exp new file mode 100644 index 00000000..8c33c414 --- /dev/null +++ b/test/unit/compgen.exp @@ -0,0 +1,52 @@ +proc setup {} { + save_env +}; # setup() + + +proc teardown {} { + assert_env_unmodified {/OLDPWD/d} +}; # teardown() + + +setup + + +if {[lindex $::BASH_VERSINFO 0] <= 3} { + set test {compgen -f a\\\\\\\'b/ on bash-3 should return a\'b/c}; + set cmd {compgen -f a\\\\\\\'b/} +} else { + set test {compgen -f a\\\'b/ on bash-4 should return a\'b/c}; + set cmd {compgen -f a\\\'b/} +}; # if +set dir fixtures/compgen +set prompt "/$dir/@" +assert_bash_exec "cd $dir" "" $prompt +send "$cmd\r" +expect -ex "$cmd\r\n" +expect { + -re {a\\\'b/c} { + # On bash-3.2, compgen returns inconsequent output + if { + [lindex $::BASH_VERSINFO 0] >= 4 || ( + [lindex $::BASH_VERSINFO 0] == 3 && + [lindex $::BASH_VERSINFO 1] == 2 + ) + } {pass $test} else {fail $test} + } + -re {a'b/c} { + if {[lindex $::BASH_VERSINFO 0] <= 3 } \ + {pass $test} else {fail $test} + } + -re $prompt { pass "$test" } + -re eof { unresolved "eof" } +}; # expect +sync_after_int $prompt +assert_bash_exec "cd \$TESTDIR" + +#assert_bash_list_dir {a\\\'b/c} $cmd fixtures/compgen + + +sync_after_int + + +teardown