diff options
author | David Paleino <dapal@debian.org> | 2011-11-03 12:32:52 +0100 |
---|---|---|
committer | David Paleino <dapal@debian.org> | 2011-11-03 12:32:52 +0100 |
commit | 2c8171c38d87ddef31c92a76547d3fdf773a1337 (patch) | |
tree | 5e720d5a06ead72ed55454bf6647a712a761ed91 /bash_completion | |
parent | 9920a8faedf704420571d8072ccab27e9dac40ba (diff) | |
download | bash-completion-2c8171c38d87ddef31c92a76547d3fdf773a1337.tar.gz |
Imported Upstream version 1.90upstream/1.90
Diffstat (limited to 'bash_completion')
-rw-r--r-- | bash_completion | 936 |
1 files changed, 569 insertions, 367 deletions
diff --git a/bash_completion b/bash_completion index 66019379..62ef87ed 100644 --- a/bash_completion +++ b/bash_completion @@ -1,5 +1,6 @@ +# -*- shell-script -*- # -# bash_completion - programmable completion functions for bash 3.2+ +# bash_completion - programmable completion functions for bash 4.1+ # # Copyright © 2006-2008, Ian Macdonald <ian@caliban.org> # © 2009-2011, Bash Completion Maintainers @@ -17,13 +18,13 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, -# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # The latest version of this software can be obtained here: # # http://bash-completion.alioth.debian.org/ # -# RELEASE: 1.3 +# RELEASE: 1.90 if [[ $- == *v* ]]; then BASH_COMPLETION_ORIGINAL_V_VALUE="-v" @@ -39,21 +40,12 @@ fi # Alter the following to reflect the location of this file. # -[ -n "$BASH_COMPLETION" ] || BASH_COMPLETION=/etc/bash_completion -[ -n "$BASH_COMPLETION_DIR" ] || BASH_COMPLETION_DIR=/etc/bash_completion.d [ -n "$BASH_COMPLETION_COMPAT_DIR" ] || BASH_COMPLETION_COMPAT_DIR=/etc/bash_completion.d -readonly BASH_COMPLETION BASH_COMPLETION_DIR BASH_COMPLETION_COMPAT_DIR +readonly BASH_COMPLETION_COMPAT_DIR -# Set a couple of useful vars +# Blacklisted completions, causing problems with our code. # -UNAME=$( uname -s ) -# strip OS type and version under Cygwin (e.g. CYGWIN_NT-5.1 => Cygwin) -UNAME=${UNAME/CYGWIN_*/Cygwin} - -case ${UNAME} in - Linux|GNU|GNU/*) USERLAND=GNU ;; - *) USERLAND=${UNAME} ;; -esac +_blacklist_glob='@(acroread.sh)' # Turn on extended globbing and programmable completion shopt -s extglob progcomp @@ -64,68 +56,6 @@ shopt -s extglob progcomp # Make directory commands see only directories complete -d pushd -# The following section lists completions that are redefined later -# Do NOT break these over multiple lines. -# -# START exclude -- do NOT remove this line -# bzcmp, bzdiff, bz*grep, bzless, bzmore intentionally not here, see Debian: #455510 -complete -f -X '!*.?(t)bz?(2)' bunzip2 bzcat pbunzip2 pbzcat -complete -f -X '!*.@(zip|[ejw]ar|exe|pk3|wsz|zargo|xpi|sxw|o[tx]t|od[fgpst]|epub|apk)' unzip zipinfo -complete -f -X '*.Z' compress znew -# zcmp, zdiff, z*grep, zless, zmore intentionally not here, see Debian: #455510 -complete -f -X '!*.@(Z|[gGd]z|t[ag]z)' gunzip zcat unpigz -complete -f -X '!*.Z' uncompress -# lzcmp, lzdiff intentionally not here, see Debian: #455510 -complete -f -X '!*.@(tlz|lzma)' lzcat lzegrep lzfgrep lzgrep lzless lzmore unlzma -complete -f -X '!*.@(?(t)xz|tlz|lzma)' unxz xzcat -complete -f -X '!*.lrz' lrunzip -complete -f -X '!*.@(gif|jp?(e)g|miff|tif?(f)|pn[gm]|p[bgp]m|bmp|xpm|ico|xwd|tga|pcx)' ee -complete -f -X '!*.@(gif|jp?(e)g|tif?(f)|png|p[bgp]m|bmp|x[bp]m|rle|rgb|pcx|fits|pm)' xv qiv -complete -f -X '!*.@(@(?(e)ps|?(E)PS|pdf|PDF)?(.gz|.GZ|.bz2|.BZ2|.Z))' gv ggv kghostview -complete -f -X '!*.@(dvi|DVI)?(.@(gz|Z|bz2))' xdvi kdvi -complete -f -X '!*.dvi' dvips dviselect dvitype dvipdf advi dvipdfm dvipdfmx -complete -f -X '!*.[pf]df' acroread gpdf xpdf -complete -f -X '!*.@(?(e)ps|pdf)' kpdf -complete -f -X '!*.@(@(?(e)ps|?(E)PS|[pf]df|[PF]DF|dvi|DVI)?(.gz|.GZ|.bz2|.BZ2)|cb[rz]|djv?(u)|gif|jp?(e)g|miff|tif?(f)|pn[gm]|p[bgp]m|bmp|xpm|ico|xwd|tga|pcx|fdf)' evince -complete -f -X '!*.@(okular|@(?(e|x)ps|?(E|X)PS|pdf|PDF|dvi|DVI|cb[rz]|CB[RZ]|djv?(u)|DJV?(U)|dvi|DVI|gif|jp?(e)g|miff|tif?(f)|pn[gm]|p[bgp]m|bmp|xpm|ico|xwd|tga|pcx|GIF|JP?(E)G|MIFF|TIF?(F)|PN[GM]|P[BGP]M|BMP|XPM|ICO|XWD|TGA|PCX|epub|EPUB|odt|ODT|fb?(2)|FB?(2)|mobi|MOBI|g3|G3|chm|CHM|fdf|FDF)?(.?(gz|GZ|bz2|BZ2)))' okular -complete -f -X '!*.@(?(e)ps|pdf)' ps2pdf ps2pdf12 ps2pdf13 ps2pdf14 ps2pdfwr -complete -f -X '!*.texi*' makeinfo texi2html -complete -f -X '!*.@(?(la)tex|texi|dtx|ins|ltx)' tex latex slitex jadetex pdfjadetex pdftex pdflatex texi2dvi -complete -f -X '!*.mp3' mpg123 mpg321 madplay -complete -f -X '!*@(.@(mp?(e)g|MP?(E)G|wma|avi|AVI|asf|vob|VOB|bin|dat|divx|DIVX|vcd|ps|pes|fli|flv|FLV|fxm|FXM|viv|rm|ram|yuv|mov|MOV|qt|QT|wmv|mp[234]|MP[234]|m4[pv]|M4[PV]|mkv|MKV|og[gmv]|OG[GMV]|t[ps]|T[PS]|m2t?(s)|M2T?(S)|wav|WAV|flac|FLAC|asx|ASX|mng|MNG|srt|m[eo]d|M[EO]D|s[3t]m|S[3T]M|it|IT|xm|XM)|+([0-9]).@(vdr|VDR))?(.part)' xine aaxine fbxine -complete -f -X '!*@(.@(mp?(e)g|MP?(E)G|wma|avi|AVI|asf|vob|VOB|bin|dat|divx|DIVX|vcd|ps|pes|fli|flv|FLV|fxm|FXM|viv|rm|ram|yuv|mov|MOV|qt|QT|wmv|mp[234]|MP[234]|m4[pv]|M4[PV]|mkv|MKV|og[gmv]|OG[GMV]|t[ps]|T[PS]|m2t?(s)|M2T?(S)|wav|WAV|flac|FLAC|asx|ASX|mng|MNG|srt|m[eo]d|M[EO]D|s[3t]m|S[3T]M|it|IT|xm|XM|iso|ISO)|+([0-9]).@(vdr|VDR))?(.part)' kaffeine dragon -complete -f -X '!*.@(avi|asf|wmv)' aviplay -complete -f -X '!*.@(rm?(j)|ra?(m)|smi?(l))' realplay -complete -f -X '!*.@(mpg|mpeg|avi|mov|qt)' xanim -complete -f -X '!*.@(ogg|m3u|flac|spx)' ogg123 -complete -f -X '!*.@(mp3|ogg|pls|m3u)' gqmpeg freeamp -complete -f -X '!*.fig' xfig -complete -f -X '!*.@(mid?(i)|cmf)' playmidi -complete -f -X '!*.@(mid?(i)|rmi|rcp|[gr]36|g18|mod|xm|it|x3m|s[3t]m|kar)' timidity -complete -f -X '!*.@(m[eo]d|s[3t]m|xm|it)' modplugplay modplug123 -complete -f -X '*.@(o|so|so.!(conf)|a|[rs]pm|gif|jp?(e)g|mp3|mp?(e)g|avi|asf|ogg|class)' vi vim gvim rvim view rview rgvim rgview gview emacs xemacs sxemacs kate kwrite -complete -f -X '!*.@([eE][xX][eE]?(.[sS][oO])|[cC][oO][mM]|[sS][cC][rR])' wine -complete -f -X '!*.@(zip|z|gz|tgz)' bzme -# konqueror not here on purpose, it's more than a web/html browser -complete -f -X '!*.@(?([xX]|[sS])[hH][tT][mM]?([lL]))' netscape mozilla lynx opera galeon dillo elinks amaya firefox mozilla-firefox iceweasel google-chrome chromium-browser epiphany -complete -f -X '!*.@(sxw|stw|sxg|sgl|doc?([mx])|dot?([mx])|rtf|txt|htm|html|odt|ott|odm)' oowriter -complete -f -X '!*.@(sxi|sti|pps?(x)|ppt?([mx])|pot?([mx])|odp|otp)' ooimpress -complete -f -X '!*.@(sxc|stc|xls?([bmx])|xlw|xlt?([mx])|[ct]sv|ods|ots)' oocalc -complete -f -X '!*.@(sxd|std|sda|sdd|odg|otg)' oodraw -complete -f -X '!*.@(sxm|smf|mml|odf)' oomath -complete -f -X '!*.odb' oobase -complete -f -X '!*.[rs]pm' rpm2cpio -complete -f -X '!*.aux' bibtex -complete -f -X '!*.po' poedit gtranslator kbabel lokalize -complete -f -X '!*.@([Pp][Rr][Gg]|[Cc][Ll][Pp])' harbour gharbour hbpp -complete -f -X '!*.[Hh][Rr][Bb]' hbrun -complete -f -X '!*.ly' lilypond ly2dvi -complete -f -X '!*.@(dif?(f)|?(d)patch)?(.@([gx]z|bz2|lzma))' cdiff -complete -f -X '!*.lyx' lyx -complete -f -X '!@(*.@(ks|jks|jceks|p12|pfx|bks|ubr|gkr|cer|crt|cert|p7b|pkipath|pem|p10|csr|crl)|cacerts)' portecle -complete -f -X '!*.@(mp[234c]|og[ag]|@(fl|a)ac|m4[abp]|spx|tta|w?(a)v|wma|aif?(f)|asf|ape)' kid3 kid3-qt -# FINISH exclude -- do not remove this line - # start of section containing compspecs that can be handled within bash # user commands see only users @@ -163,16 +93,31 @@ complete -b builtin # start of section containing completion functions called by other functions +# Check if we're running on the given userland +# @param $1 userland to check for +_userland() +{ + local userland=$( uname -s ) + [[ $userland == @(Linux|GNU/*) ]] && userland=GNU + [[ $userland == $1 ]] +} + # This function checks whether we have a given program on the system. -# No need for bulky functions in memory if we don't. # -have() +_have() { - unset -v have # Completions for system administrator commands are installed as well in # case completion is attempted via `sudo command ...'. - PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin type $1 &>/dev/null && - have="yes" + PATH=$PATH:/usr/sbin:/sbin:/usr/local/sbin type $1 &>/dev/null +} + +# Backwards compatibility for compat completions that use have(). +# @deprecated should no longer be used; generally not needed with dynamically +# loaded completions, and _have is suitable for runtime use. +have() +{ + unset -v have + _have $1 && have=yes } # This function checks whether a given readline variable @@ -214,7 +159,8 @@ dequote() # use multiple '_upvar' calls, since one '_upvar' call might # reassign a variable to be used by another '_upvar' call. # See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference -_upvar() { +_upvar() +{ if unset -v "$1"; then # Unset & validate varname if (( $# == 2 )); then eval $1=\"\$2\" # Return single value @@ -233,7 +179,8 @@ _upvar() { # -v Assign single value to varname # Return: 1 if error occurs # See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference -_upvars() { +_upvars() +{ if ! (( $# )); then echo "${FUNCNAME[0]}: usage: ${FUNCNAME[0]} [-v varname"\ "value] | [-aN varname [value ...]] ..." 1>&2 @@ -276,8 +223,9 @@ _upvars() { # @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 +__reassemble_comp_words_by_ref() +{ + local exclude i j line ref # Exclude word separator characters? if [[ $1 ]]; then # Yes, exclude word separator characters; @@ -290,26 +238,40 @@ __reassemble_comp_words_by_ref() { # Are characters excluded which were former included? if [[ $exclude ]]; then # Yes, list of word completion separators has shrunk; + line=$COMP_LINE # 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 not - # empty and is word made up of just word separator characters to be - # excluded? + # empty and is word made up of just word separator characters to + # be excluded and is current word not preceded by whitespace in + # original line? while [[ $i -gt 0 && ${COMP_WORDS[$i]} && - ${COMP_WORDS[$i]//[^$exclude]} == ${COMP_WORDS[$i]} + ${COMP_WORDS[$i]//[^$exclude]} == ${COMP_WORDS[$i]} ]]; do - [ $j -ge 2 ] && ((j--)) - # Append word separator to current word + # Is word separator not preceded by whitespace in original line + # and are we not going to append to word 0 (the command + # itself), then append to current word. + [[ ${line:0:1} != ' ' && ${line:0:1} != $'\t' ]] && + (( j >= 2 )) && ((j--)) + # Append word separator to current or new 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 + # Remove optional whitespace + word separator from line copy + line=${line#*"${COMP_WORDS[$i]}"} + # Start new word if word separator in original line is + # followed by whitespace. + [[ ${line:0:1} == ' ' || ${line:0:1} == $'\t' ]] && ((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]} + # Remove optional whitespace + word from line copy + line=${line#*"${COMP_WORDS[i]}"} # Indicate new cword [[ $i == $COMP_CWORD ]] && eval $3=$j done @@ -323,51 +285,50 @@ __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. +# colon (:) as $1 in this case. # @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() { +# @see __reassemble_comp_words_by_ref() +__get_cword_at_cursor_by_ref() +{ local cword words=() __reassemble_comp_words_by_ref "$1" words cword - local i cur2 - 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 + local i cur cur2 index=$COMP_POINT lead=${COMP_LINE:0:$COMP_POINT} + # Cursor not at position 0 and not leaded by just space(s)? + if [[ $index -gt 0 && ( $lead && ${lead//[[:space:]]} ) ]]; then + cur=$COMP_LINE + 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 - if [[ "${words[cword]:0:${#cur}}" != "$cur" ]]; then - # We messed up. At least return the whole word so things keep working - cur2=${words[cword]} - else - cur2=${cur:0:$index} + # Does found word match 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 + # Clear $cur if just space(s) + [[ $cur && ! ${cur//[[:space:]]} ]] && cur= + # Zero $index if negative + [[ $index -lt 0 ]] && index=0 fi - local "$2" "$3" "$4" && - _upvars -a${#words[@]} $2 "${words[@]}" -v $3 "$cword" -v $4 "$cur2" + local "$2" "$3" "$4" && _upvars -a${#words[@]} $2 "${words[@]}" \ + -v $3 "$cword" -v $4 "${cur:0:$index}" } @@ -388,9 +349,7 @@ __get_cword_at_cursor_by_ref() { # -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. +# would pass the colon (:) as -n option in this case. # -c VARNAME Return cur via $VARNAME # -p VARNAME Return prev via $VARNAME # -w VARNAME Return words via $VARNAME @@ -431,7 +390,7 @@ _get_comp_words_by_ref() [[ $vcur ]] && { upvars+=("$vcur" ); upargs+=(-v $vcur "$cur" ); } [[ $vcword ]] && { upvars+=("$vcword"); upargs+=(-v $vcword "$cword"); } - [[ $vprev ]] && { upvars+=("$vprev" ); upargs+=(-v $vprev + [[ $vprev && $cword -ge 1 ]] && { upvars+=("$vprev" ); upargs+=(-v $vprev "${words[cword - 1]}"); } [[ $vwords ]] && { upvars+=("$vwords"); upargs+=(-a${#words[@]} $vwords "${words[@]}"); } @@ -448,8 +407,7 @@ _get_comp_words_by_ref() # @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. +# colon (:) as $1 in this case. # @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_cword "=:" 1' returns the word left of @@ -515,14 +473,14 @@ _get_cword() _get_pword() { if [ $COMP_CWORD -ge 1 ]; then - _get_cword "${@:-}" 1; + _get_cword "${@:-}" 1 fi } # If the word-to-complete contains a colon (:), left-trim COMPREPLY items with # word-to-complete. -# On bash-3, and bash-4 with a colon in COMP_WORDBREAKS, words containing +# With a colon in COMP_WORDBREAKS, words containing # 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. @@ -537,16 +495,9 @@ _get_pword() # @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, - # or bash-version >= 4 and COMP_WORDBREAKS contains a colon - if [[ - "$1" == *:* && ( - ${BASH_VERSINFO[0]} -lt 4 || - (${BASH_VERSINFO[0]} -ge 4 && "$COMP_WORDBREAKS" == *:*) - ) - ]]; then +__ltrim_colon_completions() +{ + if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then # Remove colon-word prefix from COMPREPLY items local colon_word=${1%${1##*:}} local i=${#COMPREPLY[*]} @@ -564,20 +515,8 @@ __ltrim_colon_completions() { # $ ls "a'b/" # c # $ compgen -f "a'b/" # Wrong, doesn't return output -# $ compgen -f "a\'b/" # Good (bash-4) +# $ compgen -f "a\'b/" # Good # a\'b/c -# $ compgen -f "a\\\\\'b/" # Good (bash-3) -# a\'b/c -# -# 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/<TAB> # Becomes: foo 'a&b/f' -# $ foo a'&b/<TAB> # Nothing happens # # See also: # - http://lists.gnu.org/archive/html/bug-bash/2009-03/msg00155.html @@ -588,17 +527,8 @@ __ltrim_colon_completions() { _quote_readline_by_ref() { if [[ ${1:0:1} == "'" ]]; then - if [[ ${BASH_VERSINFO[0]} -ge 4 ]]; then - # Leave out first character - printf -v $2 %s "${1:1}" - else - # Quote word, leaving out first character - printf -v $2 %q "${1:1}" - # Double-quote word (bash-3) - printf -v $2 %q ${!2} - fi - elif [[ ${BASH_VERSINFO[0]} -le 3 && ${1:0:1} == '"' ]]; then - printf -v $2 %q "${1:1}" + # Leave out first character + printf -v $2 %s "${1:1}" else printf -v $2 %q "$1" fi @@ -610,19 +540,6 @@ _quote_readline_by_ref() } # _quote_readline_by_ref() -# This function turns on "-o filenames" behavior dynamically. It is present -# for bash < 4 reasons. See http://bugs.debian.org/272660#64 for info about -# the bash < 4 compgen hack. -_compopt_o_filenames() -{ - # We test for compopt availability first because directly invoking it on - # bash < 4 at this point may cause terminal echo to be turned off for some - # reason, see https://bugzilla.redhat.com/653669 for more info. - type compopt &>/dev/null && compopt -o filenames 2>/dev/null || \ - compgen -f /non-existing-dir/ >/dev/null -} - - # This function performs file and directory completion. It's better than # simply using 'compgen -f', because it honours spaces in filenames. # @param $1 If `-d', complete only on directories. Otherwise filter/pick only @@ -639,7 +556,7 @@ _filedir() local quoted tmp _quote_readline_by_ref "$cur" quoted - toks=( ${toks[@]-} $( + toks=( $( compgen -d -- "$quoted" | { while read -r tmp; do # TODO: I have removed a "[ -n $tmp ] &&" before 'printf ..', @@ -654,19 +571,26 @@ _filedir() if [[ "$1" != -d ]]; then # Munge xspec to contain uppercase version too - [[ ${BASH_VERSINFO[0]} -ge 4 ]] && \ - xspec=${1:+"!*.@($1|${1^^})"} || \ - xspec=${1:+"!*.@($1|$(printf %s $1 | tr '[:lower:]' '[:upper:]'))"} - toks=( ${toks[@]-} $( compgen -f -X "$xspec" -- $quoted) ) + # http://thread.gmane.org/gmane.comp.shells.bash.bugs/15294/focus=15306 + xspec=${1:+"!*.@($1|${1^^})"} + toks+=( $( compgen -f -X "$xspec" -- $quoted ) ) fi - [ ${#toks[@]} -ne 0 ] && _compopt_o_filenames - COMPREPLY=( "${COMPREPLY[@]}" "${toks[@]}" ) + # If the filter failed to produce anything, try without it if configured to + [[ -n ${COMP_FILEDIR_FALLBACK:-} && \ + -n "$1" && "$1" != -d && ${#toks[@]} -lt 1 ]] && \ + toks+=( $( compgen -f -- $quoted ) ) + + [ ${#toks[@]} -ne 0 ] && compopt -o filenames 2>/dev/null + + COMPREPLY+=( "${toks[@]}" ) } # _filedir() # This function splits $cur=--foo=bar into $prev=--foo, $cur=bar, making it # easier to support both "--foo bar" and "--foo=bar" style completions. +# `=' should have been removed from COMP_WORDBREAKS when setting $cur for +# this to be useful. # Returns 0 if current option was split, 1 otherwise. # _split_longopt() @@ -682,31 +606,186 @@ _split_longopt() return 1 } -# This function tries to parse the help output of the given command. +# Initialize completion and deal with various general things: do file +# and variable completion where appropriate, and adjust prev, words, +# and cword as if no redirections exist so that completions do not +# need to deal with them. Before calling this function, make sure +# cur, prev, words, and cword are local, ditto split if you use -s. +# +# Options: +# -n EXCLUDE Passed to _get_comp_words_by_ref -n with redirection chars +# -e XSPEC Passed to _filedir as first arg for stderr redirections +# -o XSPEC Passed to _filedir as first arg for other output redirections +# -i XSPEC Passed to _filedir as first arg for stdin redirections +# -s Split long options with _split_longopt, implies -n = +# @return True (0) if completion needs further processing, +# False (> 0) no further processing is necessary. +# +_init_completion() +{ + local exclude flag outx errx inx OPTIND=1 + + while getopts "n:e:o:i:s" flag "$@"; do + case $flag in + n) exclude+=$OPTARG ;; + e) errx=$OPTARG ;; + o) outx=$OPTARG ;; + i) inx=$OPTARG ;; + s) split=false ; exclude+== ;; + esac + done + + # For some reason completion functions are not invoked at all by + # bash (at least as of 4.1.7) after the command line contains an + # ampersand so we don't get a chance to deal with redirections + # containing them, but if we did, hopefully the below would also + # do the right thing with them... + + COMPREPLY=() + local redir="@(?([0-9])<|?([0-9&])>?(>)|>&)" + _get_comp_words_by_ref -n "$exclude<>&" cur prev words cword + + # Complete variable names. + if [[ $cur =~ ^(\$\{?)([A-Za-z0-9_]*)$ ]]; then + [[ $cur == *{* ]] && local suffix=} || local suffix= + COMPREPLY=( $( compgen -P ${BASH_REMATCH[1]} -S "$suffix" -v -- \ + "${BASH_REMATCH[2]}" ) ) + return 1 + fi + + # Complete on files if current is a redirect possibly followed by a + # filename, e.g. ">foo", or previous is a "bare" redirect, e.g. ">". + if [[ $cur == $redir* || $prev == $redir ]]; then + local xspec + case $cur in + 2'>'*) xspec=$errx ;; + *'>'*) xspec=$outx ;; + *'<'*) xspec=$inx ;; + *) + case $prev in + 2'>'*) xspec=$errx ;; + *'>'*) xspec=$outx ;; + *'<'*) xspec=$inx ;; + esac + ;; + esac + cur="${cur##$redir}" + _filedir $xspec + return 1 + fi + + # Remove all redirections so completions don't have to deal with them. + local i skip + for (( i=1; i < ${#words[@]}; )); do + if [[ ${words[i]} == $redir* ]]; then + # If "bare" redirect, remove also the next word (skip=2). + [[ ${words[i]} == $redir ]] && skip=2 || skip=1 + words=( "${words[@]:0:i}" "${words[@]:i+skip}" ) + [[ $i -le $cword ]] && cword=$(( cword - skip )) + else + i=$(( ++i )) + fi + done + + [[ $cword -eq 0 ]] && return 1 + prev=${words[cword-1]} + + [[ $split ]] && _split_longopt && split=true + + return 0 +} + +# Helper function for _parse_help and _parse_usage. +__parse_options() +{ + local option option2 i IFS=$' \t\n,/|' + + # Take first found long option, or first one (short) if not found. + option= + for i in $1; do + case $i in + ---*) break ;; + --?*) option=$i ; break ;; + -?*) [[ $option ]] || option=$i ;; + *) break ;; + esac + done + [[ $option ]] || return 0 + + IFS=$' \t\n' # affects parsing of the regexps below... + + # Expand --[no]foo to --foo and --nofoo etc + if [[ $option =~ (\[((no|dont)-?)\]). ]]; then + option2=${option/"${BASH_REMATCH[1]}"/} + option2=${option2%%[<{().[]*} + printf '%s\n' "${option2/=*/=}" + option=${option/"${BASH_REMATCH[1]}"/"${BASH_REMATCH[2]}"} + fi + + option=${option%%[<{().[]*} + printf '%s\n' "${option/=*/=}" +} + +# Parse GNU style help output of the given command. # @param $1 command # @param $2 command options (default: --help) # -_parse_help() { - $1 ${2:---help} 2>&1 | sed -e '/^[[:space:]]*-/!d' -e 's|[,/]| |g' | \ - awk '{ print $1; if ($2 ~ /^-/) { print $2 } }' | sed -e 's|[<=].*||' +_parse_help() +{ + eval local cmd=$1 + local line + "$cmd" ${2:---help} 2>&1 | while read -r line; do + + [[ $line == *([ $'\t'])-* ]] || continue + # transform "-f FOO, --foo=FOO" to "-f , --foo=FOO" etc + while [[ $line =~ \ + ((^|[^-])-[A-Za-z0-9?][[:space:]]+)\[?[A-Z0-9]+\]? ]]; do + line=${line/"${BASH_REMATCH[0]}"/"${BASH_REMATCH[1]}"} + done + __parse_options "${line// or /, }" + + done } -# This function completes on signal names +# Parse BSD style usage output (options in brackets) of the given command. +# @param $1 command +# @param $2 command options (default: --usage) # -_signals() +_parse_usage() { - local i + eval local cmd=$1 + local line match option i char + "$cmd" ${2:---usage} 2>&1 | while read -r line; do + + while [[ $line =~ \[[[:space:]]*(-[^]]+)[[:space:]]*\] ]]; do + match=${BASH_REMATCH[0]} + option=${BASH_REMATCH[1]} + case $option in + -?(\[)+([a-zA-Z0-9?])) + # Treat as bundled short options + for (( i=1; i < ${#option}; i++ )); do + char=${option:i:1} + [[ $char != '[' ]] && printf '%s\n' -$char + done + ;; + *) + __parse_options "$option" + ;; + esac + line=${line#*"$match"} + done - # standard signal completion is rather braindead, so we need - # to hack around to get what we want here, which is to - # complete on a dash, followed by the signal name minus - # the SIG prefix - COMPREPLY=( $( compgen -A signal SIG${cur#-} )) - for (( i=0; i < ${#COMPREPLY[@]}; i++ )); do - COMPREPLY[i]=-${COMPREPLY[i]#SIG} done } +# This function completes on signal names (minus the SIG prefix) +# @param $1 prefix +_signals() +{ + local -a sigs=( $( compgen -P "$1" -A signal "SIG${cur#$1}" ) ) + COMPREPLY+=( "${sigs[@]/#${1}SIG/${1}}" ) +} + # This function completes on known mac addresses # _mac_addresses() @@ -715,16 +794,16 @@ _mac_addresses() local PATH="$PATH:/sbin:/usr/sbin" # Local interfaces (Linux only?) - COMPREPLY=( "${COMPREPLY[@]}" $( ifconfig -a 2>/dev/null | sed -ne \ + COMPREPLY+=( $( ifconfig -a 2>/dev/null | sed -ne \ "s/.*[[:space:]]HWaddr[[:space:]]\{1,\}\($re\)[[:space:]]*$/\1/p" ) ) # ARP cache - COMPREPLY=( "${COMPREPLY[@]}" $( arp -an 2>/dev/null | sed -ne \ + COMPREPLY+=( $( arp -an 2>/dev/null | sed -ne \ "s/.*[[:space:]]\($re\)[[:space:]].*/\1/p" -ne \ "s/.*[[:space:]]\($re\)[[:space:]]*$/\1/p" ) ) # /etc/ethers - COMPREPLY=( "${COMPREPLY[@]}" $( sed -ne \ + COMPREPLY+=( $( sed -ne \ "s/^[[:space:]]*\($re\)[[:space:]].*/\1/p" /etc/ethers 2>/dev/null ) ) COMPREPLY=( $( compgen -W '${COMPREPLY[@]}' -- "$cur" ) ) @@ -757,6 +836,17 @@ _configured_interfaces() fi } +# Local IP addresses. +# +_ip_addresses() +{ + COMPREPLY+=( $( compgen -W \ + "$( PATH="$PATH:/sbin" ifconfig -a | + sed -ne 's/.*addr:\([^[:space:]]*\).*/\1/p' \ + -ne 's/.*inet[[:space:]]\{1,\}\([^[:space:]]*\).*/\1/p' )" \ + -- "$cur" ) ) +} + # This function completes on available kernels # _kernel_versions() @@ -790,14 +880,14 @@ _available_interfaces() # @return True (0) if completion needs further processing, # False (> 0) if tilde is followed by a valid username, completions # are put in COMPREPLY and no further processing is necessary. -_tilde() { +_tilde() +{ local result=0 - # Does $1 start with tilde (~) and doesn't contain slash (/)? - if [[ ${1:0:1} == "~" && $1 == ${1//\/} ]]; then - _compopt_o_filenames - # Try generate username completions + if [[ $1 == ~* && $1 != */* ]]; then + # Try generate ~username completions COMPREPLY=( $( compgen -P '~' -u "${1#\~}" ) ) result=${#COMPREPLY[@]} + [ $result -gt 0 ] && compopt -o filenames 2>/dev/null fi return $result } @@ -825,7 +915,8 @@ _tilde() { # ~foo/* /home/foo/* # # @param $1 Name of variable (not the value of the variable) to expand -__expand_tilde_by_ref() { +__expand_tilde_by_ref() +{ # Does $1 start with tilde (~)? if [ "${!1:0:1}" = "~" ]; then # Does $1 contain slash (/)? @@ -850,7 +941,7 @@ __expand_tilde_by_ref() { _expand() { # FIXME: Why was this here? - #[ "$cur" != "${cur%\\}" ] && cur="$cur\\" + #[ "$cur" != "${cur%\\}" ] && cur+="\\" # Expand ~username type directory specifications. We want to expand # ~foo/... to /home/foo/... to avoid problems when $cur starting with @@ -868,7 +959,7 @@ _expand() # This function completes on process IDs. # AIX and Solaris ps prefers X/Open syntax. -[[ $UNAME == SunOS || $UNAME == AIX ]] && +[[ $OSTYPE == *@(solaris|aix)* ]] && _pids() { COMPREPLY=( $( compgen -W '$( command ps -efo pid | sed 1d )' -- "$cur" )) @@ -880,7 +971,7 @@ _pids() # This function completes on process group IDs. # AIX and SunOS prefer X/Open, all else should be BSD. -[[ $UNAME == SunOS || $UNAME == AIX ]] && +[[ $OSTYPE == *@(solaris|aix)* ]] && _pgids() { COMPREPLY=( $( compgen -W '$( command ps -efo pgid | sed 1d )' -- "$cur" )) @@ -892,7 +983,7 @@ _pgids() # This function completes on process names. # AIX and SunOS prefer X/Open, all else should be BSD. -[[ $UNAME == SunOS || $UNAME == AIX ]] && +[[ $OSTYPE == *@(solaris|aix)* ]] && _pnames() { COMPREPLY=( $( compgen -X '<defunct>' -W '$( command ps -efo comm | \ @@ -942,6 +1033,10 @@ _gids() fi } +# Glob for matching various backup files. +# +_backup_glob='@(#*#|*@(~|.@(bak|orig|rej|swp|dpkg*|rpm@(orig|new|save))))' + # This function completes on services # _services() @@ -949,14 +1044,20 @@ _services() local sysvdir famdir [ -d /etc/rc.d/init.d ] && sysvdir=/etc/rc.d/init.d || sysvdir=/etc/init.d famdir=/etc/xinetd.d - COMPREPLY=( $( printf '%s\n' \ - $sysvdir/!(*.rpm@(orig|new|save)|*~|functions) ) ) + + local restore_nullglob=$(shopt -p nullglob); shopt -s nullglob + + COMPREPLY=( $( printf '%s\n' $sysvdir/!($_backup_glob|functions) ) ) if [ -d $famdir ]; then - COMPREPLY=( "${COMPREPLY[@]}" $( printf '%s\n' \ - $famdir/!(*.rpm@(orig|new|save)|*~) ) ) + COMPREPLY+=( $( printf '%s\n' $famdir/!($_backup_glob) ) ) fi + $restore_nullglob + + COMPREPLY+=( $( systemctl list-units --full --all 2>/dev/null | \ + awk '$1 ~ /\.service$/ { sub("\\.service$", "", $1); print $1 }' ) ) + COMPREPLY=( $( compgen -W '${COMPREPLY[@]#@($sysvdir|$famdir)/}' -- "$cur" ) ) } @@ -1059,9 +1160,10 @@ _allowed_groups() # _shells() { - COMPREPLY=( "${COMPREPLY[@]}" $( compgen -W \ - '$( command grep "^[[:space:]]*/" /etc/shells 2>/dev/null )' \ - -- "$cur" ) ) + local shell rest + while read -r shell rest; do + [[ $shell == /* && $shell == "$cur"* ]] && COMPREPLY+=( $shell ) + done 2>/dev/null < /etc/shells } # This function completes on valid filesystem types @@ -1084,7 +1186,7 @@ _fstypes() fi [ -n "$fss" ] && \ - COMPREPLY=( "${COMPREPLY[@]}" $( compgen -W "$fss" -- "$cur" ) ) + COMPREPLY+=( $( compgen -W "$fss" -- "$cur" ) ) } # Get real command. @@ -1140,7 +1242,7 @@ _count_args() # _pci_ids() { - COMPREPLY=( ${COMPREPLY[@]:-} $( compgen -W \ + COMPREPLY+=( $( compgen -W \ "$( PATH="$PATH:/sbin" lspci -n | awk '{print $3}')" -- "$cur" ) ) } @@ -1148,38 +1250,43 @@ _pci_ids() # _usb_ids() { - COMPREPLY=( ${COMPREPLY[@]:-} $( compgen -W \ + COMPREPLY+=( $( compgen -W \ "$( PATH="$PATH:/sbin" lsusb | awk '{print $6}' )" -- "$cur" ) ) } # CD device names _cd_devices() { - COMPREPLY=( "${COMPREPLY[@]}" - $( compgen -f -d -X "!*/?([amrs])cd*" -- "${cur:-/dev/}" ) ) + COMPREPLY+=( $( compgen -f -d -X "!*/?([amrs])cd*" -- "${cur:-/dev/}" ) ) } # DVD device names _dvd_devices() { - COMPREPLY=( "${COMPREPLY[@]}" - $( compgen -f -d -X "!*/?(r)dvd*" -- "${cur:-/dev/}" ) ) + COMPREPLY+=( $( compgen -f -d -X "!*/?(r)dvd*" -- "${cur:-/dev/}" ) ) } -# start of section containing completion functions for external programs +# TERM environment variable values +_terms() +{ + COMPREPLY+=( $( compgen -W \ + "$( sed -ne 's/^\([^[:space:]#|]\{2,\}\)|.*/\1/p' /etc/termcap \ + 2>/dev/null )" -- "$cur" ) ) + COMPREPLY+=( $( compgen -W "$( { toe -a 2>/dev/null || toe 2>/dev/null; } \ + | awk '{ print $1 }' | sort -u )" -- "$cur" ) ) +} # a little help for FreeBSD ports users -[ $UNAME = FreeBSD ] && complete -W 'index search fetch fetch-list extract \ - patch configure build install reinstall deinstall clean clean-depends \ - kernel buildworld' make +[[ $OSTYPE == *freebsd* ]] && complete -W 'index search fetch fetch-list + extract patch configure build install reinstall deinstall clean + clean-depends kernel buildworld' make # This function provides simple user@host completion # -_user_at_host() { - local cur - - COMPREPLY=() - _get_comp_words_by_ref -n : cur +_user_at_host() +{ + local cur prev words cword + _init_completion -n : || return if [[ $cur == *@* ]]; then _known_hosts_real "$cur" @@ -1195,14 +1302,15 @@ shopt -u hostcomplete && complete -F _user_at_host -o nospace talk ytalk finger # `_known_hosts_real' instead. _known_hosts() { - local options - COMPREPLY=() + local cur prev words cword + _init_completion -n : || return # NOTE: Using `_known_hosts' as a helper function and passing options # to `_known_hosts' is deprecated: Use `_known_hosts_real' instead. + local options [[ "$1" == -a || "$2" == -a ]] && options=-a - [[ "$1" == -c || "$2" == -c ]] && options="$options -c" - _known_hosts_real $options "$(_get_cword :)" + [[ "$1" == -c || "$2" == -c ]] && options+=" -c" + _known_hosts_real $options -- "$cur" } # _known_hosts() # Helper function for completing _known_hosts. @@ -1243,11 +1351,11 @@ _known_hosts_real() # ssh config files if [ -n "$configfile" ]; then [ -r "$configfile" ] && - config=( "${config[@]}" "$configfile" ) + config+=( "$configfile" ) else for i in /etc/ssh/ssh_config "${HOME}/.ssh/config" \ "${HOME}/.ssh2/config"; do - [ -r $i ] && config=( "${config[@]}" "$i" ) + [ -r $i ] && config+=( "$i" ) done fi @@ -1265,7 +1373,7 @@ _known_hosts_real() i=${i//\"} # Eval/expand possible `~' or `~user' __expand_tilde_by_ref i - [ -r "$i" ] && kh=( "${kh[@]}" "$i" ) + [ -r "$i" ] && kh+=( "$i" ) done IFS=$OIFS fi @@ -1275,10 +1383,10 @@ _known_hosts_real() for i in /etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2 \ /etc/known_hosts /etc/known_hosts2 ~/.ssh/known_hosts \ ~/.ssh/known_hosts2; do - [ -r $i ] && kh=( "${kh[@]}" $i ) + [ -r $i ] && kh+=( $i ) done for i in /etc/ssh2/knownhosts ~/.ssh2/hostkeys; do - [ -d $i ] && khd=( "${khd[@]}" $i/*pub ) + [ -d $i ] && khd+=( $i/*pub ) done fi @@ -1305,7 +1413,7 @@ _known_hosts_real() if [ ${#kh[@]} -gt 0 ]; then # FS needs to look for a comma separated list - COMPREPLY=( "${COMPREPLY[@]}" $( awk 'BEGIN {FS=","} + COMPREPLY+=( $( awk 'BEGIN {FS=","} /^\s*[^|\#]/ {for (i=1; i<=2; ++i) { \ sub(" .*$", "", $i); \ sub("^\\[", "", $i); sub("\\](:[0-9]+)?$", "", $i); \ @@ -1321,7 +1429,7 @@ _known_hosts_real() if [[ "$i" == *key_22_$curd*.pub && -r "$i" ]]; then host=${i/#*key_22_/} host=${host/%.pub/} - COMPREPLY=( "${COMPREPLY[@]}" $host ) + COMPREPLY+=( $host ) fi done fi @@ -1335,7 +1443,7 @@ _known_hosts_real() # append any available aliases from config files if [[ ${#config[@]} -gt 0 && -n "$aliases" ]]; then local hosts=$( sed -ne 's/^[ \t]*[Hh][Oo][Ss][Tt]\([Nn][Aa][Mm][Ee]\)\{0,1\}['"$'\t '"']\{1,\}\([^#*?]*\)\(#.*\)\{0,1\}$/\2/p' "${config[@]}" ) - COMPREPLY=( "${COMPREPLY[@]}" $( compgen -P "$prefix$user" \ + COMPREPLY+=( $( compgen -P "$prefix$user" \ -S "$suffix" -W "$hosts" -- "$cur" ) ) fi @@ -1347,8 +1455,7 @@ _known_hosts_real() # if it contains ";", it may mistify the result. But on Gentoo (at # least), -k wasn't available (even if mentioned in the manpage) some # time ago, so... - COMPREPLY=( "${COMPREPLY[@]}" $( \ - compgen -P "$prefix$user" -S "$suffix" -W \ + COMPREPLY+=( $( compgen -P "$prefix$user" -S "$suffix" -W \ "$( avahi-browse -cpr _workstation._tcp 2>/dev/null | \ awk -F';' '/^=/ { print $7 }' | sort -u )" -- "$cur" ) ) fi @@ -1356,7 +1463,7 @@ _known_hosts_real() # Add results of normal hostname completion, unless # `COMP_KNOWN_HOSTS_WITH_HOSTFILE' is set to an empty value. if [ -n "${COMP_KNOWN_HOSTS_WITH_HOSTFILE-1}" ]; then - COMPREPLY=( "${COMPREPLY[@]}" + COMPREPLY+=( $( compgen -A hostname -P "$prefix$user" -S "$suffix" -- "$cur" ) ) fi @@ -1364,25 +1471,25 @@ _known_hosts_real() return 0 } # _known_hosts_real() -complete -F _known_hosts traceroute traceroute6 tracepath tracepath6 ping \ - ping6 fping fping6 telnet host nslookup rsh rlogin ftp dig mtr \ - ssh-installkeys showmount +complete -F _known_hosts traceroute traceroute6 tracepath tracepath6 \ + fping fping6 telnet rsh rlogin ftp dig mtr ssh-installkeys showmount # This meta-cd function observes the CDPATH variable, so that cd additionally # completes on directories under those specified in CDPATH. # _cd() { - local cur IFS=$'\n' i j k - _get_comp_words_by_ref cur + local cur prev words cword + _init_completion || return + local IFS=$'\n' i j k # try to allow variable completion if [[ "$cur" == ?(\\)\$* ]]; then COMPREPLY=( $( compgen -v -P '$' -- "${cur#?(\\)$}" ) ) return 0 fi - _compopt_o_filenames + compopt -o filenames # Use standard dir completion if no CDPATH or parameter starts with /, # ./ or ../ @@ -1400,7 +1507,7 @@ _cd() k="${#COMPREPLY[@]}" for j in $( compgen -d $i/$cur ); do if [[ ( $mark_symdirs && -h $j || $mark_dirs && ! -h $j ) && ! -d ${j#$i/} ]]; then - j="${j}/" + j+="/" fi COMPREPLY[k++]=${j#$i/} done @@ -1441,22 +1548,17 @@ _command() # A meta-command completion function for commands like sudo(8), which need to # first complete on a command, then complete according to that command's own -# completion definition - currently not quite foolproof (e.g. mount and umount -# don't work properly), but still quite useful. +# completion definition. # _command_offset() { - local cur func cline cspec noglob cmd i char_offset word_offset \ - _COMMAND_FUNC _COMMAND_FUNC_ARGS - - word_offset=$1 - # rewrite current completion context before invoking # actual command completion # find new first word position, then # rewrite COMP_LINE and adjust COMP_POINT - local first_word=${COMP_WORDS[$word_offset]} + local word_offset=$1 + local first_word=${COMP_WORDS[$word_offset]} char_offset i for (( i=0; i <= ${#COMP_LINE}; i++ )); do if [[ "${COMP_LINE:$i:${#first_word}}" == "$first_word" ]]; then char_offset=$i @@ -1471,25 +1573,47 @@ _command_offset() COMP_WORDS[i]=${COMP_WORDS[i+$word_offset]} done for (( i; i <= COMP_CWORD; i++ )); do - unset COMP_WORDS[i]; + unset COMP_WORDS[i] done COMP_CWORD=$(( $COMP_CWORD - $word_offset )) COMPREPLY=() + local cur _get_comp_words_by_ref cur if [[ $COMP_CWORD -eq 0 ]]; then - _compopt_o_filenames - COMPREPLY=( $( compgen -c -- "$cur" ) ) + local IFS=$'\n' + compopt -o filenames + COMPREPLY=( $( compgen -d -c -- "$cur" ) ) else - cmd=${COMP_WORDS[0]} - if complete -p ${cmd##*/} &>/dev/null; then - cspec=$( complete -p ${cmd##*/} ) + local cmd=${COMP_WORDS[0]} compcmd=${COMP_WORDS[0]} + local cspec=$( complete -p $cmd 2>/dev/null ) + if [[ ! $cspec ]]; then + if [[ $cmd == */* ]]; then + # Load completion for full path + _completion_loader $cmd + if [[ $? -eq 124 ]]; then + # Success, but we may now have the full path completion... + cspec=$( complete -p $cmd 2>/dev/null ) + if [[ ! $cspec ]]; then + # ...or just the basename one. + compcmd=${cmd##*/} + cspec=$( complete -p $compcmd 2>/dev/null ) + fi + fi + else + # Simple, non-full path case. + _completion_loader $cmd + [[ $? -eq 124 ]] && cspec=$( complete -p $cmd 2>/dev/null ) + fi + fi + + if [[ -n $cspec ]]; then if [ "${cspec#* -F }" != "$cspec" ]; then # complete -F <function> # get function name - func=${cspec#*-F } + local func=${cspec#*-F } func=${func%% *} if [[ ${#COMP_WORDS[@]} -ge 2 ]]; then @@ -1498,25 +1622,25 @@ _command_offset() $func $cmd "${COMP_WORDS[${#COMP_WORDS[@]}-1]}" fi - # remove any \: generated by a command that doesn't - # default to filenames or dirnames (e.g. sudo chown) - # FIXME: I'm pretty sure this does not work! - if [ "${cspec#*-o }" != "$cspec" ]; then - cspec=${cspec#*-o } - cspec=${cspec%% *} - if [[ "$cspec" != @(dir|file)names ]]; then - COMPREPLY=("${COMPREPLY[@]//\\\\:/:}") - else - _compopt_o_filenames + # restore initial compopts + local opt t + while true; do + # FIXME: should we take "+o opt" into account? + t=${cspec#*-o } + if [ "$t" == "$cspec" ]; then + break fi - fi - elif [ -n "$cspec" ]; then - cspec=${cspec#complete}; - cspec=${cspec%%${cmd##*/}}; - COMPREPLY=( $( eval compgen "$cspec" -- "$cur" ) ); + opt=${t%% *} + compopt -o $opt + cspec=${t#$opt} + done + else + cspec=${cspec#complete} + cspec=${cspec%%$compcmd} + COMPREPLY=( $( eval compgen "$cspec" -- "$cur" ) ) fi elif [ ${#COMPREPLY[@]} -eq 0 ]; then - _filedir + _minimal fi fi } @@ -1539,28 +1663,44 @@ _complete_as_root() _longopt() { - local cur prev split=false - _get_comp_words_by_ref -n = cur prev + local cur prev words cword split + _init_completion -s || return - _split_longopt && split=true - - case "$prev" in - --*[Dd][Ii][Rr]*) + case "${prev,,}" in + --help|--usage|--version) + return 0 + ;; + --*dir*) _filedir -d return 0 ;; - --*[Ff][Ii][Ll][Ee]*|--*[Pp][Aa][Tt][Hh]*) + --*file*|--*path*) _filedir return 0 ;; + --+([-a-z0-9_])) + local argtype=$( $1 --help 2>&1 | sed -ne \ + "s|.*$prev\[\{0,1\}=[<[]\{0,1\}\([-A-Za-z0-9_]\{1,\}\).*|\1|p" ) + case ${argtype,,} in + *dir*) + _filedir -d + return 0 + ;; + *file*|*path*) + _filedir + return 0 + ;; + esac + ;; esac $split && return 0 if [[ "$cur" == -* ]]; then COMPREPLY=( $( compgen -W "$( $1 --help 2>&1 | \ - sed -ne 's/.*\(--[-A-Za-z0-9]\{1,\}\).*/\1/p' | sort -u )" \ + sed -ne 's/.*\(--[-A-Za-z0-9]\{1,\}=\{0,1\}\).*/\1/p' | sort -u )" \ -- "$cur" ) ) + [[ $COMPREPLY == *= ]] && compopt -o nospace elif [[ "$1" == @(mk|rm)dir ]]; then _filedir -d else @@ -1568,37 +1708,25 @@ _longopt() fi } # makeinfo and texi2dvi are defined elsewhere. -for i in a2ps awk bash bc bison cat colordiff cp csplit \ - curl cut date df diff dir du enscript env expand fmt fold gperf gprof \ +complete -F _longopt a2ps awk base64 bash bc bison cat colordiff cp csplit \ + cut date df diff dir du enscript env expand fmt fold gperf \ grep grub head indent irb ld ldd less ln ls m4 md5sum mkdir mkfifo mknod \ mv netstat nl nm objcopy objdump od paste patch pr ptx readelf rm rmdir \ - sed seq sha{,1,224,256,384,512}sum shar sort split strip tac tail tee \ - texindex touch tr uname unexpand uniq units vdir wc wget who; do - have $i && complete -F _longopt -o default $i -done -unset i + sed seq sha{,1,224,256,384,512}sum shar sort split strip sum tac tail tee \ + texindex touch tr uname unexpand uniq units vdir wc wget who +declare -A _xspecs _filedir_xspec() { - local IFS cur xspec - - IFS=$'\n' - COMPREPLY=() - _get_comp_words_by_ref cur + local cur prev words cword + _init_completion || return _expand || return 0 - # get first exclusion compspec that matches this command - xspec=$( awk "/^complete[ \t]+.*[ \t]${1##*/}([ \t]|\$)/ { print \$0; exit }" \ - "$BASH_COMPLETION" ) - # prune to leave nothing but the -X spec - xspec=${xspec#*-X } - xspec=${xspec%% *} - + local IFS=$'\n' xspec=${_xspecs[${1##*/}]} tmp local -a toks - local tmp - toks=( ${toks[@]-} $( + toks=( $( compgen -d -- "$(quote_readline "$cur")" | { while read -r tmp; do # see long TODO comment in _filedir() --David @@ -1608,17 +1736,16 @@ _filedir_xspec() )) # Munge xspec to contain uppercase version too + # http://thread.gmane.org/gmane.comp.shells.bash.bugs/15294/focus=15306 eval xspec="${xspec}" local matchop=! if [[ $xspec == !* ]]; then xspec=${xspec#!} matchop=@ fi - [[ ${BASH_VERSINFO[0]} -ge 4 ]] && \ - xspec="$matchop($xspec|${xspec^^})" || \ - xspec="$matchop($xspec|$(printf %s $xspec | tr '[:lower:]' '[:upper:]'))" + xspec="$matchop($xspec|${xspec^^})" - toks=( ${toks[@]-} $( + toks+=( $( eval compgen -f -X "!$xspec" -- "\$(quote_readline "\$cur")" | { while read -r tmp; do [ -n $tmp ] && printf '%s\n' $tmp @@ -1626,65 +1753,140 @@ _filedir_xspec() } )) - [ ${#toks[@]} -ne 0 ] && _compopt_o_filenames + [ ${#toks[@]} -ne 0 ] && compopt -o filenames COMPREPLY=( "${toks[@]}" ) } -list=( $( sed -ne '/^# START exclude/,/^# FINISH exclude/p' "$BASH_COMPLETION" | \ - # read exclusion compspecs - ( - while read line - do - # ignore compspecs that are commented out - if [ "${line#\#}" != "$line" ]; then continue; fi - line=${line%# START exclude*} - line=${line%# FINISH exclude*} - line=${line##*\'} - list=( "${list[@]}" $line ) + +_install_xspec() +{ + local xspec=$1 cmd + shift + for cmd in $@; do + _xspecs[$cmd]=$xspec done - printf '%s ' "${list[@]}" - ) - ) ) -# remove previous compspecs -if [ ${#list[@]} -gt 0 ]; then - eval complete -r ${list[@]} - # install new compspecs - eval complete -F _filedir_xspec "${list[@]}" -fi -unset list + complete -F _filedir_xspec $@ +} +# bzcmp, bzdiff, bz*grep, bzless, bzmore intentionally not here, see Debian: #455510 +_install_xspec '!*.?(t)bz?(2)' bunzip2 bzcat pbunzip2 pbzcat lbunzip2 lbzcat +_install_xspec '!*.@(zip|[ejsw]ar|exe|pk3|wsz|zargo|xpi|sxw|o[tx]t|od[fgpst]|epub|apk)' unzip zipinfo +_install_xspec '*.Z' compress znew +# zcmp, zdiff, z*grep, zless, zmore intentionally not here, see Debian: #455510 +_install_xspec '!*.@(Z|[gGd]z|t[ag]z)' gunzip zcat unpigz +_install_xspec '!*.Z' uncompress +# lzcmp, lzdiff intentionally not here, see Debian: #455510 +_install_xspec '!*.@(tlz|lzma)' lzcat lzegrep lzfgrep lzgrep lzless lzmore unlzma +_install_xspec '!*.@(?(t)xz|tlz|lzma)' unxz xzcat +_install_xspec '!*.lrz' lrunzip +_install_xspec '!*.@(gif|jp?(e)g|miff|tif?(f)|pn[gm]|p[bgp]m|bmp|xpm|ico|xwd|tga|pcx)' ee +_install_xspec '!*.@(gif|jp?(e)g|tif?(f)|png|p[bgp]m|bmp|x[bp]m|rle|rgb|pcx|fits|pm)' xv qiv +_install_xspec '!*.@(@(?(e)ps|?(E)PS|pdf|PDF)?(.gz|.GZ|.bz2|.BZ2|.Z))' gv ggv kghostview +_install_xspec '!*.@(dvi|DVI)?(.@(gz|Z|bz2))' xdvi kdvi +_install_xspec '!*.dvi' dvips dviselect dvitype dvipdf advi dvipdfm dvipdfmx +_install_xspec '!*.[pf]df' acroread gpdf xpdf +_install_xspec '!*.@(?(e)ps|pdf)' kpdf +_install_xspec '!*.@(okular|@(?(e|x)ps|?(E|X)PS|[pf]df|[PF]DF|dvi|DVI|cb[rz]|CB[RZ]|djv?(u)|DJV?(U)|dvi|DVI|gif|jp?(e)g|miff|tif?(f)|pn[gm]|p[bgp]m|bmp|xpm|ico|xwd|tga|pcx|GIF|JP?(E)G|MIFF|TIF?(F)|PN[GM]|P[BGP]M|BMP|XPM|ICO|XWD|TGA|PCX|epub|EPUB|odt|ODT|fb?(2)|FB?(2)|mobi|MOBI|g3|G3|chm|CHM)?(.?(gz|GZ|bz2|BZ2)))' okular +_install_xspec '!*.pdf' epdfview +_install_xspec '!*.@(?(e)ps|pdf)' ps2pdf ps2pdf12 ps2pdf13 ps2pdf14 ps2pdfwr +_install_xspec '!*.texi*' makeinfo texi2html +_install_xspec '!*.@(?(la)tex|texi|dtx|ins|ltx|dbj)' tex latex slitex jadetex pdfjadetex pdftex pdflatex texi2dvi +_install_xspec '!*.mp3' mpg123 mpg321 madplay +_install_xspec '!*@(.@(mp?(e)g|MP?(E)G|wma|avi|AVI|asf|vob|VOB|bin|dat|divx|DIVX|vcd|ps|pes|fli|flv|FLV|fxm|FXM|viv|rm|ram|yuv|mov|MOV|qt|QT|wmv|mp[234]|MP[234]|m4[pv]|M4[PV]|mkv|MKV|og[gmv]|OG[GMV]|t[ps]|T[PS]|m2t?(s)|M2T?(S)|wav|WAV|flac|FLAC|asx|ASX|mng|MNG|srt|m[eo]d|M[EO]D|s[3t]m|S[3T]M|it|IT|xm|XM)|+([0-9]).@(vdr|VDR))?(.part)' xine aaxine fbxine +_install_xspec '!*@(.@(mp?(e)g|MP?(E)G|wma|avi|AVI|asf|vob|VOB|bin|dat|divx|DIVX|vcd|ps|pes|fli|flv|FLV|fxm|FXM|viv|rm|ram|yuv|mov|MOV|qt|QT|wmv|mp[234]|MP[234]|m4[pv]|M4[PV]|mkv|MKV|og[gmv]|OG[GMV]|t[ps]|T[PS]|m2t?(s)|M2T?(S)|wav|WAV|flac|FLAC|asx|ASX|mng|MNG|srt|m[eo]d|M[EO]D|s[3t]m|S[3T]M|it|IT|xm|XM|iso|ISO)|+([0-9]).@(vdr|VDR))?(.part)' kaffeine dragon +_install_xspec '!*.@(avi|asf|wmv)' aviplay +_install_xspec '!*.@(rm?(j)|ra?(m)|smi?(l))' realplay +_install_xspec '!*.@(mpg|mpeg|avi|mov|qt)' xanim +_install_xspec '!*.@(ogg|m3u|flac|spx)' ogg123 +_install_xspec '!*.@(mp3|ogg|pls|m3u)' gqmpeg freeamp +_install_xspec '!*.fig' xfig +_install_xspec '!*.@(mid?(i)|cmf)' playmidi +_install_xspec '!*.@(mid?(i)|rmi|rcp|[gr]36|g18|mod|xm|it|x3m|s[3t]m|kar)' timidity +_install_xspec '!*.@(669|abc|am[fs]|d[bs]m|dmf|far|it|mdl|m[eo]d|mid?(i)|mt[2m]|okta|p[st]m|s[3t]m|ult|umx|wav|xm)' modplugplay modplug123 +_install_xspec '*.@(o|so|so.!(conf)|a|[rs]pm|gif|jp?(e)g|mp3|mp?(e)g|avi|asf|ogg|class)' vi vim gvim rvim view rview rgvim rgview gview emacs xemacs sxemacs kate kwrite +_install_xspec '!*.@([eE][xX][eE]?(.[sS][oO])|[cC][oO][mM]|[sS][cC][rR])' wine +_install_xspec '!*.@(zip|z|gz|tgz)' bzme +# konqueror not here on purpose, it's more than a web/html browser +_install_xspec '!*.@(?([xX]|[sS])[hH][tT][mM]?([lL]))' netscape mozilla lynx opera galeon dillo elinks amaya firefox mozilla-firefox iceweasel google-chrome chromium-browser epiphany +_install_xspec '!*.@(sxw|stw|sxg|sgl|doc?([mx])|dot?([mx])|rtf|txt|htm|html|?(f)odt|ott|odm)' oowriter +_install_xspec '!*.@(sxi|sti|pps?(x)|ppt?([mx])|pot?([mx])|?(f)odp|otp)' ooimpress +_install_xspec '!*.@(sxc|stc|xls?([bmx])|xlw|xlt?([mx])|[ct]sv|?(f)ods|ots)' oocalc +_install_xspec '!*.@(sxd|std|sda|sdd|?(f)odg|otg)' oodraw +_install_xspec '!*.@(sxm|smf|mml|odf)' oomath +_install_xspec '!*.odb' oobase +_install_xspec '!*.[rs]pm' rpm2cpio +_install_xspec '!*.aux' bibtex +_install_xspec '!*.po' poedit gtranslator kbabel lokalize +_install_xspec '!*.@([Pp][Rr][Gg]|[Cc][Ll][Pp])' harbour gharbour hbpp +_install_xspec '!*.[Hh][Rr][Bb]' hbrun +_install_xspec '!*.ly' lilypond ly2dvi +_install_xspec '!*.@(dif?(f)|?(d)patch)?(.@([gx]z|bz2|lzma))' cdiff +_install_xspec '!*.lyx' lyx +_install_xspec '!@(*.@(ks|jks|jceks|p12|pfx|bks|ubr|gkr|cer|crt|cert|p7b|pkipath|pem|p10|csr|crl)|cacerts)' portecle +_install_xspec '!*.@(mp[234c]|og[ag]|@(fl|a)ac|m4[abp]|spx|tta|w?(a)v|wma|aif?(f)|asf|ape)' kid3 kid3-qt +unset -f _install_xspec + +# Minimal completion to use as fallback in _completion_loader. +_minimal() +{ + local cur prev words cword split + _init_completion -s || return + $split && return + _filedir +} -# source completion directory definitions +# set up dynamic completion loading +_completion_loader() +{ + local compdir=./completions + [[ $BASH_SOURCE == */* ]] && compdir="${BASH_SOURCE%/*}/completions" + + # Special case for init.d scripts. + if [[ $1 == /etc?(/rc.d)/init.d/* ]]; then + . "$compdir/service" &>/dev/null && return 124 || return 1 + fi + + # Try basename. + . "$compdir/${1##*/}" &>/dev/null && return 124 + + # Need to define *something*, otherwise there will be no completion at all. + complete -F _minimal "$1" && return 124 +} && +complete -D -F _completion_loader + +# Function for loading and calling functions from dynamically loaded +# completion files that may not have been sourced yet. +# @param $1 completion file to load function from in case it is missing +# @param $2... function and its arguments +_xfunc() +{ + set -- "$@" + local srcfile=$1 + shift + declare -F $1 &>/dev/null || { + local compdir=./completions + [[ $BASH_SOURCE == */* ]] && compdir="${BASH_SOURCE%/*}/completions" + . "$compdir/$srcfile" + } + "$@" +} + +# source compat completion directory definitions if [[ -d $BASH_COMPLETION_COMPAT_DIR && -r $BASH_COMPLETION_COMPAT_DIR && \ -x $BASH_COMPLETION_COMPAT_DIR ]]; then for i in $(LC_ALL=C command ls "$BASH_COMPLETION_COMPAT_DIR"); do i=$BASH_COMPLETION_COMPAT_DIR/$i - [[ ${i##*/} != @(*~|*.bak|*.swp|\#*\#|*.dpkg*|*.rpm@(orig|new|save)|Makefile*) \ - && -f $i && -r $i ]] && . "$i" - done -fi -if [[ $BASH_COMPLETION_DIR != $BASH_COMPLETION_COMPAT_DIR && \ - -d $BASH_COMPLETION_DIR && -r $BASH_COMPLETION_DIR && \ - -x $BASH_COMPLETION_DIR ]]; then - for i in $(LC_ALL=C command ls "$BASH_COMPLETION_DIR"); do - i=$BASH_COMPLETION_DIR/$i - [[ ${i##*/} != @(*~|*.bak|*.swp|\#*\#|*.dpkg*|*.rpm@(orig|new|save)|Makefile*) \ + [[ ${i##*/} != @($_backup_glob|Makefile*|$_blacklist_glob) \ && -f $i && -r $i ]] && . "$i" done fi unset i # source user completion file -[[ $BASH_COMPLETION != ~/.bash_completion && -r ~/.bash_completion ]] \ +[[ ${BASH_SOURCE[0]} != ~/.bash_completion && -r ~/.bash_completion ]] \ && . ~/.bash_completion unset -f have -unset UNAME USERLAND have +unset have set $BASH_COMPLETION_ORIGINAL_V_VALUE unset BASH_COMPLETION_ORIGINAL_V_VALUE -# Local variables: -# mode: shell-script -# sh-basic-offset: 4 -# sh-indent-comment: t -# indent-tabs-mode: nil -# End: # ex: ts=4 sw=4 et filetype=sh |