#autoload # Complete the arguments of the current command according to the # descriptions given as arguments to this function. local long cmd="$words[1]" descr odescr mesg subopts opt opt2 usecc autod local oldcontext="$curcontext" hasopts rawret optarg singopt alwopt local setnormarg start rest local -a match mbegin mend integer opt_args_use_NUL_separators=0 subopts=() singopt=() while [[ "$1" = -([AMO]*|[0CRSWnsw]) ]]; do case "$1" in -0) opt_args_use_NUL_separators=1; shift ;; -C) usecc=yes; shift ;; -O) subopts=( "${(@P)2}" ); shift 2 ;; -O*) subopts=( "${(@P)${1[3,-1]}}" ); shift ;; -R) rawret=yes; shift;; -n) setnormarg=yes; NORMARG=-1; shift;; -w) optarg=yes; shift;; -W) alwopt=arg; shift;; -[Ss]) singopt+=( $1 ); shift;; -[AM]) singopt+=( $1 $2 ); shift 2 ;; -[AM]*) singopt+=( $1 ); shift ;; esac done [[ $1 = ':' ]] && shift singopt+=( ':' ) # always end with ':' to indicate the end of options [[ "$PREFIX" = [-+] ]] && alwopt=arg long=$argv[(I)--] if (( long )); then local name tmp tmpargv tmpargv=( "${(@)argv[1,long-1]}" ) # optspec's before --, if any name=${~words[1]} 2>/dev/null [[ "$name" = [^/]*/* ]] && name="$PWD/$name" name="_args_cache_${name}" name="${name//[^a-zA-Z0-9_]/_}" if (( ! ${(P)+name} )); then local iopts sopts lflag pattern tmpo dir cur cache typeset -Ua 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" = -[lis]* ]]; do if [[ "$1" = -l ]]; then lflag='-l' shift continue fi 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+=( "$tmp[@]" ) else 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. tmp=() _call_program $lflag options ${~words[1]} --help 2>&1 | while IFS= read -r opt; do if (( ${#tmp} )); then # Previous line had no comment. Is the current one suitable? # It's hard to be sure, but if it there was nothing on the # previous line and the current one is indented more than # a couple of spaces (and isn't completely whitespace or punctuation) # there's a pretty good chance. if [[ $opt = [[:space:]][[:space:]][[:space:]]*[[:alpha:]]* ]]; then # Assume so. opt=${opt##[[:space:]]##} # Same substitution as below. lopts+=("${^tmp[@]}":${${${opt//:/-}//\[/(}//\]/)}) tmp=() # Finished with this line. continue else # Still no comment, add the previous options anyway. # Add a ':' after the option anyways, to make the matching of # the options lateron work as intended. # It will be removed again later. lopts+=("${^tmp[@]}":) tmp=() fi fi while [[ $opt = [,[:space:]]#(#b)(-[^,[:space:]]#)(*) ]]; do # We used to remove the brackets from "[=STUFF]", # but later the code appears to handle it with the brackets # present. Maybe the problem was that the intervening code # didn't. If it's buggy without removing them, the problem # probably is later, not here. start=${match[1]} rest=${match[2]} if [[ -z ${tmp[(r)${start%%[^a-zA-Z0-9_-]#}]} ]]; then # variant syntax seen in fetchmail: # --[fetch]all means --fetchall or --all. # maybe needs to be more general if [[ $start = (#b)(*)\[(*)\](*) ]]; then tmp+=("${match[1]}${match[2]}${match[3]}" "${match[1]}${match[3]}") else tmp+=($start) fi fi opt=$rest done # If there's left over text, assume it's a description; it # may be truncated but if it's too long it's no use anyway. # There's one hiccup: we sometimes get descriptions like # --foo fooarg Do some foo stuff with foo arg # and we need to remove fooarg. Use whitespace for hints. opt=${opt## [^[:space:]]## } opt=${opt##[[:space:]]##} if [[ -n $opt ]]; then # Add description after a ":", converting any : in the description # to a -. Use RCQUOTES to append this to all versions of the option. lopts+=("${^tmp[@]}":${${${opt//:/-}//\[/(}//\]/)}) tmp=() # If there's no comment, we'll see if there's one on the # next line. fi done # Tidy up any remaining uncommented options. if (( ${#tmp} )); then lopts+=("${^tmp[@]}":) fi # Remove options also described by user-defined specs. tmp=() # Ignore any argument and description information when searching # the long options array here and below. for opt in "${(@)${(@)lopts:#--}%%[\[:=]*}"; do # Using (( ... )) gives a parse error. let "$tmpargv[(I)(|\([^\)]#\))(|\*)${opt}(|[-+]|=(|-))(|\[*\])(|:*)]" || 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 # This implements adding things like --disable-* based # on the existence of --enable-*. # TODO: there's no anchoring here, is that correct? # If it's not, careful with the [\[:=]* stuff. 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. argv+=( '*=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. # This is the first bit of the arguments in the special form # for converting --help texts, taking account of any quoting # of colons. pattern="${${${(M)1#*[^\\]:}[1,-2]}//\\\\:/:}" # Any action specifications that go with it. descr="${1#${pattern}}" if [[ "$pattern" = *\(-\) ]]; then # This is the special form to disallow arguments # in the next word. 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. # Ignore :descriptions at the ends of lopts for matching this; # they aren't in the patterns. tmp=("${(@M)lopts:##$~pattern:*}") lopts=("${(@)lopts:##$~pattern:*}") (( $#tmp )) || continue opt='' # Clean suffix ':' added earlier tmp=("${(@)tmp%:}") # If there are option strings with a `[=', we take these to get an # optional argument. tmpo=("${(@M)tmp:#[^:]##\[\=*}") if (( $#tmpo )); then tmp=("${(@)tmp:#[^:]##\[\=*}") for opt in "$tmpo[@]"; do # Look for --option:description and turn it into # --option[description]. We didn't do that above # since it could get confused with the [=ARG] stuff. if [[ $opt = (#b)(*):([^:]#) ]]; then opt=$match[1] odescr="[${match[2]}]" else odescr= fi if [[ $opt = (#b)(*)\[\=* ]]; then opt2=${${match[1]}//[^a-zA-Z0-9_-]}=-${dir}${odescr} else opt2=${${opt}//[^a-zA-Z0-9_-]}=${dir}${odescr} fi if [[ "$descr" = :\=* ]]; then cache+=( "${opt2}::${(L)${opt%\]}#*\=}: " ) elif [[ "$descr" = ::* ]]; then cache+=( "${opt2}${descr}" ) else cache+=( "${opt2}:${descr}" ) fi done fi # Descriptions with `=': mandatory argument. # Basically the same as the foregoing. # TODO: could they be combined? tmpo=("${(@M)tmp:#[^:]##\=*}") if (( $#tmpo )); then tmp=("${(@)tmp:#[^:]##\=*}") for opt in "$tmpo[@]"; do if [[ $opt = (#b)(*):([^:]#) ]]; then opt=$match[1] odescr="[${match[2]}]" else odescr= fi opt2="${${opt%%\=*}//[^a-zA-Z0-9_-]}=${dir}${odescr}" if [[ "$descr" = :\=* ]]; then cache+=( "${opt2}:${(L)${opt%\]}#*\=}: " ) else cache+=( "${opt2}${descr}" ) fi done fi # Everything else is just added as an option without arguments or # as described by $descr. if (( $#tmp )); then tmp=( # commands with a description of the option (as opposed # to the argument, which is what descr contains): needs to be # "option[description]". # Careful: \[ on RHS of substitution keeps the backslash, # I discovered after about half an hour, so don't do that. "${(@)^${(@)tmp:#^*:*}//:/[}]" # commands with no description "${(@)${(@)tmp:#*:*}//[^a-zA-Z0-9_-]}") if [[ -n "$descr" && "$descr" != ': : ' ]]; then cache+=( "${(@)^tmp}${descr}" ) else cache+=( "$tmp[@]" ) fi fi done set -A "$name" "${(@)cache:# #}" fi set -- "$tmpargv[@]" "${(@P)name}" fi 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=() state_descr=() 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 [[ $subc = argument* && -n $setnormarg ]]; then comparguments -n NORMARG fi 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 $opt_args_use_NUL_separators state+=( "$action" ) state_descr+=( "$descr" ) if [[ -n "$usecc" ]]; then curcontext="${oldcontext%:*}:$subc" else 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 $opt_args_use_NUL_separators 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+=( "$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