#autoload # Complete the arguments of the current command according to the # descriptions given as arguments to this function. setopt localoptions extendedglob local long args rest ws cur nth def nm expl descr action opt arg tmp local single uns ret=1 soptseq soptseq1 sopts prefix line local beg optbeg argbeg # Associative arrays used to collect information about the options. typeset -A opts dopts odopts typeset -A oneshot # See if we are using single-letter options. if [[ "$1" = -s ]]; then shift single=yes fi # See if we support long options, too. nth=$argv[(I)--] if (( nth )); then long=( "${(@)argv[nth+1,-1]}" ) argv=("${(@)argv[1,nth-1]}") else long=() fi # Now parse the arguments... args=() nth=1 while (( $# )); do # This describes a one-shot option. if [[ "$1" = [-+]* ]]; then if [[ "$1" = *:* ]]; then # If the option name ends in a `-', the first argument comes # directly after the option, if it ends in a `+', the first # argument *may* come directly after the option, otherwise it # is in the next word. if [[ "$1" = [^:]##-:* ]]; then tmp="${${1%%:*}[1,-2]}" dopts[$tmp]="${1#*:}" elif [[ "$1" = [^:]##+:* ]]; then tmp="${${1%%:*}[1,-2]}" odopts[$tmp]="${1#*:}" else tmp="${1%%:*}" opts[$tmp]="${1#*:}" fi else tmp="$1" opts[$tmp]='' fi oneshot[$tmp]=yes elif [[ "$1" = \*[-+]* ]]; then # The same for options that may appear more than once. if [[ "$1" = *:* ]]; then if [[ "$1" = [^:]##-:* ]]; then tmp="${${1[2,-1]%%:*}[1,-2]}" dopts[$tmp]="${1#*:}" elif [[ "$1" = [^:]##+:* ]]; then tmp="${${1[2,-1]%%:*}[1,-2]}" odopts[$tmp]="${1#*:}" else tmp="${1[2,-1]%%:*}" opts[$tmp]="${1#*:}" fi else tmp="${1[2,-1]}" opts[$tmp]='' fi unset "oneshot[$tmp]" elif [[ "$1" = \*:* ]]; then # This is `*:...', describing `all other arguments'. rest="${1[3,-1]}" elif [[ "$1" = :* ]]; then # This is `:...', describing `the next argument'. args[nth++]="${1#*:}" else # And this is `n:...', describing the `n'th argument. args[${1%%:*}]="${1#*:}" nth=$(( ${1%%:*} + 1 )) fi shift done if [[ -n "$single" ]]; then soptseq="${(@j::)${(@M)${(@k)opts[(R)]}:#[-+]?}#[-+]}" if [[ -n "$soptseq" ]]; then soptseq="[$soptseq]#" soptseq1="$soptseq#" else soptseq='' soptseq1='' fi sopts="${(@j::)${(@M)${(@k)opts}:#[-+]?}#[-+]}${(@j::)${(@M)${(@k)dopts}:#[-+]?}#[-+]}${(@j::)${(@M)${(@k)odopts}:#[-+]?}#[-+]}" else soptseq='' soptseq1='' sopts='' fi # Parse the command line... ws=( "${(@)words[2,-1]}" ) cur=$(( CURRENT-2 )) nth=1 line=( "$words[1]" ) beg=2 argbeg=2 optbeg=2 # ...until the current word is reached. while [[ cur -gt 0 ]]; do # `def' holds the description for the option we are currently after. # Check if the next argument for the option is optional. if [[ "$def" = :* ]]; then opt=yes else opt='' fi arg='' # See if we are after an option getting n arguments ended by something # that matches the current word. if [[ "$def" = \**[^\\]:* && "$ws[1]" = ${~${(M)def#*[^\\]:}[2,-2]} ]]; then def='' shift 1 ws (( cur-- )) (( beg++ )) continue fi # Remove one description/action pair from `def' if that isn't empty. if [[ -n "$def" && "$def" != \** ]]; then if [[ "$def" = ?*[^\\]:*[^\\]:* ]]; then def="${def#?*[^\\]:*[^\\]:}" argbeg="$beg" else def='' fi elif [[ -z "$def" ]]; then # If it is empty, and the word starts with `--' and we should # complete long options, just ignore this word, otherwise make sure # we test for options below and handle normal arguments. if [[ $#long -eq 0 || "$ws[1]" != --* ]]; then opt=yes arg=yes else def='' fi fi if [[ -n "$opt" ]]; then # `opt' was set above if we have to test if the word is an option. # We first test for the simple options -- those without arguments or # those whose arguments have to be given as separate words. if (( $+opts[$ws[1]] )); then # Options that may only be given once are removed from the # associative array so that we don't offer them again. def="$opts[$ws[1]]" [[ -n "$oneshot[$ws[1]]" ]] && unset "opts[$ws[1]]" else uns='' if [[ -n "$sopts" && "$ws[1]" = [-+]${~soptseq}[$sopts] ]]; then tmp="${ws[1][1]}${ws[1][-1]}" if (( $+opts[$tmp] )); then def="$opts[$tmp]" optbeg="$beg" argbeg="$beg" uns="${ws[1][2,-1]}" opt='' fi fi # If the word is none of the simple options, test for those # whose first argument has to or may come directly after the # option. This is done in two loops looking very much alike. if [[ -n "$opt" && $#dopts -ne 0 ]]; then # First we get the option names. tmp=( "${(@k)dopts}" ) # Then we loop over them and see if the current word begins # with one of the option names. while (( $#tmp )); do if [[ -n "$sopts" && $tmp[1] = [-+]? ]]; then if [[ "$ws[1]" = ${tmp[1][1]}${~soptseq}${tmp[1][2]}* ]]; then uns="${ws[1][2,-1]%%${tmp[1][2]}*}${tmp[1][2]}" break; fi elif [[ "$ws[1]" = ${tmp[1]}* ]]; then break fi shift 1 tmp done if (( $#tmp )); then # It does. So use the description for it, but only from # the second argument on, because we are searching the # description for the next command line argument. opt='' def="$dopts[$tmp[1]]" optbeg="$beg" argbeg="$beg" [[ -n "$oneshot[$tmp[1]]" ]] && unset "dopts[$tmp[1]]" if [[ "$def" = [^*]*[^\\]:*[^\\]:* ]]; then def="${def#?*[^\\]:*[^\\]:}" else def='' fi fi fi if [[ -n "$opt" && $#odopts -ne 0 ]]; then tmp=( "${(@k)odopts}" ) while (( $#tmp )); do if [[ -n "$sopts" && $tmp[1] = [-+]? ]]; then if [[ "$ws[1]" = ${tmp[1][1]}${~soptseq}${tmp[1][2]}* ]]; then uns="${ws[1][2,-1]%%${tmp[1][2]}*}${tmp[1][2]}" break; fi elif [[ "$ws[1]" = ${tmp[1]}* ]]; then break fi shift 1 tmp done if (( $#tmp )); then opt='' def="$odopts[$tmp[1]]" optbeg="$beg" argbeg="$beg" [[ -n "$oneshot[$tmp[1]]" ]] && unset "odopts[$tmp[1]]" # For options whose first argument *may* come after the # option, we skip over the first description only if there # is something after the option name on the line. if [[ ( -z "$sopts" && "$ws[1]" != "$tmp[1]" ) || ( -n "$sopts" && ( ( $tmp[1] = [-+]? && "$ws[1]" != "${tmp[1][1]}"${~soptseq}"${tmp[1][2]}" ) || ( $tmp[1] != [-+]? && "$ws[1]" != "$tmp[1]" ) ) ) ]]; then if [[ "$def" = [^*]*[^\\]:*[^\\]:* ]]; then def="${def#?*[^\\]:*[^\\]:}" optbeg="$beg" argbeg="$beg" else def='' fi fi fi fi [[ -n "$sopts" && -n "$opt" && "$ws[1]" = [-+]${~soptseq} ]] && \ uns="${ws[1][2,-1]}" if [[ -n "$uns" ]]; then uns="${(j::)${(@k)oneshot[(I)${ws[1][1]}[$uns]]#[-+]}}" unset "opts[${(@)^opts[(I)${ws[1][1]}[$uns]]}]" \ "dopts[${(@)^dopts[(I)${ws[1][1]}[$uns]]}]" \ "odopts[${(@)^odopts[(I)${ws[1][1]}[$uns]]}]" fi # If we didn't find a matching option description and we were # told to use normal argument descriptions, just increase # our counter `nth'. if [[ -n "$opt" && -n "$arg" ]]; then def='' line=( "$line[@]" "$ws[1]" ) (( nth++ )) fi fi fi shift 1 ws (( cur-- )) (( beg++ )) done # Now generate the matches. if [[ $#long -ne 0 && "$PREFIX" = --* ]]; then # If the current words starts with `--' and we should use long # options, just call... _long_options "$long[@]" else nm="$compstate[nmatches]" if [[ -z "$def" || "$def" = :* ]]; then local pre="$PREFIX" uns='' # We either don't have a description for an argument of an option # or we have a description for a optional argument. if [[ -z "$def" ]]; then # If we have none at all, use the one for this argument position. def="$args[nth]" [[ -z "$def" ]] && def="$rest" if [[ "$def" = \** ]]; then def="${def#*[^\\]:}" [[ "$def" = :* ]] && def="$def[2,-1]" [[ "$def" = :* ]] && def="$def[2,-1]" fi fi # In any case, we have to complete option names here, but we may # be in a string that starts with an option name and continues with # the first argument, test that (again, two loops). opt=yes if (( $#dopts )); then # Get the option names. tmp=( "${(@k)dopts}" ) prefix="$PREFIX" while (( $#tmp )); do if [[ -n "$sopts" && $tmp[1] = [-+]? ]] && compset -P "${tmp[1][1]}${~soptseq}${tmp[1][2]}"; then def="$dopts[$tmp[1]]" opt='' uns="${prefix[2,-1]%%${tmp[1][2]}*}${tmp[1][2]}" break elif compset -P "$tmp[1]"; then # The current string starts with the option name, so ignore # that and complete the rest of the string. def="$dopts[$tmp[1]]" opt='' break fi shift 1 tmp done fi if [[ -n "$opt" && $#odopts -ne 0 ]]; then tmp=( "${(@k)odopts}" ) prefix="$PREFIX" while (( $#tmp )); do if [[ -n "$sopts" && $tmp[1] = [-+]? ]] && compset -P "${tmp[1][1]}${~soptseq}${tmp[1][2]}"; then def="$odopts[$tmp[1]]" opt='' uns="${prefix[2,-1]%%${tmp[1][2]}*}${tmp[1][2]}" break elif compset -P "$tmp[1]"; then def="$odopts[$tmp[1]]" opt='' break fi shift 1 tmp done fi [[ -n "$sopts" && -n "$opt" && "$PREFIX" = [-+]${~soptseq}[$sopts] ]] && \ uns="${PREFIX[2,-1]}" if [[ -n "$uns" ]]; then uns="${(j::)${(@k)oneshot[(I)${ws[1][1]}[$uns]]#[-+]}}" unset "opts[${(@)^opts[(I)${pre[1]}[$uns]]}]" \ "dopts[${(@)^dopts[(I)${pre[1]}[$uns]]}]" \ "odopts[${(@)^odopts[(I)${pre[1]}[$uns]]}]" fi if [[ -n "$opt" ]]; then # We aren't in an argument directly after a option name, so # all option names are possible matches. if [[ "$compconfig[option_prefix]" != *(short|all)* || "$PREFIX" = [-+]* ]]; then _description expl option if [[ -n "$sopts" && -n "$PREFIX" && "$PREFIX" = [-+]${~soptseq}[$sopts] ]]; then if [[ "$PREFIX" = [-+]${~soptseq1} ]]; then compadd "$expl[@]" -Q \ -y "( ${(k)opts} ${(k)dopts} ${(k)odopts} )" - \ "${PREFIX}${(@k)^opts[(I)${PREFIX[1]}?]#?}" \ "${PREFIX}${(@k)^dopts[(I)${PREFIX[1]}?]#?}" \ "${PREFIX}${(@k)^odopts[(I)${PREFIX[1]}?]#?}" && ret=0 else # The last option takes an argument in next word. compadd "$expl[@]" -Q - "${PREFIX}" && ret=0 fi else compadd "$expl[@]" -Q - "${(@k)opts}" "${(@k)odopts}" && ret=0 compadd "$expl[@]" -QS '' - "${(@k)dopts}" && ret=0 fi fi [[ $#long -ne 0 && ( "$compconfig[option_prefix]" != *(long|all)* || "$PREFIX" = --* ) ]] && \ _long_options "$long[@]" && ret=0 fi fi # Now add the matches from the description, if any. if [[ -n "$def" ]]; then # Ignore the leading colon or `*...' describing optional arguments. if [[ "$def" = :* ]]; then def="$def[2,-1]" elif [[ "$def" = \** ]]; then tmp="${${(M)def#*[^\\]:}[2,-2]}" def="${def#*[^\\]:}" if [[ "$def" = :* ]]; then if [[ "$def" = ::* ]]; then def="$def[3,-1]" beg=$argbeg else def="$def[2,-1]" beg=$optbeg fi shift beg words (( CURRENT -= beg )) if [[ -n "$tmp" ]]; then tmp="$words[(ib:CURRENT:)${~tmp}]" [[ tmp -le $#words ]] && words=( "${(@)words[1,tmp-1]}" ) fi fi fi # Get the description and the action. descr="${${${(M)def#*[^\\]:}[1,-2]}//\\\\:/:}" if [[ "$def" = *[^\\]:*[^\\]:* ]]; then action="${${${(M)${def#*[^\\]:}#*[^\\]:}[1,-2]}//\\\\:/:}" else action="${${def#*[^\\]:}//\\\\:/:}" fi _description expl "$descr" if [[ -z "$action" ]]; then # An empty action means that we should just display a message. _message "$descr" return ret elif [[ "$action" = \(*\) ]]; then # Anything inside `(...)' is added directly. compadd "$expl[@]" - ${=action[2,-2]} elif [[ "$action" = \{*\} ]]; then # A string in braces is evaluated. eval "$action[2,-2]" elif [[ "$action" = \ * ]]; then # If the action starts with a space, we just call it. ${(e)=~action} else # Otherwise we call it with the description-arguments built above. action=( $=action ) ${(e)action[1]} "$expl[@]" ${(e)~action[2,-1]} fi fi # Set the return value. [[ nm -ne "$compstate[nmatches]" ]] fi