# 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 if [[ $# -eq 1 ]] then comps[$1]="__$1" else v="$1" shift for i; do comps[$i]="$v" done fi } defpatcomp() { if [[ ${+patcomps} == 1 ]] 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() { local var eval var\=\$\{\+$1\} if [[ "$var" == 0 ]] then "$@" else eval complist \$\{${1}\[\@\]\} 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 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 } # Do sub-completion for pre-command modifiers. defcomp __precmd - noglob nocorrect exec command builtin __precmd() { COMMAND="$1" shift (( CURRENT-- )) if [[ CURRENT -eq 0 ]] then CONTEXT=command else CONTEXT=argument fi compsub } # 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:.=,' # so you may want to modify this. pfiles() { local nm str pa pre epre a b c s rest setopt localoptions nullglob rcexpandparam globdots extendedglob unsetopt markdirs globsubst shwordsplit nounset nm=$NMATCHES if [[ $# -eq 0 ]] then complist -f elif [[ "$1" = -g ]] then complist -g "$argv[2,-1]" shift else complist $1 shift fi [[ -nmatches nm ]] || return str="$PREFIX*$SUFFIX" [[ -z "$1" ]] && 1='*' if [[ $str[1] = \~ ]] then pre="${str%%/*}/" eval epre\=$pre str="${str#*/}" pa='' else pre='' epre='' if [[ $str[1] = / ]] then str="$str[2,-1]" pa='/' else pa='' fi fi str="$str:gs/,/*,/:gs/_/*_/:gs./.*/.:gs/-/*[-_]/:gs/./*[.,]/:gs-*[.,]*[.,]*/-../-:gs.**.*." while [[ "$str" = */* ]] do rest="${str#*/}" a="${epre}${pa}(#l)${str%%/*}(-/)" a=( $~a ) if [[ $#a -eq 0 ]] then return elif [[ $#a -gt 1 ]] then c=() s=( $rest$@ ) s=( "${(@)s:gs.**.*.}" ) for i in $a; do b=( $~i/(#l)$~s ) eval b\=\( \$\{b:/\*\(${(j:|:)fignore}\)\} \) [[ $#b -ne 0 ]] && c=( $c $i ) done if [[ $#c -eq 0 ]] then return elif [[ $#c -ne 1 ]] then a="$epre$pa" c=( $~c/(#l)$~s ) eval c\=\( \$\{c:/\*\(${(j:|:)fignore}\)\} \) c=( ${c#$a} ) for i in $c; do compadd -p "$pre$pa" -W "$a" -s "/${i#*/}" -f "${i%%/*}" done return fi a=( "$c[1]" ) fi a="$a[1]" pa="$pa${a##*/}/" str="$rest" done a="$epre$pa" s=( $str$@ ) s=( "${(@)s:gs.**.*.}" ) b=( $~a(#l)$~s ) eval b\=\( \$\{b:/\*\(${(j:|:)fignore}\)\} \) compadd -p "$pre$pa" -W "$epre$pa" -f ${b#$a} } # 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 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 __math --math-- __math=( -v ) defcomp __subscr --subscr-- __subscr() { compalso --math-- "$@" # ...probably other stuff } # A simple pattern completion, just as an example. defpatcomp __x_options '*/X11/*' __x_options() { complist -J options -k '(-display -name -xrm)' } # A better example: completion for `find'. 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 } # Various completions... 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 __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 __which which whence __which=( -caF ) 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 __dvi xdvi dvips dvibook dviconcat dvicopy dvidvi dviselect dvitodvi dvitype __dvi() { files -g '*.(dvi|DVI)' } defcomp __dirs rmdir df du dircmp cd __dirs() { files -/ '*(-/)' } defcomp __jobs fg bg jobs __jobs=(-j -P '%?') defcomp kill __kill() { if [[ -iprefix '-' ]] then complist -k signals else complist -P '%?' -j fi } defcomp __uncompress uncompress zmore __uncompress() { files -g '*.Z' } defcomp compress __compress() { files -g '*~*.Z' } defcomp __tex tex latex glatex slitex gslitex __tex() { files -g '*.(tex|TEX|texinfo|texi)' } defcomp __options setopt unsetopt __options=(-M 'L:|[nN][oO]= M:_= M:{A-Z}={a-z}' -o) defcomp __funcs unfunction __funcs=(-F) defcomp __aliases unalias __aliases=(-a) defcomp __vars unset __vars=(-v) defcomp __enabled disable __enabled=(-FBwa) defcomp __disabled enable __disabled=(-dFBwa) defcomp __pdf acroread __pdf() { files -g '*.(pdf|PDF)' } defcomp tar __tar() { local nm tf compsave tf="$2" nm=$NMATCHES 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 }