diff --git a/command.go b/command.go index 2783cb6b2f..541081a59c 100644 --- a/command.go +++ b/command.go @@ -56,6 +56,8 @@ type Command struct { ShellCompletionCommandName string `json:"-"` // The function to call when checking for shell command completions ShellComplete ShellCompleteFunc `json:"-"` + // The function to configure a shell completion command + ConfigureShellCompletionCommand ConfigureShellCompletionCommand `json:"-"` // An action to execute before any subcommands are run, but after the context is ready // If a non-nil error is returned, no subcommands are run Before BeforeFunc `json:"-"` diff --git a/command_setup.go b/command_setup.go index 060b9ba9ba..09df4a3084 100644 --- a/command_setup.go +++ b/command_setup.go @@ -88,7 +88,7 @@ func (cmd *Command) setupDefaults(osArgs []string) { cmd.SuggestCommandFunc = suggestCommand } - if isRoot && cmd.EnableShellCompletion { + if isRoot && cmd.EnableShellCompletion || cmd.ConfigureShellCompletionCommand != nil { completionCommand := buildCompletionCommand(cmd.Name) if cmd.ShellCompletionCommandName != "" { @@ -102,6 +102,9 @@ func (cmd *Command) setupDefaults(osArgs []string) { tracef("appending completionCommand (cmd=%[1]q)", cmd.Name) cmd.appendCommand(completionCommand) + if cmd.ConfigureShellCompletionCommand != nil { + cmd.ConfigureShellCompletionCommand(completionCommand) + } } tracef("setting command categories (cmd=%[1]q)", cmd.Name) diff --git a/completion.go b/completion.go index 789c9d0a7a..d97ade6e49 100644 --- a/completion.go +++ b/completion.go @@ -5,6 +5,7 @@ import ( "embed" "fmt" "sort" + "strings" ) const ( @@ -39,10 +40,28 @@ var ( } ) +const completionDescription = `Output shell completion script for bash, zsh, fish, or Powershell. +Source the output to enable completion. + +# .bashrc +source <($COMMAND completion bash) + +# .zshrc +source <($COMMAND completion zsh) + +# fish +$COMMAND completion fish > ~/.config/fish/completions/$COMMAND.fish + +# Powershell +Output the script to path/to/autocomplete/$COMMAND.ps1 an run it. +` + func buildCompletionCommand(appName string) *Command { return &Command{ - Name: completionCommandName, - Hidden: true, + Name: completionCommandName, + Hidden: true, + Usage: "Output shell completion script for bash, zsh, fish, or Powershell", + Description: strings.ReplaceAll(completionDescription, "$COMMAND", appName), Action: func(ctx context.Context, cmd *Command) error { return printShellCompletion(ctx, cmd, appName) }, diff --git a/docs/v3/examples/completions/shell-completions.md b/docs/v3/examples/completions/shell-completions.md index 901171a716..29b55daab3 100644 --- a/docs/v3/examples/completions/shell-completions.md +++ b/docs/v3/examples/completions/shell-completions.md @@ -233,6 +233,50 @@ func main() { ``` ![](../../images/custom-bash-autocomplete.gif) +#### Customize a completion command + +By default, a completion command is hidden, meaning the command isn't included in the help message. +You can customize it by setting root Command's `ConfigureShellCompletionCommand`. + +```go +package main + +import ( + "context" + "fmt" + "log" + "os" + + "github.com/urfave/cli/v3" +) + +func main() { + cmd := &cli.Command{ + Name: "greet", + // EnableShellCompletion is unnecessary + ConfigureShellCompletionCommand: func(cmd *cli.Command) { // cmd is a completion command + cmd.Hidden = false // Make a completion command public + cmd.Usage = "..." // Customize Usage + cmd.Description = "..." // Customize Description + }, + Commands: []*cli.Command{ + { + Name: "hello", + Usage: "Say hello", + Action: func(ctx context.Context, cmd *cli.Command) error { + fmt.Println("Hello") + return nil + }, + }, + }, + } + + if err := cmd.Run(context.Background(), os.Args); err != nil { + log.Fatal(err) + } +} +``` + #### Customization The default shell completion flag (`--generate-shell-completion`) is defined as diff --git a/funcs.go b/funcs.go index e0d1e19c2b..fe1224c44e 100644 --- a/funcs.go +++ b/funcs.go @@ -20,6 +20,9 @@ type ActionFunc func(context.Context, *Command) error // CommandNotFoundFunc is executed if the proper command cannot be found type CommandNotFoundFunc func(context.Context, *Command, string) +// ConfigureShellCompletionCommand is a function to configure a shell completion command +type ConfigureShellCompletionCommand func(*Command) + // OnUsageErrorFunc is executed if a usage error occurs. This is useful for displaying // customized usage error messages. This function is able to replace the // original error messages. If this function is not set, the "Incorrect usage" diff --git a/godoc-current.txt b/godoc-current.txt index 4a55270015..34ef5b6158 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -433,6 +433,8 @@ type Command struct { ShellCompletionCommandName string `json:"-"` // The function to call when checking for shell command completions ShellComplete ShellCompleteFunc `json:"-"` + // The function to configure a shell completion command + ConfigureShellCompletionCommand ConfigureShellCompletionCommand `json:"-"` // An action to execute before any subcommands are run, but after the context is ready // If a non-nil error is returned, no subcommands are run Before BeforeFunc `json:"-"` @@ -751,6 +753,10 @@ type CommandCategory interface { type CommandNotFoundFunc func(context.Context, *Command, string) CommandNotFoundFunc is executed if the proper command cannot be found +type ConfigureShellCompletionCommand func(*Command) + ConfigureShellCompletionCommand is a function to configure a shell + completion command + type Countable interface { Count() int } diff --git a/testdata/godoc-v3.x.txt b/testdata/godoc-v3.x.txt index 4a55270015..34ef5b6158 100644 --- a/testdata/godoc-v3.x.txt +++ b/testdata/godoc-v3.x.txt @@ -433,6 +433,8 @@ type Command struct { ShellCompletionCommandName string `json:"-"` // The function to call when checking for shell command completions ShellComplete ShellCompleteFunc `json:"-"` + // The function to configure a shell completion command + ConfigureShellCompletionCommand ConfigureShellCompletionCommand `json:"-"` // An action to execute before any subcommands are run, but after the context is ready // If a non-nil error is returned, no subcommands are run Before BeforeFunc `json:"-"` @@ -751,6 +753,10 @@ type CommandCategory interface { type CommandNotFoundFunc func(context.Context, *Command, string) CommandNotFoundFunc is executed if the proper command cannot be found +type ConfigureShellCompletionCommand func(*Command) + ConfigureShellCompletionCommand is a function to configure a shell + completion command + type Countable interface { Count() int }