#autoload # Complete the arguments of the current command according to the # descriptions given as arguments to this function. local long args rest ws cur nth def nm expl descr action opt arg tmp # Associative arrays used to collect information about the options. typeset -A opts mopts dopts dmopts odopts odmopts # 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 are not offered them again. def="$opts[$ws[1]]" unset "opts[$ws[1]]" elif (( $+mopts[$ws[1]] )); then def="$mopts[$ws[1]]" else # 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 (( $#dopts )); 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 [[ "$ws[1]" = ${tmp[1]}* ]] && break 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]]" 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 [[ "$ws[1]" = ${tmp[1]}* ]] && break 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 [[ "$ws[1]" = ${tmp[1]}* ]] && break shift 1 tmp done if (( $#tmp )); then opt='' def="$odopts[$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 [[ "$ws[1]" != "$tmp[1]" ]]; 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 [[ "$ws[1]" = ${tmp[1]}* ]] && break shift 1 tmp done if (( $#tmp )); then opt='' def="$odmopts[$tmp[1]]" if [[ "$ws[1]" != "$tmp[1]" ]]; then if [[ "$def" = ?*:*:* ]]; then def="${def#?*:*:}" else def='' fi fi fi 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='' (( nth++ )) fi fi fi shift 1 ws (( cur-- )) done # Now generate the matches. nm="$compstate[nmatches]" if [[ -z "$def" || "$def" = :* ]]; then # 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 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 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 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 compset -P "$tmp[1]"; then def="$odmopts[$tmp[1]]" opt='' break fi shift 1 tmp done fi if [[ -n "$opt" ]]; then # We aren't in an argument directly after a option name, so # all option names are possible matches. _description expl option compadd "$expl[@]" - "${(@k)opts}" "${(@k)mopts}" \ "${(@k)dopts}" "${(@k)dmopts}" \ "${(@k)odopts}" "${(@k)odmopts}" 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 1 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. $=action else # Otherwise we call it with the description-arguments built above. action=( $=action ) "$action[1]" "$expl[@]" "${(@)action[2,-1]}" fi fi # Set the return value. [[ nm -ne "$compstate[nmatches]" ]] fi