#autoload # This function tries to automatically complete long option names. For # this it invokes the command from the line with the `--help' option # and then parses the output to find possible option names, so you # should be careful to make sure that this function is not called for # a command that does not support this option. # # For options that get an argument after a `=', the function also tries # to automatically find out what should be complete as the argument. # The possible completions for option-arguments can be described with # the arguments to this function. This is done by giving pairs of # patterns and actions as consecutive arguments. The actions specify # what should be done to complete arguemts of those options that match # the pattern. The action may be a list of words in brackets or in # parentheses, separated by spaces. A list in brackets denotes # possible values for an optional argument, a list in parentheses # gives words to complete for mandatory arguments. If the action does # not start with a bracket or parentheses, it should be the name of a # command (probably with arguments) that should be invoked to complete # after the equal sign. E.g.: # # _long_options '*\*' '(yes no)' \ # '*=FILE*' '_files' \ # '*=DIR*' '_files -/' # # This makes `yes' and `no' be completed as the argument of options # whose description ends in a star, file names for options that # contain the substring `=FILE' in the description, and paths for # options whose description contains `=DIR'. Note the last two # patterns are not needed since this function always completes files # for option descriptions containing `=FILE' and paths for option # descriptions that contain `=DIR' or `=PATH'. These builtin patterns # can be overridden by patterns given as arguments, though. # # This function accepts following options: # # -t do completion only on words starting with two hyphens # # -i list of patterns. Options, matching these patterns, are ignored. # The list may be given as array name or as literal list in braces. # E.g. _long_options -i '(--(enable|disable)-FEATURE*)' will ignore # --enable-FEATURE, that is listed in configure help output # # -s list of pattern/replacement pairs. The argument is the same as above. # E.g. configure often lists only --enable but accepts both # --enable and --disable options. # _long_options -s '(#--enable- --disable)' will accept both forms. # # This function also accepts the `-X', `-J', and `-V' options which # are given to `compadd'. local opt expl group test i name action ret=1 tmp suffix iopts sopts setopt extendedglob # Get the options. group=() expl=() if [[ $1 = -*~--* ]]; then while getopts "J:V:X:ti:s:" opt; do case "$opt" in [JV]) group=("-$opt" "$OPTARG");; X) expl=(-X "$OPTARG");; t) test=yes;; i) if [[ "$OPTARG[1]" = '(' ]]; then iopts=( ${=OPTARG[2,-2]} ) else iopts=( ${(P)${OPTARG}} ) fi ;; s) if [[ "$OPTARG[1]" = '(' ]]; then sopts=( ${=OPTARG[2,-2]} ) else sopts=( ${(P)${OPTARG}} ) fi ;; esac done shift OPTIND-1 fi # Test if we are completing after `--' if we were asked to do so. [[ -n "$test" && "$PREFIX" != --* ]] && return 1 # We cache the information about options and the command name, see if # we can use the cache. if [[ "$words[1]" = (.|..)/* ]]; then tmp="$PWD/$words[1]" else tmp="$words[1]" fi if [[ "$tmp" != $_lo_cache_cmd ]]; then # No, store the new command name and clear the old parameters. _lo_cache_cmd="$tmp" (( $+_lo_cache_actions )) && unset "$_lo_cache_names[@]" _lo_cache_actions _lo_cache_names local opts pattern anum=1 tmpo str typeset -U opts # 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 int # 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 comma after the # option up to the end. opts=("--${(@)^${(@)${(@M)${(@ps:\n:j:\n:)${(@)${(@M)${(@f)$("$words[1]" --help)}:#[ ]#-*}//,/ }}:#[ ]#--*}#*--}%%[, ]*}") # Now remove all ignored options ... while (($#iopts)) ; do opts=( ${opts:#$~iopts[1]} ) shift iopts done # ... and add "same" options while (($#sopts)) ; do opts=( $opts ${opts/$sopts[1]/$sopts[2]} ) shift 2 sopts done # The interpretation of the options is completely table driven. We # use the positional parameters we were given and a few standard # ones. Then we loop through this table. set -- "$@" '*=FILE*' '_files' '*=(DIR|PATH)*' '_files -/' '*' '' while [[ $# -gt 1 ]]; do # First, we get the pattern and the action to use and take them # from the positional parameters. pattern="$1" action="$2" shift 2 # 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)opts:##$~pattern}") opts=("${(@)opts:##$~pattern}") (( $#tmp )) || continue # Now we collect the options for the pattern in an array. We also # check if the options take an argument after a `=', and if this # argument is optional. The name of the array built contains # `_arg_' for mandatory arguments, `_optarg_' for optional # arguments, and `_simple_' for options that don't get an # argument. In `_lo_cache_names' we save the names of these # arrays and in `_lo_cache_actions' the associated actions. # If the action is a list of words in brackets, this denotes # options that get an optional argument. If the action is a list # of words in parentheses, the option has to get an argument. # In both cases we just build the array name to use. if [[ "$action[1]" = '[' ]]; then name="_lo_cache_optarg_$anum" elif [[ "$action[1]" = '(' ]]; then name="_lo_cache_arg_$anum" else # If there are option strings with a `[=', we take make these # get an optional argument... tmpo=("${(@M)tmp:#*\[\=*}") if (( $#tmpo )); then # ...by removing them from the option list and storing them in # an array. tmp=("${(@)tmp:#*\[\=*}") tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}") _lo_cache_names[anum]="_lo_cache_optarg_$anum" _lo_cache_actions[anum]="$action" eval "_lo_cache_optarg_${anum}=(\"\$tmpo[@]\")" (( anum++ )) fi # Now we do the same for option strings containing `=', these # are options getting an argument. tmpo=("${(@M)tmp:#*\=*}") if (( $#tmpo )); then tmp=("${(@)tmp:#*\=*}") tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}") _lo_cache_names[anum]="_lo_cache_arg_$anum" _lo_cache_actions[anum]="$action" eval "_lo_cache_arg_${anum}=(\"\$tmpo[@]\")" (( anum++ )) fi # The name for the options without arguments, if any. name="_lo_cache_simple_$anum" fi # Now filter out any option strings we don't like and stuff them # in an array, if there are still some. tmp=("${(@)${(@)tmp%%\=*}//[^a-zA-Z0-9-]}") if (( $#tmp )); then _lo_cache_names[anum]="$name" _lo_cache_actions[anum]="$action" eval "${name}=(\"\$tmp[@]\")" (( anum++ )) fi done fi # We get the string from the line and and see if it already contains a # equal sign. str="$PREFIX$SUFFIX" if [[ "$str" = *\=* ]]; then # It contains a `=', now we ignore anything up to it, but first save # the old contents of the special parameters we change. local oipre opre osuf pre parto parta pat patflags anum=1 oipre="$IPREFIX" opre="$PREFIX" osuf="$SUFFIX" pre="${str%%\=*}" # Then we walk through the array names. For each array we test if it # contains the option string. If so, we `invoke' the action stored # with the name. If the action is a list of words, we just add them, # otherwise we invoke the command or function named. for name in "$_lo_cache_names[@]"; do action="$_lo_cache_actions[anum]" if (( ${(@)${(@P)name}[(I)$pre]} )); then IPREFIX="${oipre}${pre}=" PREFIX="${str#*\=}" SUFFIX="" if [[ "$action[1]" = (\[|\() ]]; then compadd - ${=action[2,-2]} elif (( $#action )); then $=action fi # We found the option string, return. return fi # The array did not contain the full option string, see if it # contains a string matching the string from the line. # If there is one, we store the option string in `parto' and the # element from `_lo_actions' in `parta'. If we find more than one # such option or if we already had one, we set `parto' to `-'. PREFIX="${str%%\=*}" SUFFIX="" compadd -O tmp -M 'r:|-=* r:|=*' - "${(@P)name}" if [[ $#tmp -eq 1 ]]; then if [[ -z "$parto" ]]; then parto="$tmp[1]" parta="$action" else parto=- fi elif (( $#tmp )); then parto=- fi (( anum++ )) done # If we found only one matching option, we accept it and immediatly # try to complete the string after the `='. if [[ -n "$parto" && "$parto" != - ]]; then IPREFIX="${oipre}${parto}=" PREFIX="${str#*\=}" SUFFIX="" if (( $#parta )); then if [[ "$parta[1]" = (\[|\() ]]; then compadd - ${=parta[2,-2]} else $=parta fi else compadd -S '' - "$PREFIX" fi return fi # The option string was not found, restore the special parameters. IPREFIX="$oipre" PREFIX="$opre" SUFFIX="$osuf" fi # The string on the line did not contain a `=', or we couldn't # complete the option string since there were more than one matching # what's on the line. So we just add the option strings as possible # matches, giving the string from the `=' on as a suffix. if [[ "$str" = *\=* ]]; then str="=${str#*\=}" PREFIX="${PREFIX%%\=*}" suffix=() else str="" suffix=('-S=') fi anum=1 for name in "$_lo_cache_names[@]"; do action="$_lo_cache_actions[anum]" if [[ "$name" = *_optarg_* ]]; then compadd -M 'r:|-=* r:|=*' -Qq "$suffix[@]" -s "$str" - \ "${(@P)name}" && ret=0 elif [[ "$name" = *_arg_* ]]; then compadd -M 'r:|-=* r:|=*' -Q "$suffix[@]" -s "$str" - \ "${(@P)name}" && ret=0 elif [[ -z "$str" ]]; then compadd -M 'r:|-=* r:|=*' -Q - \ "${(@P)name}" && ret=0 fi (( anum++ )) done return ret