#autoload # Complete the arguments of the current command according to the # descriptions given as arguments to this function. local long cmd="$words[1]" descr mesg subopts opt usecc autod local oldcontext="$curcontext" hasopts rawret optarg singopt alwopt long=$argv[(I)--] if (( long )); then local name tmp tmpargv if [[ long -eq 1 ]]; then tmpargv=() else tmpargv=( "${(@)argv[1,long-1]}" ) fi name=${~words[1]} [[ "$name" = [^/]*/* ]] && name="$PWD/$name" name="_args_cache_${name}" name="${name//[^a-zA-Z0-9_]/_}" if (( ! ${(P)+name} )); then local iopts sopts pattern tmpo dir cur cache typeset -U lopts cache=() # We have to build a new long-option cache, get the `-i' and # `-s' options. set -- "${(@)argv[long+1,-1]}" iopts=() sopts=() while [[ "$1" = -[is]* ]]; do if [[ "$1" = -??* ]]; then tmp="${1[3,-1]}" cur=1 else tmp="$2" cur=2 fi if [[ "$tmp[1]" = '(' ]]; then tmp=( ${=tmp[2,-2]} ) else tmp=( "${(@P)tmp}" ) fi if [[ "$1" = -i* ]]; then iopts=( "$iopts[@]" "$tmp[@]" ) else sopts=( "$sopts[@]" "$tmp[@]" ) fi shift cur done # Now get the long option names by calling the command with `--help'. # The parameter expansion trickery first gets the lines as separate # array elements. Then we select all lines whose first non-blank # character is a hyphen. Since some commands document more than one # option per line, separated by commas, we convert commas into # newlines and then split the result again at newlines after joining # the old array elements with newlines between them. Then we select # those elements that start with two hyphens, remove anything up to # those hyphens and anything from the space or tab after the # option up to the end. lopts=("--${(@)^${(@)${(@)${(@M)${(@ps:\n:j:\n:)${(@)${(@M)${(@f)$(_call_program options ${~words[1]} --help 2>&1)//\[--/ --}:#[ ]#-*}//,/ }}:#[ ]#--*}#*--}%%[] ]*}:#}") # Remove options also described by user-defined specs. tmp=() for opt in "${(@)${(@)lopts:#--}%%\=*}"; do # Using (( ... )) gives a parse error. let "$tmpargv[(I)(|\([^\)]#\))(|\*)${opt}(|[-+]|=(|-))(|\[*\])(|:*)]" || tmp=( "$tmp[@]" "$lopts[(r)$opt(|=*)]" ) done lopts=( "$tmp[@]" ) # Now remove all ignored options ... while (( $#iopts )); do lopts=( ${lopts:#$~iopts[1]} ) shift iopts done # ... and add "same" options while (( $#sopts )); do lopts=( $lopts ${lopts/$~sopts[1]/$sopts[2]} ) shift 2 sopts done # Then we walk through the descriptions plus a few builtin ones. # The last one matches all options; the `special' description and action # makes those options be completed without an argument description. set -- "$@" '*=FILE*:file:_files' \ '*=(DIR|PATH)*:directory:_files -/' '*=*:=: ' '*: : ' while (( $# )); do # First, we get the pattern and the action to use and take them # from the positional parameters. pattern="${${${(M)1#*[^\\]:}[1,-2]}//\\\\:/:}" descr="${1#${pattern}}" if [[ "$pattern" = *\(-\) ]]; then pattern="$pattern[1,-4]" dir=- else dir= fi shift # We get all options matching the pattern and take them from the # list we have built. If no option matches the pattern, we # continue with the next. tmp=("${(@M)lopts:##$~pattern}") lopts=("${(@)lopts:##$~pattern}") (( $#tmp )) || continue opt='' # If there are option strings with a `[=', we take these to get an # optional argument. tmpo=("${(@M)tmp:#*\[\=*}") if (( $#tmpo )); then tmp=("${(@)tmp:#*\[\=*}") if [[ "$descr" = :\=* ]]; then for opt in "$tmpo[@]"; do cache=( "$cache[@]" "${${opt%%\=*}//[^a-zA-Z0-9-]}=::${(L)${opt%\]}#*\=}: " ) done else tmpo=("${(@)${(@)tmpo%%\=*}//[^a-zA-Z0-9-]}") if [[ "$descr" = ::* ]]; then cache=( "$cache[@]" "${(@)^tmpo}=${dir}${descr}" ) else cache=( "$cache[@]" "${(@)^tmpo}=${dir}:${descr}" ) fi fi fi # Descriptions with `=': mandatory argument. tmpo=("${(@M)tmp:#*\=*}") if (( $#tmpo )); then tmp=("${(@)tmp:#*\=*}") if [[ "$descr" = :\=* ]]; then for opt in "$tmpo[@]"; do cache=( "$cache[@]" "${${opt%%\=*}//[^a-zA-Z0-9-]}=:${(L)${opt%\]}#*\=}: " ) done else tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}") cache=( "$cache[@]" "${(@)^tmpo}=${dir}${descr}" ) fi fi # Everything else is just added as an option without arguments or # as described by $descr. if (( $#tmp )); then tmp=("${(@)tmp//[^a-zA-Z0-9-]}") if [[ -n "$descr" && "$descr" != ': : ' ]]; then cache=( "$cache[@]" "${(@)^tmp}${descr}" ) else cache=( "$cache[@]" "$tmp[@]" ) fi fi done set -A "$name" "${(@)cache:# #}" fi set -- "$tmpargv[@]" "${(@P)name}" fi subopts=() singopt=() while [[ "$1" = -(O*|[CRWsw]) ]]; do case "$1" in -C) usecc=yes; shift ;; -O) subopts=( "${(@P)2}" ); shift 2 ;; -O*) subopts=( "${(@P)${1[3,-1]}}" ); shift ;; -R) rawret=yes; shift;; -w) optarg=yes; shift;; -s) singopt=(-s); shift;; -W) alwopt=arg; shift;; esac done [[ "$PREFIX" = [-+] ]] && alwopt=arg zstyle -s ":completion:${curcontext}:options" auto-description autod if (( $# )) && comparguments -i "$autod" "$singopt[@]" "$@"; then local action noargs aret expl local tried ret=1 local next direct odirect equal single matcher matched ws tmp1 tmp2 tmp3 local opts subc tc prefix suffix descrs actions subcs anum local origpre="$PREFIX" origipre="$IPREFIX" nm="$compstate[nmatches]" if comparguments -D descrs actions subcs; then if comparguments -O next direct odirect equal; then opts=yes _tags "$subcs[@]" options else _tags "$subcs[@]" fi else if comparguments -a; then noargs='no more arguments' else noargs='no arguments' fi if comparguments -O next direct odirect equal; then opts=yes _tags options elif [[ $? -eq 2 ]]; then compadd -Q - "${PREFIX}${SUFFIX}" return 0 else _message "$noargs" return 1 fi fi comparguments -M matcher context=() state=() while true; do while _tags; do anum=1 if [[ -z "$tried" ]]; then while [[ anum -le $#descrs ]]; do action="$actions[anum]" descr="$descrs[anum]" subc="$subcs[anum++]" if [[ -n "$matched" ]] || _requested "$subc"; then curcontext="${oldcontext%:*}:$subc" _description "$subc" expl "$descr" if [[ "$action" = \=\ * ]]; then action="$action[3,-1]" words=( "$subc" "$words[@]" ) (( CURRENT++ )) fi if [[ "$action" = -\>* ]]; then action="${${action[3,-1]##[ ]#}%%[ ]#}" if (( ! $state[(I)$action] )); then comparguments -W line opt_args state=( "$state[@]" "$action" ) if [[ -n "$usecc" ]]; then curcontext="${oldcontext%:*}:$subc" else context=( "$context[@]" "$subc" ) fi compstate[restore]='' aret=yes fi else if [[ -z "$local" ]]; then local line typeset -A opt_args local=yes fi comparguments -W line opt_args if [[ "$action" = \ # ]]; then # An empty action means that we should just display a message. _message -e "$subc" "$descr" mesg=yes tried=yes alwopt=${alwopt:-yes} elif [[ "$action" = \(\(*\)\) ]]; then # ((...)) contains literal strings with descriptions. eval ws\=\( "${action[3,-3]}" \) _describe -t "$subc" "$descr" ws -M "$matcher" "$subopts[@]" || alwopt=${alwopt:-yes} tried=yes elif [[ "$action" = \(*\) ]]; then # Anything inside `(...)' is added directly. eval ws\=\( "${action[2,-2]}" \) _all_labels "$subc" expl "$descr" compadd "$subopts[@]" -a - ws || alwopt=${alwopt:-yes} tried=yes elif [[ "$action" = \{*\} ]]; then # A string in braces is evaluated. while _next_label "$subc" expl "$descr"; do eval "$action[2,-2]" && ret=0 done (( ret )) && alwopt=${alwopt:-yes} tried=yes elif [[ "$action" = \ * ]]; then # If the action starts with a space, we just call it. eval "action=( $action )" while _next_label "$subc" expl "$descr"; do "$action[@]" && ret=0 done (( ret )) && alwopt=${alwopt:-yes} tried=yes else # Otherwise we call it with the description-arguments. eval "action=( $action )" while _next_label "$subc" expl "$descr"; do "$action[1]" "$subopts[@]" "$expl[@]" "${(@)action[2,-1]}" && ret=0 done (( ret )) && alwopt=${alwopt:-yes} tried=yes fi fi fi done fi if _requested options && [[ -z "$hasopts" && -z "$matched" && ( -z "$aret" || "$PREFIX" = "$origpre" ) ]] && { ! zstyle -T ":completion:${oldcontext%:*}:options" prefix-needed || [[ "$origpre" = [-+]* || -z "$aret$mesg$tried" ]] } ; then local prevpre="$PREFIX" previpre="$IPREFIX" prevcontext="$curcontext" curcontext="${oldcontext%:*}:options" hasopts=yes PREFIX="$origpre" IPREFIX="$origipre" if [[ -z "$alwopt" || -z "$tried" || "$alwopt" = arg ]] && comparguments -s single; then if [[ "$single" = direct ]]; then _all_labels options expl option \ compadd -QS '' - "${PREFIX}${SUFFIX}" elif [[ -z "$optarg" && "$single" = next ]]; then _all_labels options expl option \ compadd -Q - "${PREFIX}${SUFFIX}" elif [[ "$single" = equal ]]; then _all_labels options expl option \ compadd -QqS= - "${PREFIX}${SUFFIX}" else tmp1=( "$next[@]" "$direct[@]" "$odirect[@]" "$equal[@]" ) [[ "$PREFIX" = [-+]* ]] && tmp1=( "${(@M)tmp1:#${PREFIX[1]}*}" ) [[ "$single" = next ]] && tmp1=( "${(@)tmp1:#[-+]${PREFIX[-1]}((#e)|:*)}" ) [[ "$PREFIX" != --* ]] && tmp1=( "${(@)tmp1:#--*}" ) tmp3=( "${(M@)tmp1:#[-+]?[^:]*}" ) tmp1=( "${(M@)tmp1:#[-+]?(|:*)}" ) tmp2=( "${PREFIX}${(@M)^${(@)${(@)tmp1%%:*}#[-+]}:#?}" ) _describe -O option \ tmp1 tmp2 -Q -S '' -- \ tmp3 -Q [[ -n "$optarg" && "$single" = next && nm -eq $compstate[nmatches] ]] && _all_labels options expl option \ compadd -Q - "${PREFIX}${SUFFIX}" fi single=yes else next=( "$next[@]" "$odirect[@]" ) _describe -O option \ next -Q -M "$matcher" -- \ direct -QS '' -M "$matcher" -- \ equal -QqS= -M "$matcher" fi PREFIX="$prevpre" IPREFIX="$previpre" curcontext="$prevcontext" fi [[ -n "$tried" && "${${alwopt:+$origpre}:-$PREFIX}" != [-+]* ]] && break done if [[ -n "$opts" && -z "$aret" && -z "$matched" && ( -z "$tried" || -n "$alwopt" ) && nm -eq compstate[nmatches] ]]; then PREFIX="$origpre" IPREFIX="$origipre" prefix="${PREFIX#*\=}" suffix="$SUFFIX" PREFIX="${PREFIX%%\=*}" SUFFIX='' compadd -M "$matcher" -D equal - "${(@)equal%%:*}" if [[ $#equal -eq 1 ]]; then PREFIX="$prefix" SUFFIX="$suffix" IPREFIX="${IPREFIX}${equal[1]%%:*}=" matched=yes comparguments -L "${equal[1]%%:*}" descrs actions subcs _tags "$subcs[@]" continue fi fi break done [[ -z "$aret" || -z "$usecc" ]] && curcontext="$oldcontext" if [[ -n "$aret" ]]; then [[ -n $rawret ]] && return 300 ### Returning non-zero would allow the calling function to add its own ### completions if we generated only options and have to use a ->state ### action. But if that then doesn't generate matches, the calling ### function's return value would be wrong unless it compares ### $compstate[nmatches] to its previous value. Ugly. ### ### return 1 else [[ -n "$noargs" && nm -eq "$compstate[nmatches]" ]] && _message "$noargs" fi # Set the return value. [[ nm -ne "$compstate[nmatches]" ]] else return 1 fi