Search code examples
zshzsh-completion

Holding state in zsh completions


I want to write zsh completions for a program with the following calling convention:

program [generaloptions] operation [operationoptions]

where operation is one of --install, --upgrade...

What I have so far, are the general options and the operation options. My code looks something like this:

local generaloptions; generaloptions=(...)
local installoptions; installoptions=(...)
local upgradeoptions; upgradeoptions=(...)

case "$state" in
  (install)
    _arguments -s \
      "$installoptions[@]" \
      && ret=0
  (upgrade)
    _arguments -s \
      "$upgradeoption[@]" \
      && ret=0
  *)
    _arguments -s \
      "$generaloptions[@]" \
      '--install[...]: :->install' \
      '--upgrade[...]: :->upgrade' \
      && ret=0

The problem is, after I type the operation and the first operation option, the state gets reset to the *) case.

Example

$ program --install --installoption --<tab>
list of general options

How can I set the next state to be the same as the old? Which command has similar calling conventions, so I can look at the code of the completion for this command?


Solution

  • The main problem is that the operations start with a --, so it is harder to find them in the arguments. In git for example all subcommands are only a word without dashes. So git solves this problem something like this:

    1. Find the first argument without dashes because this must be the subcommand
    2. Dispatch based on the subcommand to the commandline arguments for that subcommand.

    So git dispatches in every call to the completion function (this was what I meant with "holding the state").

    The way I solved this problem was by looking through many completion functions and finding a command that had a similar calling convention. The command that I found the most useful is pacman. Here is what I extracted from that:

    # This somehow disassembles the commandline options
    args=( ${${${(M)words:#-*}#-}:#-*}
    case $args in
      *i)
        _arguments -s \
          ${installoptions} \
          '(-i[...]' \
          && ret=0
        ;;
      *u)
        _arguments -s \
          ${upgradeoption} \
          '-u[...]' \
          && ret=0
        ;;
      *)
        case ${(M)words:#--*} in
          *--install*)
            _arguments -s \
              ${installoptions} \
              '--install[...]' \
              && ret=0
          ;;
          *--upgrade*)
            _arguments -s \
              ${upgradeoption} \
              '--upgrade[...]' \
              && ret=0
          ;;
          *)
            _arguments -s \
              {generaloptions} \
              && ret=0
          ;;
        esac
     esac
    

    I know, there is a lot of dublication, but I think you get the point. Also notice, I moved the --install and --upgrade options from the general case to the operation case. If you don't do that, you loose the argument if you want complete after --install or --upgrade