Search code examples
autocompletezshzsh-completion

ZSH autocomplete function using existing autocompletions


I have a function mycmd to launch a program that I wrote. The program needs the first argument to be foo, ssh or ls. The second argument depends on the first argument as follows,

  1. foo -> No second argument
  2. ssh -> Something to ssh to
  3. ls -> A file

I want to write zsh autocomplete function for mycmd which suggest the second argument depending on the first argument. In the simplest form, I know that I can do the following for the first argument

_mycmd() {
    compadd foo ssh ls
}
compdef _mycmd mycmd

I have a hard time understanding what to do for the second argument from here. How do I use _ssh autocomplete for ssh argument and _ls autocomplete for ls argument? (And nothing for foo as well)


Solution

  • To inspect the current command line, it could be used $words and $CURRENT that are the completion special parameters.

    CURRENT
    This is the number of the current word, i.e. the word the cursor is currently on in the words array.
    ...
    words
    This array contains the words present on the command line curently being edited.

    --- zshcompwid(1), completion special parameters, zshcompwid - zsh completion widgets

    The completion function could modify $words and $CURRENT (and/or other variables) and then start the entire completion system based with its modified command line. For example:

    $ mycmd ls -al<TAB>               ;# This is the input, and
    ;# $words == ("mycmd" "ls" "-al") ;# original value for $words.
    ;# $words=("ls" "-al")            ;# We could update $words for making zsh
    ;# $ ls -al<TAB>                  ;# to start the completion system with
                                      ;# its modified command line.
                                      ;# Finally, _ls would be called.
    

    The utility function _normal could be used.

    _normal
    ...
    A second use is to reexamine the command line specified by the $words array and the $CURRENT parameter after those have been modified.

    -- zshcompsys(1), utility function, _normal

    _mycmd could be listed below:

    _mycmd () {
      if ((CURRENT == 2)); then
        compadd foo ssh ls
      elif ((CURRENT > 2)); then
        case "$words[2]" in
          (ssh|ls)
            shift words
            ((CURRENT--))
            _normal -p mycmd
            ;;
          (foo)
            _nothing
            ;;
          (*)
            _message "mycmd: invalid subcommand or arguments"
            ;;
        esac
      fi
      return $?
    }
    

    or even, more use of the completion builtin/utility functions like below:

    _mycmd () {
      local curcontext="${curcontext}" state context line
      local -A opt_args
      _arguments '*:: :->subcmd'
      if [[ "$state" == "subcmd" ]]; then
        if ((CURRENT == 1)); then
          _describe -t mycmd-subcmd "mycmd command" '(foo ssh ls)'
        else
          curcontext="${curcontext%:*:*}:mycmd-$words[1]:"
          case "$words[1]" in
            (ssh|ls)
              compset -n 1
              _normal -p $service
            ;;
            (foo)
              _nothing
            ;;
            (*)
              _message "mycmd: invalid subcommand or arguments"
            ;;
          esac
        fi
      fi
      return $?
    }