# Define a new widget behaving like `expand-or-complete' but calling the # function `main-complete' to generate matches. zle -C my-comp expand-or-complete main-complete bindkey '\C-i' my-comp # Below is a proposed main loop and the things it needs. # One associative array for normal completions and one for patterns. typeset -A comps # These may be used to define completion handlers. First argument is the # name of the function/variable containing the definition, the other # arguments are the command names for which this definition should be used. # With only one argument the function/variable-name __$1 is used. defcomp() { local v a='' if [[ "$1" = -a ]] then shift a=yes fi if [[ $# -eq 1 ]] then comps[$1]="__$1" [[ -z "$a" ]] || autoload "__$1" else v="$1" shift for i; do comps[$i]="$v" done [[ -z "$a" ]] || autoload "$v" fi } defpatcomp() { if [[ "$1" = -a ]] then shift autoload "$1" fi if (( $+patcomps )) then patcomps=("$patcomps[@]" "$2 $1" ) else patcomps=( "$2 $1" ) fi } # These can be used to easily save and restore the state of the special # variables used by the completion code. alias compsave='local _opre _oipre _oargs _ocur;_opre="$PREFIX";_oipre="$IPREFIX";_oargs=( "$@" );_ocur="$CURRENT"' alias compreset='PREFIX="$_opre";IPREFIX="$_oipre";argv=( "$_oargs[@]" );CURRENT="$_ocur"' # This is an easy way to get completion for sub-commands. alias compsub='do-complete "$@" || return 1' # This searches $1 in the array for normal completions and calls the result. compalso() { 1="$comps[$1]" [[ -z "$1" ]] || call-complete "$@" } # This generates matches. The first argument is something we got from one # of the associative arrays above. This is expected to be either the name # of a variable in which case we use its value as arguments to complist, # or it is the name of a function in which case we call this function with # the arguments from the command line as its arguments. call-complete() { if [[ ${(P)+${1}} -eq 1 ]] then complist ${(@P)${1}} else "$@" fi } # The main loop of the competion code. This is what is called when TAB is # pressed. The completion code gives us the special variables and the # arguments from the command line are gives as positional parameters. main-complete() { # emulate -R zsh local comp setopt localoptions nullglob rcexpandparam globdots unsetopt markdirs globsubst shwordsplit nounset # An entry for `--first--' is the replacement for `compctl -T' # The `|| return 1' is used throughout: if a function producing matches # returns non-zero this is interpreted as `do not try to produce more matches' # (this is the replacement for `compctl -t'). comp="$comps[--first--]" [[ -z "$comp" ]] || call-complete "$comp" "$@" || return 1 # For arguments we use the `do-complete' function below called via the # convenience alias `compsub'. if [[ $CONTEXT == argument || $CONTEXT == command ]] then # We first try the `compctl's. This is without first (-T) and default (-D) # completion. If you want them add `-T' and/or `-D' to this command. # If this produces any matches, we don't try new style completion. If you # want to have that tried anyway, remove the `[[ -nmatches ... ]] ...' # below. compcall [[ -nmatches 0 ]] || return compsub else # Let's see if we have a special completion definition for the other # possible contexts. comp='' case $CONTEXT in redirect) comp="$comps[--redir--]";; math) comp="$comps[--math--]";; subscript) comp="$comps[--subscr--]";; value) comp="$comps[--value--]";; condition) comp="$comps[--cond--]";; esac # If not, we use default completion, if any. [[ -z "$comp" ]] && comp="$comps[--default--]" [[ -z "$comp" ]] || call-complete "$comp" "$@" || return 1 fi } # This does completion for a command (in command position and for the # arguments). do-complete() { local comp cmd1 cmd2 pat val # Completing in command position? If not we set up `cmd1' and `cmd2' as # two strings we have search in the completion definition arrays (e.g. # a path and the last path name component). if [[ $CONTEXT == command ]] then comp="$comps[--command--]" [[ -z "$comp" ]] || call-complete "$comp" "$@" || return 1 return 0 elif [[ "$COMMAND[1]" == '=' ]] then eval cmd1\=$COMMAND cmd2="$COMMAND[2,-1]" elif [[ "$COMMAND" == */* ]] then cmd1="$COMMAND" cmd2="${COMMAND:t}" else cmd1="$COMMAND" eval cmd2=$(whence -p $COMMAND) fi # See if there are any matching pattern completions. for i in "$patcomps[@]"; do pat="${i% *}" val="${i#* }" if [[ "$cmd1" == $~pat || "$cmd2" == $~pat ]] then call-complete "$val" "$@" || return 1 fi done # Now look up the two names in the normal completion array. comp="${comps[$cmd1]:-$comps[$cmd2]}" # And generate the matches, probably using default completion. [[ -z "$comp" ]] && comp="$comps[--default--]" [[ -z "$comp" ]] || call-complete "$comp" "$@" || return 1 } # Utility function for in-path completion. # First argument should be an complist-option (e.g. -f, -/, -g). The other # arguments should be glob patterns, one per argument. # E.g.: files -g '*.tex' '*.texi' # This is intended as a replacement for `complist -f', `complist -/', and # `complist -g ...' (but don't use it with other options). # This function behaves as if you have a matcher definition like: # compctl -M 'r:|[-.,_/]=* r:|=* m:{a-z}={A-Z} m:-=_ m:.=,' \ # 'm:{a-z}={A-Z} l:|=* r:|=*' # so you may want to modify this. pfiles() { local nm ostr opa pre epre a b c s rest ppres setopt localoptions nullglob rcexpandparam globdots extendedglob unsetopt markdirs globsubst shwordsplit nounset if [[ "$1" = -W ]] then a="$2" if [[ "$a[1]" = '(' ]] then ppres=( $a[2,-2]/ ) else ppres=( ${(P)${a}} ) [[ $#ppres -eq 0 ]] && ppres=( $a/ ) fi [[ $#ppres -eq 0 ]] && ppres=( '' ) shift 2 else ppres=( '' ) fi str="${PREFIX:q}*${SUFFIX:q}" if [[ -z "$a[1]" || "$str[1]" = [~/] || "$str" = (.|..)/* ]] then a=() else a=(-W "( $ppres )") fi nm=$NMATCHES if [[ $# -eq 0 ]] then complist "$a[@]" -f elif [[ "$1" = -g ]] then complist "$a[@]" -g "$argv[2,-1]" shift else complist "$a[@]" $1 shift fi [[ -nmatches nm ]] || return [[ -z "$1" ]] && 1='*' if [[ "$str[1]" = \~ ]] then pre="${str%%/*}/" eval epre\=$pre str="${str#*/}" opa='' ppres=( '' ) else pre='' epre='' if [[ "$str[1]" = / ]] then str="$str[2,-1]" opa='/' ppres=( '' ) else [[ "$str" = (.|..)/* ]] && ppres=( '' ) opa='' fi fi while [[ "$str" = */* ]] do [[ -e "$epre$opa${str%%/*}" ]] || break opa="$opa${str%%/*}/" str="${str#*/}" done if [[ -matcher 1 ]] then ostr="$str:gs/,/*,/:gs/_/*_/:gs./.*/.:gs/-/*[-_]/:gs/./*[.,]/:gs-*[.,]*[.,]*/-../-:gs.**.*." else ostr="${str%/*}/*${str##*/}*" ostr="$ostr:gs./.*/.:gs.**.*." fi for ppre in "$ppres[@]"; do str="$ostr" pa="$opa" while [[ "$str" = */* ]] do rest="${str#*/}" a="${ppre}${epre}${pa}(#l)${str%%/*}(-/)" a=( $~a ) if [[ $#a -eq 0 ]] then continue 2 elif [[ $#a -gt 1 ]] then c=() s=( $rest$@ ) s=( "${(@)s:gs.**.*.}" ) for i in $a; do b=( $~i/(#l)$~s ) [[ $#b -ne 0 ]] && c=( $c $i ) done if [[ $#c -eq 0 ]] then continue 2 elif [[ $#c -ne 1 ]] then a="$ppre$epre$pa" c=( $~c/(#l)$~s ) c=( ${c#$a} ) for i in $c; do compadd -p "$pre$pa" -W "$a" -s "/${i#*/}" -fF -- "${i%%/*}" done continue 2 fi a=( "$c[1]" ) fi a="$a[1]" pa="$pa${a##*/}/" str="$rest" done a="$ppre$epre$pa" s=( $str$@ ) s=( "${(@)s:gs.**.*.}" ) b=( $~a(#l)$~s ) compadd -p "$pre$pa" -W "$ppre$epre$pa" -fF -- ${b#$a} done } # Utility function for completing files of a given type or any file. # In many cases you will want to call this one instead of pfiles(). files() { local nm=$NMATCHES pfiles "$@" [[ $# -ne 0 && -nmatches nm ]] && pfiles } # Simple default, command, and math completion defined with variables. defcomp __default --default-- __default() { files } defcomp __command --command-- __command=( -c ) defcomp __vars --math-- __vars=( -v ) defcomp __subscr --subscr-- __subscr() { compalso --math-- "$@" [[ ${(Pt)${COMMAND}} = assoc* ]] && complist -k "( ${(kP)${COMMAND}} )" } defcomp __cond --cond-- __cond() { if [[ -current -1 -o ]] then complist -o -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' elif [[ -current -1 -nt || -current -1 -ot || -current -1 -ef ]] then files else files complist -v fi } # Do sub-completion for pre-command modifiers. defcomp __precmd - nohup nice eval time rusage noglob nocorrect exec __precmd() { COMMAND="$1" shift (( CURRENT-- )) if [[ CURRENT -eq 0 ]] then CONTEXT=command else CONTEXT=argument fi compsub } defcomp builtin __builtin() { if [[ -position 2 -1 ]] then compsub else complist -eB fi } defcomp command __command() { if [[ -position 2 -1 ]] then compsub else complist -em fi } # Various completions... defcomp __jobs fg jobs __jobs=(-j -P '%') defcomp __bjobs bg __bjobs=(-z -P '%') defcomp __arrays shift __arrays=(-A) defcomp __which which whence where type __which=( -caF ) defcomp unhash __unhash() { [[ -mword 1 -*d* ]] && complist -n [[ -mword 1 -*a* ]] && complist -a [[ -mword 1 -*f* ]] && complist -F [[ ! -mword 1 -* ]] && complist -m } defcomp hash __hash() { if [[ -mword 1 -*d* ]] then if [[ -string 1 '=' ]] then pfiles -g '*(-/)' else complist -n -q -S '=' fi elif [[ -string 1 '=' ]] then files -g '*(*)' '*(-/)' else complist -m -q -S '=' fi } defcomp __funcs unfunction __funcs=(-F) defcomp echotc __echotc=(-k '(al dc dl do le up al bl cd ce cl cr dc dl do ho is le ma nd nl se so up)') defcomp __aliases unalias __aliases=(-a) defcomp __vars getopts read unset vared defcomp __varseq declare export integer local readonly typeset __varseq=(-v -S '=') defcomp disable __disable() { [[ -mcurrent -1 -*a* ]] && complist -ea [[ -mcurrent -1 -*f* ]] && complist -eF [[ -mcurrent -1 -*r* ]] && complist -ew [[ ! -mcurrent -1 -* ]] && complist -eB } defcomp enable __enable() { [[ -mcurrent -1 -*a* ]] && complist -da [[ -mcurrent -1 -*f* ]] && complist -dF [[ -mcurrent -1 -*r* ]] && complist -dw [[ ! -mcurrent -1 -* ]] && complist -dB } defcomp __limits limit unlimit __limits=(-k "(${(j: :)${(f)$(limit)}%% *})") defcomp source __source() { if [[ -position 2 -1 ]] then compsub else files fi } defcomp setopt __setopt() { local nm=$NMATCHES complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' \ -s '$({ unsetopt kshoptionprint; unsetopt } 2>/dev/null)' [[ -nmatches nm ]] && complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' -o } defcomp unsetopt __unsetopt() { local nm=$NMATCHES complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' \ -s '$({ unsetopt kshoptionprint; setopt } 2>/dev/null)' [[ -nmatches nm ]] && complist -M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' -o } defcomp autoload __autoload=(-s '${^fpath}/*(N:t)') defcomp bindkey __bindkey() { if [[ -mword 1 -*[DAN]* || -mcurrent -1 -*M ]] then complist -s '$(bindkey -l)' else complist -b fi } defcomp fc __fc() { if [[ -mcurrent -1 -*e ]] then complist -c elif [[ -mcurrent -1 -[ARWI]## ]] then files fi } defcomp sched __sched() { [[ -position 2 -1 ]] && compsub } defcomp set __set() { if [[ -mcurrent -1 [-+]o ]] then complist -o elif [[ -current -1 -A ]] then complist -A fi } defcomp zle __zle() { if [[ -word 1 -N && -position 3 ]] then complist -F else complist -b fi } defcomp zmodload __zmodload() { if [[ -mword 1 -*(a*u|u*a)* || -mword 1 -*a* && -position 3 -1 ]] then complist -B elif [[ -mword 1 -*u* ]] then complist -s '$(zmodload)' else complist -s '${^module_path}/*(N:t:r)' fi } defcomp trap __trap() { if [[ -position 1 ]] then complist -c else complist -k signals fi } killfunc() { reply=( "$(ps -x 2>/dev/null)" ) } defcomp kill __kill() { if [[ -iprefix '-' ]] then complist -k "($signals[1,-3])" else complist -P '%' -j complist -y killfunc -s '`ps -x 2>/dev/null | tail +2 | cut -c1-5`' fi } defcomp wait __wait() { complist -P '%' -j complist -y killfunc -s '`ps -x 2>/dev/null | tail +2 | cut -c1-5`' } defcomp cd __cd() { files -W cdpath -g '*(-/)' } defcomp __rlogin rlogin rsh ssh __rlogin() { if [[ -position 1 ]] then complist -k hosts elif [[ -position 2 ]] then complist -k '(-l)' elif [[ -position 3 && -word 1 artus ]] then complist -k '(puck root)' fi } defcomp __gunzip gunzip zcat __gunzip() { files -g '*.[gG][z]' } defcomp gzip __gzip() { files -g '*~*.[gG][zZ]' } defcomp xfig __xfig() { files -g '*.fig' } defcomp __make make gmake pmake __make() { complist -s "\$(awk '/^[a-zA-Z0-9][^/ ]+:/ {print \$1}' FS=: [mM]akefile)" } defcomp __ps gs ghostview gview psnup psselect pswrap pstops pstruct lpr __ps() { files -g '*([pP][sS]|eps)' } defcomp __dvi xdvi dvips dvibook dviconcat dvicopy dvidvi dviselect dvitodvi dvitype __dvi() { files -g '*.(dvi|DVI)' } defcomp __dirs rmdir df du dircmp __dirs() { files -/ '*(-/)' } defcomp __uncompress uncompress zmore __uncompress() { files -g '*.Z' } defcomp compress __compress() { files -g '*~*.Z' } defcomp __tex tex latex slitex __tex() { files -g '*.(tex|TEX|texinfo|texi)' } defcomp __pdf acroread __pdf() { files -g '*.(pdf|PDF)' } defcomp tar __tar() { local nm=$NMATCHES tf="$2" compsave if [[ ( -mword 1 *t*f* || -mword 1 *x*f* ) && -position 3 100000 ]] then complist -k "( $(tar tf $tf) )" compreset elif [[ -mword 1 *c*f* && -position 3 100000 ]] then files compreset elif [[ -mcurrent -1 *f* && -position 2 ]] then files -g '*.(tar|TAR)' fi } defcomp find __find() { compsave if [[ -mbetween -(ok|exec) \\\; ]] then compsub elif [[ -iprefix - ]] then complist -s 'daystart {max,min,}depth follow noleaf version xdev \ {a,c,}newer {a,c,m}{min,time} empty false {fs,x,}type gid inum links \ {i,}{l,}name {no,}{user,group} path perm regex size true uid used \ exec {f,}print{f,0,} ok prune ls' compreset elif [[ -position 1 ]] then complist -g '. ..' files -g '(-/)' elif [[ -mcurrent -1 -((a|c|)newer|fprint(|0|f)) ]] then files elif [[ -current -1 -fstype ]] then complist -k '(ufs 4.2 4.3 nfs tmp mfs S51K S52K)' elif [[ -current -1 -group ]] then complist -k groups elif [[ -current -1 -user ]] then complist -u fi } # A simple pattern completion, just as an example. defpatcomp __x_options '*/X11/*' __x_options() { complist -J options -k '(-display -name -xrm)' }