Writing Zsh Completion for Padrino
This article is part of an ongoing series about my exploration of the Padrino web framework. If you want to read more about it please check out padrinobook.com. You sniff around in the sources of the book under GitHub.
I’m a user of the Z shell (zsh). It is easy to customize your prompt and to have more abilities for tab completion of the most used programs with this shell. If you are lazy or new to a new command line tool with a huge amount of options, this will help you in the beginning to remember the commands. When you use [git](http://en.wikipedia.org/wiki/Git_(software) you can press tab to see all available options you have in the current context. The same functions aren’t available for Padrino and this is how I came to this article.
Start Small with Basic tmuxinator Completion
Your first step is to create a file with the name of the command for which you want to have autocompletion. The name of the command must begin with an underscore and needs to be in your fpath. The fpath
is the variable which will be used for shell searches when the autoload
function is first called. Let’s enable this feature in your .zshrc
:
# folder of all of your autocomplete functions
fpath=($HOME/.zsh-completions $fpath)
# enable autocomplete function
autoload -U compinit
compinit
Before going on, you need to install the tmuxinator gem with:
$ gem install tmuxinator
Let’s implement the hello-tmux
and version
commands for the tmuxinator gem. Inside your ~/.zsh-completions/_tmuxinator
file:
#compdef tmuxinator
_tmuxinator() {
local -a commands
commands=(
'hello-tmux:our first autocompletion function.'
'version:show the used gem version.'
)
if (( CURRENT == 2 )); then
_describe -t commands 'commands' commands
fi
return 0
}
_tmuxinator
Now reload your shell with exec zsh
and type in $ tmuxinator <Tab>
. Voilà, you’ve written you first autocompletion. I will explain part of the code in the next chapters in case you don’t know each detail of the code.
Autocompletion for Padrino
First of all we need to specify the name of the command for which we want to have for Padrino’s autocompletion:
#compdef padrino
typeset -A opt_args
Please note, that the first line must be comment with the compdef
command followed by the name of the command. With the help of the typeset
command we can set and display attributes and values for shell parameters. The -A
options says, that names we pass into the autocomplete function refers to associative array parameters. opt_args
makes it possible to add command-line options like -d
or -f
for our associative array.
Next, we are going to define the inputs of our function. We need this to be able to define states in order to get the context in which we are during our completion:
_arguments -C \
'1:cmd:->cmds' \
'2:generators:->generator_lists' \
'*:: :->args' \
&& ret=0
The _arguments
function is used to give a complete specification for a command whose arguments follow standard UNIX option and argument conventions. _arguments
returns status zero if it was able to add matches and non-zero otherwise. The option -C
makes it possible to modify the curcontext
parameter. With the help of curcontext
it is possible to keep track of the current context. This will be used later in our big state machine like case construct.
1:cmd:->cmds
describes the first argument in our completion function. The cmd
message will be printed above matches generated and the cmds
action indicates what can be completed in this position. Similar, 2:generators:->generator_lists
stands for the second argument with the generator_lists
option. At last, we need to be able to pass in certain arguments whatever position we are in. For this case we are using *:: :->args
. The two colons before any message of the command array indicates that the current context parameter refer only to the normal arguments when the action is evaluated.
Padrino Generators
Everything is set and we need to define Padrino’s command for autocompletion. Padrino uses the g
option to specify the command for using a generator. After we are in this context we can chose a generator like controller, mailer, migration, model, plugin or project. Depending which generator we take, we can use different, context specific arguments like -a
for the mailer generator to create a mailer for the specified sub application or the -d
option for defining the orm adapter when using the project generator.
The first argument in Padrino autocompletion, 1:cmd:->cmds
, is created with the help of a case statement:
#compdef padrino
typeset -A opt_args
_arguments -C \
'1:cmd:->cmds' \
'2:generators:->generator_lists' \
'*:: :->args' \
&& ret=0
case "$state" in
(cmds)
local commands; commands=(
'g:Padrinos generators'
)
_describe -t commands 'command' commands && ret=0
;;
ease;
return 1;
Let’s go through the parts:
case "$state"
- The beginning of our state statement. The$state
variable is used for tracking the current state we are in.local commands
- Definition of a variable which is assigned as a list of strings for our completion function. After the name of the command follows the explanation of the command._describe -t
- Thedescribe
functions associates completions with description. All it does is taking our previously defined commands as a list variablecommands
for zsh compsys function and return 0 to indicate that error occurs. The-t
option specify the tag we want to use we want to use in our describe function. In our case we are tagging the local commands for the completions.
Now we know that we are going to use a generator we need to find out which generator want to use in the completion. This is the '2:generators:->generator_lists'
part of the _arguments
function:
#compdef padrino
typeset -A opt_args
_arguments -C \
'1:cmd:->cmds' \
'2:generators:->generator_lists' \
'*:: :->args' \
&& ret=0
case "$state" in
(cmds)
local commands; commands=(
'g:Padrinos generators'
)
_describe -t commands 'command' commands && ret=0
;;
(generator_lists)
local generators; generators=(
'controller:creates a new controller'
'mailer:creates a new mailer'
'migration:creates a new migration'
'model:creates a new model'
'project:create a new Padrino app'
'plugin:add plugin to your app'
)
_describe -t generators 'generator' generators && ret=0
;;
esac
return 1
The code above follows nearly the same syntax as the cmds
command before. The last thing we need to handle are the optional arguments for each generate state, as defined *:: :->args
(imagine this thing as the default or else branch of your control structure):
#compdef padrino
typeset -A opt_args
_arguments -C \
'1:cmd:->cmds' \
'2:generators:->generator_lists' \
'*:: :->args' \
&& ret=0
case "$state" in
(cmds)
local commands; commands=(
'g:Padrinos generators'
)
_describe -t commands 'command' commands && ret=0
;;
(generator_lists)
local generators; generators=(
'controller:creates a new controller'
'mailer:creates a new mailer'
'migration:creates a new migration'
'model:creates a new model'
'project:create a new Padrino app'
'plugin:add plugin to your app'
)
_describe -t generators 'generator' generators && ret=0
;;
(args)
case $line[2] in
(controller)
_arguments \
'-d:remove all generated files' \
'-n:specify the application' \
'-r:specify the root'
ret=0
;;
(mailer)
local mailers; mailers=(
'-a:creates a mailer for the specified subapp'
'-d:removes all generated files'
'-n:specify the application'
'-r:specify the root destination path'
)
_describe -t mailers 'mailer' mailers && ret=0
;;
...
;;
esac
;;
esac
return 1
If our second inline parameter $line[2]
is controller
then we have the -d, -n
, and -r
options. One the other hand if the second inline parameter is mailer
the options -a, -d, -n
, and -r
are available. You can find the whole implementation code under GitHub.
Conclusion
The benefit of writing function completions for zsh is that it is available on nearly every platform. All you need is to have the zsh installed on your machine - there are no other dependencies. Documentation about writing is available but there aren’t many practical examples. Look and learn from other how used it and then experiment with it. After some time you’ll get the smell how to do it. And please, if you are writing something complex, write a blog post about your completion function to help other getting into it.