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:

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

# compsys initialization
autoload -U 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
        _arguments '1:Countries:(France Germany Italy)'
        case $words[2] in
            compadd "$@" Paris Lyon Marseille
            compadd "$@" Berlin Munich Dresden
            compadd "$@" Rome Napoli Palermo

_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.

Posted in Uncategorized | Tagged | 7 Comments

Vim – jump to db function under cursor

Our database functions files grouped in the code repository by their schemas. I.e. there is a folder for every schema, and there are sql files named by function name inside the folders. So files hierarchy looks like


If you have the similar code structure (what is quite common) and use vim as editor, then the following trick let you jump to the database function file under cursor with hotkey combination \gf (analogue with embedded gf – go file)
To go back use either command :b# or keys combination ctrl+shift+6 (all keys are for normal mode)

Place the following code into your .vimrc and redefine the value of the first variable with absolute path to your source directory.
Works only for schema prefixed functions with the corresponding folders in the repository.

" Go to db function in the local repo
fu! GoFunction()
    " Redefine the _absolute_ path to the db folder
    let l:sourcePath = '/Users/username/db_repo/'

    let l:schema = ''
    let l:ff = ''
    let l:word = expand("") 
    let l:word = matchstr(l:word, '[^\(]*')
    let l:farr = split(l:word, '\.')
    :if len(l:farr) != 2
        echo 'Word "' . expand("") . '" is not a function'
        let l:schema = l:farr[0]
        let l:ff = l:farr[1]

        let l:fpath = l:sourcePath . l:schema .'/'. l:ff . '.sql'
        :if filereadable(l:fpath)
            execute ':edit ' l:fpath
            echo 'Can not read file ' l:fpath

    unlet l:sourcePath l:schema l:ff l:farr l:fpath l:word

nmap \gf :call GoFunction()

Hope it will be useful for somebody.

Posted in Vim | Leave a comment