Skip to content

Zsh completion for cligen commands (with colored help output)

c-blake edited this page Feb 16, 2024 · 5 revisions

Zsh includes a large selection of parsers to support auto completion for a variety of commands. Common GNU tools with a 'standardized' -h/--help interface normally use the _gnu_generic completion module.

Unfortunately, when cligen's help output includes ANSI SGR color escape sequences, it breaks _gnu_generic's --help parser. At least as of Zsh version 5.9 this can be easily worked around by disabling the cligen color escape sequences in the context of the help parsing.

To do this, we must define a custom completer. For the sake of explanation, assume your Zsh setup contains a ~/.zsh directory with a ~/.zsh/completions.zsh customization file (this assumes this file is sourced from e.g ~/.zshrc of course). Create a directory for the location of any additional custom completers, ~/.zsh/completers/, in which we place the following file named _cligen:

# For cligen commands (which follow the GNU -h/--help style), disable color output in the context
# of `_gnu_generic`:
NO_COLOR=1 _gnu_generic

We make use of setting the NO_COLOR environment variable (supported by cligen) to disable the color escape sequences only in the context of the _gnu_generic call.

Then, in ~/.zsh/completions.zsh all we need to do, is to autoload this file and add the _cligen completer to our default completions. A basic ~/.zsh/completions.zsh file might look like:

autoload -U compinit && compinit
# load the custom completer
autoload -Uz ~/.zsh/completers/_cligen

## list of completers to use, extend by custom `_cligen` completer
zstyle ':completion:*::::' completer _expand _complete _cligen

With this done and Zsh restarted, you should get a nice list of possible completions when typing dups -<TAB>, with dups.nim from examples/:

dups -
--brief    -b  -- bool    false do NOT print sets of dups
--cmp      -c  -- bool    false compare; do not trust hash
--delim    -d  -- char    'n'  input file delimiter; 0 -> NUL
--Deref    -D  -- bool    false dereference symlinks
--endOut   -e  -- string  "n"  output record terminator
--file     -f  -- string  ""    optional input ( "-" | !tty = stdin )
--follow   -F  -- bool    false follow symlinks to dirs in recursion
--Hash     -H  -- Digest  wy    hash function (size|wy|nim|SHA)
--jobs     -j  -- int     1     Use this much parallelism
--log      -l  -- set(Lg) osErr >stderr{ osErr, summ }
--minLen   -m  -- int     1     minimum file size to consider
--outDlm   -o  -- string  "t"  output internal delimiter
--recurse  -r  -- 1     recurse n-levels on dirs; 0- unlimited
--slice    -s  -- string  ""    file slice (float|%-frac; <0-tailRel)
--time     -t  -- string  ""    sort each set by file time- {-}(bamcv).*
--xdev     -x  -- bool    false block cross-device recursion

Completion of individual options should work as expected, including filtering to remaining matching (e.g. for multiple options starting with --f* typing --f<TAB> only lists the remaining, in this case --file & --follow.

Arguably it would be better to upstream something to Zsh using the NO_COLOR convention cligen apps honor by default. Then if any other CLI toolkit/framework supports colorized help, it can honor the same convention and Zsh can just work. Let me know if you do that. :-) The bash complete -F _longopt does not seem to require any of this, and shells do like to compete with each other. Color in terminals is also pretty popular. So, chances are high that a change like this would be accepted by Zsh maintainers, especially as a new name like _gnu_generic_no_color.