#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 # Associative arrays used to collect information about the options. typeset -A opts mopts dopts dmopts odopts odmopts # 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 dopts[${${1%%:*}[1,-2]}]="${1#*:}" elif [[ "$1" = [^:]##+:* ]]; then odopts[${${1%%:*}[1,-2]}]="${1#*:}" else opts[${1%%:*}]="${1#*:}" fi else opts[$1]='' fi elif [[ "$1" = \*[-+]* ]]; then # The same for options that may appear more than once. if [[ "$1" = *:* ]]; then if [[ "$1" = [^:]##-:* ]]; then dmopts[${${1[2,-1]%%:*}[1,-2]}]="${1#*:}" elif [[ "$1" = [^:]##+:* ]]; then odmopts[${${1[2,-1]%%:*}[1,-2]}]="${1#*:}" else mopts[${1[2,-1]%%:*}]="${1#*:}" fi else mopts[${1[2,-1]}]='' fi 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 [[ $#long -ne 0 && "$PREFIX" = --* ]]; then # If the current words starts with `--' and we should use long # options, just call... _long_options "$long[@]" else # Otherwise parse the command line... ws=( "${(@)words[2,-1]}" ) cur=$(( CURRENT-2 )) nth=1 # ...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='' # Remove one description/action pair from `def' if that isn't empty. if [[ -n "$def" ]]; then if [[ "$def" = ?*:*:* ]]; then def="${def#?*:*:}" else def='' fi else # 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]]" unset "opts[$ws[1]]" elif (( $+mopts[$ws[1]] )); then def="$mopts[$ws[1]]" else uns='' if [[ -n "$single" && "$ws[1]" = [-+]* ]]; then if [[ ${#${(kj::)opts[(R)?*]#?}} -ne 0 && "$ws[1]" = ${ws[1][1]}*[${(kj::)opts[(R)?*]#?}] ]]; then def="$opts[${ws[1][1]}${ws[1][-1]}]" uns="${ws[1][2,-1]}" opt='' elif [[ ${#${(kj::)mopts[(R)?*]#?}} -ne 0 && "$ws[1]" = ${ws[1][1]}*[${(kj::)mopts[(R)?*]#?}] ]]; then def="$mopts[${ws[1][1]}${ws[1][-1]}]" 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 four 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 "$single" ]]; then if [[ "$ws[1]" = ${tmp[1][1]}*${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]]" [[ -z "$single" ]] && unset "dopts[$tmp[1]]" if [[ "$def" = ?*:*:* ]]; then def="${def#?*:*:}" else def='' fi fi fi if [[ -n "$opt" && $#dmopts -ne 0 ]]; then tmp=( "${(@k)dmopts}" ) while (( $#tmp )); do if [[ -n "$single" ]]; then if [[ "$ws[1]" = ${tmp[1][1]}*${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="$dmopts[$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 "$single" ]]; then if [[ "$ws[1]" = ${tmp[1][1]}*${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]]" [[ -z "$single" ]] && 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 "$single" && "$ws[1]" != "$tmp[1]" ) || ( -n "$single" && "$ws[1]" != *${tmp[1][2]} ) ]]; then if [[ "$def" = ?*:*:* ]]; then def="${def#?*:*:}" else def='' fi fi fi fi if [[ -n "$opt" && $#odmopts -ne 0 ]]; then tmp=( "${(@k)odmopts}" ) while (( $#tmp )); do if [[ -n "$single" ]]; then if [[ "$ws[1]" = ${tmp[1][1]}*${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="$odmopts[$tmp[1]]" if [[ ( -z "$single" && "$ws[1]" != "$tmp[1]" ) || ( -n "$single" && "$ws[1]" != *${tmp[1][2]} ) ]]; then if [[ "$def" = ?*:*:* ]]; then def="${def#?*:*:}" else def='' fi fi fi fi [[ -n "$single" && -n "$opt" && "$ws[1]" = [-+]* ]] && \ uns="${ws[1][2,-1]}" [[ -n "$uns" ]] && unset "opts[${(@)^opts[(I)${ws[1][1]}[$uns]]}]" \ "dopts[${(@)^dopts[(I)${ws[1][1]}[$uns]]}]" \ "odopts[${(@)^odopts[(I)${ws[1][1]}[$uns]]}]" # 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='' (( nth++ )) fi fi fi shift 1 ws (( cur-- )) done # Now generate the matches. 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" fi # In any case, we have to complete option names here, but we may # be in a string that starts with an option names and continues with # the first argument, test that (again, four loops). opt=yes if (( $#dopts )); then # Get the option names. tmp=( "${(@k)dopts}" ) while (( $#tmp )); do if [[ -n "$single" ]] && compset -P "${tmp[1][1]}*${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" && $#dmopts -ne 0 ]]; then tmp=( "${(@k)dmopts}" ) while (( $#tmp )); do if [[ -n "$single" ]] && compset -P "${tmp[1][1]}*${tmp[1][2]}"; then def="$dmopts[$tmp[1]]" opt='' uns="${PREFIX[2,-1]%%${tmp[1][2]}*}${tmp[1][2]}" break elif compset -P "$tmp[1]"; then def="$dmopts[$tmp[1]]" opt='' break fi shift 1 tmp done fi if [[ -n "$opt" && $#odopts -ne 0 ]]; then tmp=( "${(@k)odopts}" ) while (( $#tmp )); do if [[ -n "$single" ]] && compset -P "${tmp[1][1]}*${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 if [[ -n "$opt" && $#odmopts -ne 0 ]]; then tmp=( "${(@k)odmopts}" ) while (( $#tmp )); do if [[ -n "$single" ]] && compset -P "${tmp[1][1]}*${tmp[1][2]}"; then def="$odmopts[$tmp[1]]" opt='' uns="${PREFIX[2,-1]%%${tmp[1][2]}*}${tmp[1][2]}" break elif compset -P "$tmp[1]"; then def="$odmopts[$tmp[1]]" opt='' break fi shift 1 tmp done fi [[ -n "$single" && -n "$opt" && "$PREFIX" = [-+]* ]] && \ uns="${PREFIX[2,-1]}" [[ -n "$uns" ]] && unset "opts[${(@)^opts[(I)${pre[1]}[$uns]]}]" \ "dopts[${(@)^dopts[(I)${pre[1]}[$uns]]}]" \ "odopts[${(@)^odopts[(I)${pre[1]}[$uns]]}]" 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 "$single" ]]; then if [[ -z "$PREFIX" || "$PREFIX" = ? ]]; then compadd "$expl[@]" - "${(@k)opts}" "${(@k)mopts}" \ "${(@k)dopts}" "${(@k)dmopts}" \ "${(@k)odopts}" "${(@k)odmopts}" && ret=0 else compadd "$expl[@]" - "${PREFIX}${(@k)^opts[(I)${PREFIX[1]}*]#?}" \ "${PREFIX}${(@k)^mopts[(I)${PREFIX[1]}*]#?}" \ "${PREFIX}${(@k)^dopts[(I)${PREFIX[1]}*]#?}" \ "${PREFIX}${(@k)^dmopts[(I)${PREFIX[1]}*]#?}" \ "${PREFIX}${(@k)^odopts[(I)${PREFIX[1]}*]#?}" \ "${PREFIX}${(@k)^odmopts[(I)${PREFIX[1]}*]#?}" && ret=0 fi else compadd "$expl[@]" - "${(@k)opts}" "${(@k)mopts}" \ "${(@k)dopts}" "${(@k)dmopts}" \ "${(@k)odopts}" "${(@k)odmopts}" && 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 describing optional arguments. [[ "$def" = :* ]] && def="$def[2,-1]" # Get the description and the action. descr="${def%%:*}" action="${${def#*:}%%:*}" _description expl "$descr" if [[ -z "$action" ]]; then # An empty action means that we should just display a message. _message "$descr" return ret elif [[ "$action[1]" = \( ]]; then # Anything inside `(...)' is added directly. compadd "$expl[@]" - ${=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