Zsh completion arguments

From wikinotes

The _arguments function is one method available for producing completions.

Cheat Sheet

'-o[output file]'            # `foo -o`
{-h,--help}'[show help]'    # `foo -h`  OR  `foo --help`
'-u[choose user]:::_users'  # `foo -u $USER` (completes from os users)
'1:foo:_normal'             # positional arg $1 completes to anything (usually defaults to files)
'2:bar:_users'              # positional arg $2 completes to os users
'3:baz:->my-state-name'     # $state assigned val 'my-state-name' (use case to set completer)
'*:foo:(one two three)'     # completes 1x optional un-named param (rest arg) to 'one', 'two' or 'three'
'*::foo:(one two three)'    # unlimited optional un-named params (rest args) to 'one', 'two' or 'tree'

Syntax

Basics

arguments \
  '( ${excludes} )${param}:[:]${message}:${action}'
# ${excludes} is an array of param ${state_descr}s
#    arguments \
#      '::one:_normal' \
#      '::two:_normal' \
#      '(one)::three:(foo bar baz)'   
#
# don't complete 'three' if 'one' has been used
# ${param} has different formats depending the param
# (or the next param, depending on param type)
#    ex: '-h[show help]'  # flag
#    ex: '1'              # positional
#    ex: '*'              # rest argument(s)
#    ex: ''               # optional
# ':' vs '::' 
#    here ${param}:${message}:${action}  is a required param
#    here ${param}::${message}:${action} is an optional param
# ${message} is the name of the param
#    this is the name of the param, when referred to in other completer functions.
#    it gets bound to ${state_descr}.
# ${action} is instructions on how to complete the next param
#    '1:one:'                  # ? valid ?
#    '1:one:(one two three)'   # argument must be one, two, or three
#    '1:one:_user'             # argument uses '_user' (function) to provide completions

Flag Arguments

_arguments \
   {-h,--help}'[show help]' \
       # param:   -h/--help
       # desc:    show help
       # action:  (none)
   '-o[output file]:::_files' \
       # param:   -o
       # desc:    output file
       # action:  (complete any file)

Positional Arguments

# ex: ${position}:${state_descr}:${action}

_arguments \
    '1:user:_users' \              # 1st positional arg (required)
    '2:priority:(low med high)' \  # 2nd positional arg (required) (complete to low/med/high)
    '3::group:_groups' \           # 3nd positional arg (optional)

The argument-name is bound to $state_descr.

Value for last defined Param

:${message}:${action}   # reqd completion for prev command
::${message}:${action}  # optional completion for prev command
_arguments \
    {-h,--help}'[show help]' \
    '*::extra:(foo bar baz)' \  # any extra parameters, (complete to (foo bar baz))
    ':value:(foo bar baz)'      # after prev param (extra), always complete one of these words

Rest Arguments

Any arguments not consumed earlier use this completer.

*:${message}:${action}    # 
*::${message}:${action}   # 
*:::${message}:${action}  #

State Setting/Parsing

The -C argument enables state-setting.
This lets you determine the parser that gets used based on the state you set.

local state               # scope state locally

_arguments -C \
    '*:extra:->my-state'  # sets `$state='my-state'`

case $state in
    (my-state)
        _arguments '1:foo:(one two three four)'
        ;;
esac

Strategies

TODO:

This section is unclear, requires experimentation and clarity

Subparsers

Sometimes, the use of a particular flag opens up a chain of possible sub-commands. (ex: git add, git commit, ...). This can be handled in ZSH as follows:

#compdef wtest

_wtest() {
    # limit scope of variables used by zsh completer
    local context state line expl implementation

    # list of subparsers
    local -a subparsers
    subparsers=(archive display data)

    # Determine completer for subcommand
    # (based on 1st positional argument)
    _arguments -C \
        {-h,--help}'[show help information]' \
        '1:subcommand:compadd -a subparsers' \
        '*:: :->subcmd' && return

    # parsers for each subparser
    subparser="$words[1]"
    curcontext="${curcontext%:*}-$subparser:"  # <-- unecessary (?)
    case $subparser in
    (archive)
        _arguments -A "-*" \
            '-a[append files to archive]' \
            '-e[extract files to archive]'
        ;;
    (display)
        _arguments -A "-*" \
            '-n[next file in archive]' \
            '-p[previous file in archive]'
        ;;
    (data)
        _arguments -A "-*" \
            '-s[shuffle order of wallpapers]' 
        ;;
    (*)
        _message "unknown sub-command: $subparser"
        ;;
    esac
}

_wtest "$@"

Dynamic Arguments

Some Background Info:

  • $words[] is an array of all arguments used so far
  • $state is set in _arguments. In the below example. If completion is used for '1', the value of $state will be 'country'
hello COUNTRY CITY  # (where completed city belongs to selected country)
#compdef hello
 
_hello() { 
    # limit scope of zsh vars bound by completer
    local curcontext="$curcontext" state line
    typeset -A opt_args

    _arguments \
        '1: :->country' \
        '*: :->city'

    case $state in
    country)
        _arguments '1:Countries:(France Germany Italy)'
    ;;
    *)
        case $words[2] in
        France)
            compadd "$@" Paris Lyon Marseille
        ;;
        Germany)
            compadd "$@" Berlin Munich Dresden
        ;;
        Italy)
            compadd "$@" Rome Napoli Palermo
        ;;
        *)
            _files 
        esac
    esac
}
 
_hello "$@"

Params without Flags

I don't think zsh _arguments supports params without - as a prefix.
This is a workaround, treating optional key/val params without - as subparsers.

foo ACTION [user USER] [group GROUP] [-h|--help]   # completes
foo ACTION [subparser1|subparser2]                 # redirects to subparser to complete
#compdef foo

_foo() {
    local context state state_descr line
    typeset -A opt_args

    _arguments  -C \
        '1:action:->action' \
        '*:extra:->extra' \
        {-h,--help}'[show help]' \

    case $state in
        (extra)
            case $words[-1] in
                subparser1)
                    # call completer for function for subparser1
                    ;;
                subparser2)
                    # call completer for function for subparser2
                    ;;
                user|group)
                    # last param was user or group.
                    # complete their value.
                    _arguments \
                        '*:: :_normal'
                    ;;
                *)
                    # anything else, suggest key/val params
                    _arguments \
                        '*:: :(user group)'
                    ;;
            esac
            ;;
        *)
            ;;
    esac
}

_foo