zshzsh-completion

Completion when program has sub-commands


I have written a command-line tool that uses sub-commands much like Mercurial, Git, Subversion &c., in that its general usage is:

>myapp [OPTS] SUBCOMMAND [SUBCOMMAND-OPTS] [ARGS]

E.g.

>myapp --verbose speak --voice=samantha --quickly "hello there"

I'm now in the process of building Zsh completion for it but have quickly found out that it is a very complex beast. I have had a look at the _hg and _git completions but they are very complex and different in approach (I struggle to understand them), but both seem to handle each sub-command separately.

Does anyone know if there a way using the built in functions (_arguments, _values, pick_variant &c.) to handle the concept of sub-commands correctly, including handling general options and sub-command specific options appropriately? Or would the best approach be to manually handle the general options and sub-command?

A noddy example would be very much appreciated.

Many thanks.


Solution

  • Writing completion scripts for zsh can be quite difficult. Your best bet is to use an existing one as a guide. The one for Git is way too much for a beginner. You can use this repo:

    https://github.com/zsh-users/zsh-completions

    As for your question, you have use the concept of state. You define your subcommands in a list and then identify via $state which command you are in. Then you define the options for each command. You can see this in the completion script for play. A simplified version is below:

    _play() {
      local ret=1
    
      _arguments -C \
        '1: :_play_cmds' \
        '*::arg:->args' \
      && ret=0
    
      case $state in
        (args)
           case $line[1] in
             (build-module|list-modules|lm|check|id)
               _message 'no more arguments' && ret=0
             ;;
             (dependencies|deps)
               _arguments \
                 '1:: :_play_apps' \
                 '(--debug)--debug[Debug mode (even more informations logged than in verbose mode)]' \
                 '(--jpda)--jpda[Listen for JPDA connection. The process will  suspended until a client is plugged to the JPDA port.]' \
                 '(--sync)--sync[Keep lib/ and modules/ directory synced. Delete unknow dependencies.]' \
                 '(--verbose)--verbose[Verbose Mode]' \
                 && ret=0
             ;;
           esac
       esac
    

    (If you are going to paste this, use the original source, as this won't work).

    It looks daunting, but the general idea is not that complicated:

    With man zshcompsys, you can find more info about the whole system, although it is somewhat dense.