Search code examples
zshzsh-completion

Complete a command (with arguments) like another command (with other arguments)


compdef cmd1=service can be used to define a completion alias, however, that works only when the arguments are going to be the same.

For example, consider a helper script which rewrites some arguments before executing another command:

| What is typed | What is executed           |
|---------------+----------------------------|
| s             | systemctl                  |
| s q           | systemctl status           |
| s q foo       | systemctl status foo       |
| s j foo       | journalctl --unit foo      |
| s r foo       | sudo systemctl restart foo |

We can ask the script to print the arguments it would execute, so e.g. PRINT_ONLY=1 s would print just systemctl.

Assuming completion is already set up for systemctl / journalctl / sudo, how would one define a zsh completion for such a script? Rather than redundantly reimplementing completion for those commands, how to implement completion for s such that the completion system is invoked with a transformed command -- i.e. something like function _s() { get_completions $(PRINT_ONLY=1 s "$@") ; }?


Solution

  • This should go in a file named _s somewhere on your $fpath:

    #compdef s
    
    local -a orig_command new_words
    orig_command=("${words[@]}")
    if [[ $words[-1] == '' ]]; then
      # remove an empty word at the end, which the completion system cares about
      orig_command[-1]=()
    fi
    # split the rewritten command into words using the shell parser
    new_words=("${(z)$(PRINT_ONLY=1 "${orig_command[@]}")}")
    if [[ $words[-1] == '' ]]; then
      # restore the empty word if we removed it
      new_words+=('')
    fi
    # update the cursor position
    CURRENT=$(( CURRENT - $#words + $#new_words ))
    words=("${new_words[@]}")
    # restart completion with the rewritten command
    _normal
    

    Note: this doesn't do any error handling and just assumes that any unknown arguments will be passed to the default command (e.g. s start foo -> systemctl start foo). If that's not the case, let me know how s behaves in those cases and I can update the completion script.