complgen compiles man-page/EBNF-like grammars into standalone completion scripts. See
examples.
$ cat hello.usage
hello --color=(always | never | auto);
$ complgen --bash hello.bash hello.usage
$ source hello.bash
$ hello --color=<TAB>
always auto never$ cat hello.usage
hello --color=(always | never | auto);
$ complgen --fish hello.fish hello.usage
$ source hello.fish
$ hello --color=<TAB>
--color=always --color=auto --color=never% cat hello.usage
hello --color=(always | never | auto);
% complgen --zsh _hello hello.usage
% source _hello
% hello --color=<TAB>
always
auto
neverπ‘ Note: Under ZSH, source isn't strictly necessary β it is enough to put the output file in one of the
directories listed in $fpath variable.
cargo install --git https://github.com/adaszko/complgen --tag v0.5.0 complgenSee the examples subdirectory.
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.(aaa | bbb || ccc)showsaaaandbbbas candidates, andccconly when current input matches neitheraaanorbbb.||behaves exactly like|when matching, it differs only when offering completions.
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.
complgen will error out if you place {{{ ... }}} at a position where it's a subject to matching (as
opposed to completing). It is possible to overcome that restriction by providing a regular expression
matching the command output:
cmd ({{{ echo foo }}}@bash"foo" | bar);
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 }}}; # produce candidates on stdout under bash
<USER@fish> ::= {{{ __fish_complete_users "$1" }}}; # produce candidates on stdout under fish
<USER@zsh> ::= {{{ _users }}}; # produce candidates via compadd and friends under zsh
compadd within a subword, candidates are automatically filtered based on
the prefix input so far. It may result in some completions not showing up. You may want call it as compadd ${1}<YOUR_CANDIDATE> to include the entered prefix, or as compadd -U to disable the builtin filtering
mechanism.
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 (there are
limitations on {{{ ... }}} usage though). 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.
If you do git <TAB> in most shells you're presented with a list of git subcommands. Even though git accepts
a bunch of global options (--help, --version, etc.), they don't show up there (sic!). That's a special
mechanism intended for reducing clutter. Under complgen, the same effect is achieved via a construction
called fallbacks, which are represented in the grammar as the double bar operator (||):
mygit (<SUBCOMMAND> || <OPTION>);
<SUBCOMMAND> ::= fetch | add | commit | push;
<OPTION> ::= --help | --version;
With the grammar above, git <TAB> will offer to complete only subcommands. For git --<TAB> OTOH,
complgen will offer to complete options.
|| has the lowest priority of all operators, so the grammar above might have been written without any use of
<NONTERMINALS>. They're there only for readability sake.
There are few general rules governing whether to append a space to a completion:
-
A space is appended if the completion corresponds to an entire literal from the
.usagefile, e.g. for the grammarcmd --help;and the command linecmd <TAB>, it completes tocmd --help<SPACE>. -
A trailing space isn't appended if the literal is a part of a subword and the entire subword hasn't been completed yet, e.g. for the grammar
cmd --color=(auto|always);and the command linecmd --col<TAB>, it completes tocmd --color=(no trailing space).
There are exceptions:
- Under Fish, if your completion contains one of the special characters, fish won't insert the trailing space. See also Not adding space after dot at completion time Β· Issue #6928.
-
The tail commands limitation applies to predefined nonterminals (
<PATH>,<DIRECTORY>, etc.) since they're internally implemented as external commands. -
Bash requires
bash-completionOS package to be installed because completion scripts produced bycomplgen, call 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. -
Fish 4.0 fuzzy subsequence filtering does not work in scripts generated by complgen.
-
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.
complgen's source code is covered by License. Completion scripts generated by complgen are
subject only to Apache License 2.0.
