complgen allows you to generate completion scripts for all major shells from a single, concise EBNF-like
grammar. It compiles the grammar down to a standalone bash/fish/zsh shell script that can be distributed on
its own. As a separate use case, it can also produce completions directly on stdout, which is meant to be
used in interactive shells (see below).
There are two ways to use complgen:
This mode is most useful if you're a CLI tool author and want to ship shell completions in your installation package.
$ complgen aot --bash-script grep.bash usage/small.usage
$ bash
$$ source grep.bash
$$ grep --color <TAB>
always auto never
This mode is useful if you're command line user and want to improve the CLI experience on your machine by either implementing a missing autocompletion for a specific CLI tool, or override the default one with a one better tailored for your needs and usage patterns.
$ complgen jit usage/small.usage bash -- --color
always
auto
never
The just-in-time mode is intended to be further integrated with shells so that it provides completions
directly from grammars, bypassing compilation and sourceing completion shell script files.
Note that it is assummed the .usage file stem is the same as the completed command name, so to complete
grep command, its grammar needs to land in grep.usage file.
Note: This assumes you have bash-completion OS-level package installed and it's been sourced! It often
boils down to apt install bash-completion; source /etc/bash_completion or brew install bash-completion; source /opt/homebrew/etc/profile.d/bash_completion.sh, depending on your OS. Without this package, scripts
generated by complgen are not able to correctly process command lines containing containing characters like
=, :, @, or any other from $COMP_WORDBREAKS.
Assumming your .usage files are stored in the ~/.config/complgen directory, add this to your ~/.bashrc:
Details
for path in ~/.config/complgen/*.usage; do
stem=$(basename "$path" .usage)
eval "
_complgen_jit_$stem () {
local words cword
_get_comp_words_by_ref -n \"\$COMP_WORDBREAKS\" words cword
local prefix="\${words[\$cword]}"
mapfile -t COMPREPLY < <(complgen jit \"$HOME/.config/complgen/${stem}.usage\" bash --comp-wordbreaks=\"\$COMP_WORDBREAKS\" --prefix=\"\$prefix\" -- \"\${words[@]:1:\$cword-1}\")
return 0
}
"
complete -o nospace -F _complgen_jit_$stem "$stem"
unset stem
doneAssumming your .usage files are stored in the ~/.config/complgen directory, add this to your ~/.config/fish/config.fish:
Details
function _complgen_jit
set --local COMP_LINE (commandline --cut-at-cursor)
set --local COMP_WORDS
echo $COMP_LINE | read --tokenize --array COMP_WORDS
if string match --quiet --regex '.*\s$' $COMP_LINE
set COMP_CWORD (math (count $COMP_WORDS) + 1)
else
set COMP_CWORD (count $COMP_WORDS)
end
set --local usage_file_path $argv[1]
set --local prefix $COMP_WORDS[$COMP_CWORD]
set --local last (math $COMP_CWORD - 1)
if test $last -lt 2
set words
else
set words $COMP_WORDS[2..$last]
end
complgen jit $usage_file_path fish --prefix="$prefix" -- $words
end
for path in ~/.config/complgen/*.usage
set --local stem (basename $path .usage)
complete --command $stem --no-files --arguments "(_complgen_jit ~/.config/complgen/$basename.usage)"
endAssumming your .usage files are stored in the ~/.config/complgen directory, add this to your ~/.zshrc:
Details
_complgen_jit () {
local stem=$1
local -a w=("${(@)words[2,$CURRENT-1]}")
local zsh_code=$(complgen jit ~/.config/complgen/${stem}.usage zsh --prefix="$PREFIX" -- "${w[@]}")
eval $zsh_code
return 0
}
for f in $HOME/.config/complgen/*.usage(N); do
local stem=$f:t:r
compdef "_complgen_jit $stem" $stem
done$ cargo install --git https://github.com/adaszko/complgen complgen$ brew tap adaszko/complgen https://github.com/adaszko/complgen-homebrew-tap.git
$ brew install adaszko/complgen/complgen
Just wget a binary for your architecture from the releases
page, chmod a+x the downloaded file and you're good to go.
The Linux binaries are linked against musl libc, so they should work on any Linux
distribution.
See the examples subdirectory for simple examples and usage subdirectory for more
involved ones.
Try piping through the scrape subcommand to quickly generate grammar skeleton that can be tweaked
further, e.g.:
$ grep --help | complgen scrape
| (-E | --extended-regexp) "PATTERNS are extended regular expressions"
| (-F | --fixed-strings) "PATTERNS are strings"
| (-G | --basic-regexp) "PATTERNS are basic regular expressions"
[...]
The grammar is based on compleat's one.
A grammar is a series of lines terminated by a semicolon (;). Each line either represents a single variant
of invoking the completed command or is a nonterminal definition.
a bmatchesafollowed byb.a b | cmatches eithera borc(IOW: sequence binds stronger than alternative).[a]matches zero or one occurrences ofa.a...matches one or more occurrences ofa[a]...matches zero or more occurrences ofa.
Use parentheses to group patterns:
a (b | c)matchesafollowed by eitherborc.(a | b) ...matchesaorbfollowed by any number of additionalaorb.
There's a couple of predefined nonterminals that are handled specially by complgen:
| Name | bash | fish | zsh | Description |
|---|---|---|---|---|
<PATH> |
✅ | ✅ | ✅ | file or directory path |
<DIRECTORY> |
✅ | ✅ | ✅ | directory path |
<PID> |
❎ | ✅ | ✅ | process id |
<USER> |
✅ | ✅ | ✅ | user name |
<GROUP> |
✅ | ✅ | ✅ | group name |
<HOST> |
✅ | ✅ | ✅ | hostname |
<INTERFACE> |
❎ | ✅ | ✅ | network interface name |
<PACKAGE> |
❎ | ✅ | ❎ | OS package name |
The reason there's no predefined <FILE> nonterminal is that it would work only for files from the current
directory which is too specific to be generally useful.
These nonterminals can still be defined in the grammar in the usual way (<PATH> ::= ...), in which case
their predefined meaning gets overriden.
If a literal is immediately followed with a quoted string, it's going to appear as a hint to the user at completion time. E.g. the grammar:
grep --extended-regexp "PATTERNS are extended regular expressions" | --exclude (skip files that match GLOB)results in something like this under fish (and zsh):
fish> grep --ex<TAB>
--exclude (skip files that match GLOB) --extended-regexp (PATTERNS are extended regular expressions)Note that bash does not support showing descriptions.
It is possible to use entire shell commands as a source of completions:
cargo {{{ rustup toolchain list | cut -d' ' -f1 | sed 's/^/+/' }}};
The stdout of the pipeline above will be automatically filtered by the shell based on the prefix entered so far.
Sometimes, it's more efficient to take into account the entered prefix in the shell command itself. For all
three shells (bash, fish, zsh), it's available in the $1 variable:
cargo {{{ rustup toolchain list | cut -d' ' -f1 | grep "^$1" | sed 's/^/+/' }}};
Note that in general, it's best to leave the filtering up to the executing shell since it may be configured to
perform some non-standard filtering. zsh for example is capable of expanding /u/l/b to /usr/local/bin.
Externals commands are also assumed to produce descriptions similar to those described in the section above. Their expected stdout format is a sequence of lines of the form
COMPLETION\tDESCRIPTION
For fish and zsh, the DESCRIPTION part will be presented to the user. Under bash, only the COMPLETION
part will be visible. All external commands nonetheless need to take care as to not produce superfluous
\t characters that may confuse the resulting shell scripts.
In order to make use of shell-specific completion functions, complgen supports a mechanism that allows for
picking a specific nonterminal expansion based on the target shell. To use an example, all shells are able to
complete a user on the system, although each has a different function for it. We unify their interface under
the nonterminal <USER> using few nonterminal@shell definitions:
cmd <USER>;
<USER@bash> ::= {{{ compgen -A user "$1" | sort | uniq }}}; # bash produces duplicates for some reason
<USER@fish> ::= {{{ __fish_complete_users "$1" }}};
<USER@zsh> ::= {{{ _users }}};
It's possible to match not only entire words, but also within words themselves, using the same grammar
syntax as for matching entire words. In that sense, it all fractally works on subwords too. The most common
application of that general mechanism is to handle equal sign arguments (--option=ARGUMENT):
grep --color=(always | never | auto);
Note however that equal sign arguments aren't some special case within complgen — the same mechanism works for more complicated things, e.g.:
strace -e <EXPR>;
<EXPR> ::= [<qualifier>=][!]<value>[,<value>]...;
<qualifier> ::= trace | read | write | fault;
<value> ::= %file | file | all;
The above grammar was pulled straight out of strace man page.
-
Bash requires
bash-completionOS package to be installed because completion scripts produced bycomplgencall shell functions from that package at completion time. This is necessary to work around Bash's default behavior of breaking shell words on any character present in the$COMP_WORDBREAKSenvironment variable. -
Under Fish, if your grammar tokens contain one of the special characters, the inserted completion won't end in a space indicating full completion. See also Not adding space after dot at completion time · Issue #6928
- Non-regular grammars aren't completed 100% precisely. For instance, in case of
find(1),complgenwill still suggest)even in cases when all(have already been properly closed before the cursor.
Best way is to watch GitHub releases.
- clap
complgenis able to produce completions by executing an arbitrary shell command (e.g.cargo -Z <TAB>, complete test name incargo test <TAB>)- All the grammar specification mechanisms are available for completing option parameters. That means
complgenis able to complete DSLs of the likes ofstrace -e <TAB>orlsof -i <TAB>. - No recompilation and reloading necessary in shell integration mode -- just modify the grammar file and completions automatically reflect that.
- There's a possibility of the program options and completion grammar diverging since they're maintained
separately. On the plus side,
complgenisn't tied to the implementation language and independent users can write their custom completion grammars suited for their own needs.
- zsh-capture-completion
- This must have been painful to implement but is indispensable to complgen!
- argcomplete
- Oil's shellac protocol
- _regex_arguments and _regex_words completions
- sh-manpage-completions
- Little Language