This package provides a library for writing shell-agnostic tab completion. (bash, zsh, fish)
While completion for any program can be written with the library, it's targeted for self-completing Go programs. This is a program that doesn't need to distribute, or maintain, any complex bash/zsh/fish script alongside their normal CLI.
It was originally forked from posener/complete, which has went in a different direction. See "Motivation for Forking".
- Describe the skeleton of your CLI and get tab-completion for free
- Write custom predictors to enrich suggestions using your existing business logic
- Dynamic, contextual suggestions based on what the user has typed.
- A flag may need different values depending on a positional argument's value.
- Helpers to generate skeletons from well-known CLI frameworks.
cobra
is automatic- Seamless helpers for
flags
andurfavecli
is planned.
Shells can query external binaries to get tab suggestions.
They provide a few environment variables to the program, and this package parses them. It notes what has been typed in the prompt, the cursor position where the user has pressed TAB, and returns relevant suggestions.
By writing custom predictors, tools can hook into this and enrich the user's experience. Use existing logic in your code without duplicating it to a shell script.
While developers don't need to distribute complicated shell programs for completion, a bit of configuration is still needed.
Bash is one command and zsh is two, fish is a bit more. Any program that uses this
package can be installed by setting COMP_INSTALL=1
and running the program.
# Detects the user's shell and operating system, configuring them appropriately.
COMP_INSTALL=1 mycli
COMP_INSTALL=1 COMP_YES=1 mycli # To auto confirm
# Uninstall
COMP_UNINSTALL=1 mycli
If you prefer the manual way:
# Bash
# The last argument is what the user is typing as argv[0] - not a path
complete -C /path/to/mycli mycli
# Zsh
autoload -U +X bashcompinit && bashcompinit
complete -C /path/to/mycli mycli
If you want to jump into an example, here they are:
- Runnable programs: ./examples
- Godoc examples: pkgsite
A Predictor
is any type that implements Predict(args.Args) []string
.
args.Args
contains a few fields:
type Args struct {
// Arguments in typed by the user so far, up until they pressed TAB.
// - At some point this will be all arguments, even if TAB was pressed in the
// middle of the line.
All []string
// Same as above, excluding the one currently being typed.
Completed []string
// The word currently being typed, or empty if there's a space before where
// TAB was pressed.
Last string
// Last fully-typed word
LastCompleted string
// Domain-specific value that was emitted by `args.Parser(all []string)`
ParsedRoot any
}
Each Predictor
is mapped to a flag or command to generate suggestions depending on
where the user presses TAB. If no predictor is set for a command, it's sub-commands are
used. Otherwise it defaults to predict.Files
.
There are a few canonical predictors to help you get started:
predict.Anything
predict.Cached
predict.Dirs
predict.Files
predict.Func
predict.Nothing
predict.Or
predict.ScopedCache
predict.Set
To make testing easy, the cmptest
package provides two functions:
// Suggestions returns the options returned by the parser with a given prompt
//
// The prompt should look like it would on the command-line. If '<TAB>' is included,
// that is where we will assume the user pressed the tab key. The end of the prompt is
// used otherwise.
//
// Example: "mycli sub --<TAB> --other"
func Suggestions(t testing.TB, cp complete.CommandParser, prompt string) []string
// Assert suggestions from [Suggestions]
func Assert(t *testing.T, cp complete.CommandParser, prompt string, want []string)
A basic example using cobra
would look like:
func TestBasic(t *testing.T) {
cmd := &cobra.Command{
Use: "count",
ValidArgs: []string{"one", "two", "three"},
CompletionOptions: cobra.CompletionOptions{
DisableDefaultCmd: true,
},
}
cmptest.Assert(t, cmpcobra.New(cmd), "count <TAB>", []string{"one", "two", "three"})
}
Running your program with COMP_DEBUG=1
will output any logs written with
cmplog.Log("some msg: %v", val)
.
The internal functions use this quite a bit, and you can include your own diagnostic messages for live troubleshooting.
export COMP_DEBUG=1
mycli <TAB>
complete 2024/09/04 17:19:30 Completing phrase: mycli
complete 2024/09/04 17:19:30 Completing last field:
complete 2024/09/04 17:19:30 Options: [one two three]
complete 2024/09/04 17:19:30 Matches: [one two three]
First, much thanks and credit to posener/complete. It was the first library that demonstrated self-completion to me many years ago.
I've been using it ever sincce until recently, mostly due to it's v2
version making
decisions that make dynamic, contextual completion hard.
-
Assumptions around certain flags always existing (
--help
, and-h
)- While I agree every CLI should define these, it's not the completion engine's place to assert.
-
Predictors only having access to their token
- Tab completion should enrich a user's experience, and sometimes this means making different decisions depending on other parts of the prompt.
As such, this is a fork from v1
. I've had to change a few core ways in how the
library works, and decided to publish the results going forward. You should be able to
use this library by simply changing the import - all of the exported symbols have been
aliased.
- Removal of
cmd install
in favor of v2'sCOMP_INSTALL
semantics New2
andNew2F
functions as primary entrypoints- The concept of a
Parser
that can give wider context to eachPredictor
- The concept of a
Commander
that can return aCommand
- Enables framework-aware helpers to generate a completion skeleton
- New packages
args
andpredict
for scoping and import cycle issues args.Args.ParsedRoot
contains the result ofParser.Parse()
predict.Cached
to re-use values that may need a network or expensive call to generate- New packages
cmptest
andcmplog
for easier testing - New package
cmpcobra
for generating completion fromcobra
programs