ZSH – Writing own completion functions

To be honest it wasn’t easy for me to get from the ZSH man pages how to make own completion functions. Sharing my modest achievement in that area here.

The main source of info is man zshcompsys.

Zsh completion system loads completion functions from the directories listed in $fpath variable:

~ $ echo $fpath
/usr/share/zsh/site-functions /usr/share/zsh/4.3.4/functions

I created a separate directory for my functions: ~/.zsh/completion
So, to initialize the compsys add the following code into your .zshrc file:

# COMPLETION SETTINGS
# add custom completion scripts
fpath=(~/.zsh/completion $fpath) 

# compsys initialization
autoload -U compinit
compinit

# show completion menu when number of options is at least 2
zstyle ':completion:*' menu select=2

Then in the ~/.zsh/completion folder create file for your future function. Filename has to start with underscore, otherwise it won’t be loaded. Let’s create file _hello for completion hello command. Doesn’t matter you don’t have such a command, completion will work though.

#compdef hello

_arguments "1: :(World)"

Now restart your zsh, type “”hello “, press TAB key. Done.
Won’t dwell on _arguments function in very details. Example of it’s usage with options exists in the linux-mag article. Also read description of _arguments on man page zshcompsys.

1: :(World) means that the 1st argument will be completed from the list of one element – World. Space between the conlons means that there will be no message in completion menu. We don’t need any message here because no menu will be shown as there is only one option.

Another example of _hello:

#compdef hello

_arguments "1: :(World)"\
    "2:Countries:(France Germany Italy)"\
    "*:Cities:((Paris\:France Berlin:\Germany Rome:\Italy))"

Here the fourth line means that the second parameter will be completed from the list of three countries. The message in the completion menu will be “Countries”. To see it you may need to add the following line into your .zshrc:

 zstyle ":completion:*:descriptions" format "%B%d%b"

The fifth line in the listing means that every next argument should be completed from the list of cities. Every city will be shown in a separate line of completion menu with the description.

The _arguments function suits very well for static completion. But what if you need to complete the second parameter depending on the first one? Let’s try to understand some more about zsh completion system.

The _hello script is executed every time you try to complete smth in hello command. Zsh completion system sets a number of internal variables you can test for their value. I’ll describe only two of those I use in the next example.
$words contains list of words from current command line
$state is a variable you can set in a special _arguments format ->value. For example “1: :->test” means that if completion happens for the first argument then the $state variable will have value “test”.

So the last example:

#compdef hello

_hello() { 
    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 "$@"

Won’t describe every line of code here. Just will say that the lines 4 and 5 are mandatory if you use “->value” option in the _arguments command. It just declares the local variables in order to avoid altering global environment.
The rest should be clear enough (knowledge of bash scripting is needed of course).
Surely in the real functions you shouldn’t declare static options and command and to use command help output to add options dynamically. But this is up to you how to get it and parse.
More info and tricks you pick up from the existing built-in completion functions placed at $fpath.
Good luck with you completion frunctions.

This entry was posted in Uncategorized and tagged . Bookmark the permalink.

7 Responses to ZSH – Writing own completion functions

  1. Nate says:

    Thanks – I had been looking for a while on how to set up some zsh completion functions for a custom script that I wrote – this really helped out!

  2. gdh says:

    If you’re converting a bash completion script to zsh, just add this to the top of your original:

    if [[ -n ${ZSH_VERSION-} ]]; then
    autoload -U +X bashcompinit && bashcompinit
    fi

  3. Pwnn says:

    Hello,

    Thank you very much for these explanations :-)
    I’ve been looking on how to write Zsh completion script, and simple examples are not easy to find.

  4. jpoppe says:

    This has been one of the most useful articles for me when I started my custom Zsh completion journey about a year ago, thanks!!!!

  5. channygold says:

    When I type hello then TAB it converts to _hello, any suggestions?

    • panicslash says:

      You need to leave one space right after hello, otherwise zsh will try to complete hello as program name (and obviously there is no program named hello), instead of its arguments.

  6. Trevor Gerbrand says:

    Thanks for this. It was really helpful for me.

    One thing that you can also do is instead of setting the fpath to a file you can use the compdef command to set the autocomplete function.
    e.g.
    compdef _hello hello

    This allows you to store your _hello function in the same location as your hello function which in my case was in the .zshrc file.

Leave a reply to Pwnn Cancel reply