Skip to content

Zsh completion for cligen commands (with colored help output)

c-blake edited this page Mar 30, 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 with something like this in your ~/.zshrc (or $ZDOTDIR/.zshrc):

compinit -u                           # probably already in your .zshrc
_ggnc() { NO_COLOR=1 _gnu_generic; }  # define _gnu_generic_no_color completer
## list of completers to use, extend by custom `_ggnc` completer
zstyle ':completion:*::::' completer _complete _ggnc # + others like _expand?

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.

While the above works and is quick to describe, it applies _gnu_generic fairly promiscuously which can be dangerous since any-command --help may not produce parsable output or even be safe to run. A more refined config would be a separate somewhere-in-FPATH file perhaps called _cligen looking like:

#compdef cols dirq dirt dups fkindc newest only rp rr
NO_COLOR=1 _gnu_generic

With either setup & 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. 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, but possibly even more directly as setting NO_COLOR=1 ahead of _call_command. Let me know if you do that and these instructions may simplify to use Zsh version > X. :-)