Mix Completions
I’ve released mix_completions
, a mix task that allows you to add shell completions for mix
. mix_completions
supports completions for bash
and zsh
.fish
already had community contributions adding completions for mix
: fish completions
How it works
mix_completions
adds three tasks to work with shell completions:
mix complete
mix complete
caches the available completions from mix help
. If mix complete
detects a mix.exs
file, it will cache these completions in the project in .mix_complete.cache
. Otherwise it will store the cache in $XDG_CACHE_HOME/.mix_complete.cache
.
This means that you can have a separate set of completions for a project with custom tasks than what is generally available to mix globally.
mix complete.bash
mix complete.bash
will refresh the cache a la mix complete
, and outputs bash that when sourced enables shell completions for mix
.
mix complete.zsh
mix complete.zsh
, like its bash
counterpart, refreshes the cache and generates output that can be loaded by zsh
. Instructions on where to put this output can be found in the README.
Why Cache Completions
With shell completions there seem to be two ways of sourcing the completions:
-
Ask the program for the completions available. This is the approach that cobra and click take. The shell completions they output are simply a parser for completions output by the program itself.
-
Caching completions outside of the program. This is the approach that tools like completely take. Saving the completions in plain text or directly in the shell output.
I chose to use option 2, because Elixir has a non-trivial startup time. Recently work has reduced the startup time of the BEAM for Elixir, but mix
still takes a few seconds to run on my machine. Instead of waiting multiple seconds for completions to come back, I chose to cache the completions in a TSV that can parsed and handled by the shell completions directly.
Where to store the cache?
This project led me to learn about the XDG Base Directory Specification. I’ve known about these environment variables for a while, but I didn’t know how to use them as a software author.
The XDG specification gives guidance on where to put configuration or cache files on unix systems. The recommendations were simple to use: XDG_CACHE_HOME
is configurable by a user, and defaults to $HOME/.local/cache
if unset.
I can either put my cache as a file in that directory or create a subdirectory if I have multiple cached things to store. mix_completions
only stores one TSV for its cache, so it’s stored directly in the XDG_CACHE_HOME
directory.
Finding all the tasks
Initially, when I wanted to find all of the available mix tasks,
I used System.shell
to call mix help
and then processed the output with awk
.
This got the job done, but felt fragile.
While explaining this to a friend, they pointed out that mix help --names
does this work for me.Thanks Jeff! Though I did have to filter iex
from the list, and I was still invoking System.shell
.
Then I realized that if mix
had an option for this, it must know how to find (and print) all of the available tasks. Now, mix_completions
uses vendored versions of the same functions that mix help
uses. This proved to be useful when I switched from a simple list of tasks to a tab-separated values file of both tasks and their shortsdocs, since Mix.Task
provides helpers to fetch things like a task’s shortdoc.
Limitations
Since I chose to work in a project agnostic way, I have no way of knowing what arguments and flag are available for subcommands. Thus I chose to only provide completions for available tasks, since I can ask mix
for these directly. The fish
community completions provide more granularity, at the cost of pre-assuming which tasks you have available.
I chose to use caching over hard-coding so that you can detect new tasks and add completions for them, if only at the surface level.
Try It Now
If you would like to try mix_completions
run:
mix archive.install hex mix_completions
Instructions on how to source the completions for your chosen shell are in the README.
Please report any issues you find. :)