1
0
Fork 0
mirror of https://github.com/spf13/cobra synced 2025-04-01 20:39:12 +00:00

feat: add CompletionWithDesc helper

The code has also been refactored to use a type alias for completion and a completion helper

Using a type alias is a non-breaking change and it makes the code more readable and easier to understand.

Signed-off-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com>
This commit is contained in:
ccoVeille 2025-02-08 02:00:26 +01:00
parent 17b6dca2ff
commit f80a6ab97a
No known key found for this signature in database
2 changed files with 35 additions and 19 deletions

View file

@ -82,7 +82,7 @@ type Command struct {
Example string
// ValidArgs is list of all valid non-flag arguments that are accepted in shell completions
ValidArgs []string
ValidArgs []CompletionChoice
// ValidArgsFunction is an optional function that provides valid non-flag arguments for shell completion.
// It is a dynamic version of using ValidArgs.
// Only one of ValidArgs and ValidArgsFunction can be used for a command.
@ -94,7 +94,7 @@ type Command struct {
// ArgAliases is List of aliases for ValidArgs.
// These are not suggested to the user in the shell completion,
// but accepted if entered manually.
ArgAliases []string
ArgAliases []CompletionChoice
// BashCompletionFunction is custom bash functions used by the legacy bash autocompletion generator.
// For portability with other shells, it is recommended to instead use ValidArgsFunction

View file

@ -117,15 +117,31 @@ type CompletionOptions struct {
HiddenDefaultCmd bool
}
// CompletionChoice is a string that can be used for completions
//
// two formats are supported:
// - the name of the flag
// - the name of the flag with its description (separated by a tab)
//
// [CompletionChoiceWithDescription] can be used to create a completion string with a description using the TAB separator.
//
// Note: Go type alias is used to provide a more descriptive name in the documentation, but any string can be used.
type CompletionChoice = string
// CompletionFunc is a function that provides completion results.
type CompletionFunc func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)
type CompletionFunc func(cmd *Command, args []CompletionChoice, toComplete string) ([]string, ShellCompDirective)
// CompletionChoiceWithDescription returns a CompletionChoice with a description
func CompletionChoiceWithDescription(name, desc string) CompletionChoice {
return name + "\t" + desc
}
// NoFileCompletions can be used to disable file completion for commands that should
// not trigger file completions.
//
// This method satisfies [CompletionFunc].
// It can be used with [Command.RegisterFlagCompletionFunc] and for [Command.ValidArgsFunction].
func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
func NoFileCompletions(cmd *Command, args []CompletionChoice, toComplete string) ([]string, ShellCompDirective) {
return nil, ShellCompDirectiveNoFileComp
}
@ -134,8 +150,8 @@ func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]string
//
// This method returns a function that satisfies [CompletionFunc]
// It can be used with [Command.RegisterFlagCompletionFunc] and for [Command.ValidArgsFunction].
func FixedCompletions(choices []string, directive ShellCompDirective) CompletionFunc {
return func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
func FixedCompletions(choices []CompletionChoice, directive ShellCompDirective) CompletionFunc {
return func(cmd *Command, args []CompletionChoice, toComplete string) ([]string, ShellCompDirective) {
return choices, directive
}
}
@ -290,7 +306,7 @@ type SliceValue interface {
GetSlice() []string
}
func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) {
func (c *Command) getCompletions(args []string) (*Command, []CompletionChoice, ShellCompDirective, error) {
// The last argument, which is not completely typed by the user,
// should not be part of the list of arguments
toComplete := args[len(args)-1]
@ -318,7 +334,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
}
if err != nil {
// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("unable to find a command for arguments: %v", trimmedArgs)
return c, []CompletionChoice{}, ShellCompDirectiveDefault, fmt.Errorf("unable to find a command for arguments: %v", trimmedArgs)
}
finalCmd.ctx = c.ctx
@ -348,7 +364,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
// Parse the flags early so we can check if required flags are set
if err = finalCmd.ParseFlags(finalArgs); err != nil {
return finalCmd, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
return finalCmd, []CompletionChoice{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
}
realArgCount := finalCmd.Flags().NArg()
@ -360,14 +376,14 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
if flagErr != nil {
// If error type is flagCompError and we don't want flagCompletion we should ignore the error
if _, ok := flagErr.(*flagCompError); !(ok && !flagCompletion) {
return finalCmd, []string{}, ShellCompDirectiveDefault, flagErr
return finalCmd, []CompletionChoice{}, ShellCompDirectiveDefault, flagErr
}
}
// Look for the --help or --version flags. If they are present,
// there should be no further completions.
if helpOrVersionFlagPresent(finalCmd) {
return finalCmd, []string{}, ShellCompDirectiveNoFileComp, nil
return finalCmd, []CompletionChoice{}, ShellCompDirectiveNoFileComp, nil
}
// We only remove the flags from the arguments if DisableFlagParsing is not set.
@ -396,11 +412,11 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
return finalCmd, subDir, ShellCompDirectiveFilterDirs, nil
}
// Directory completion
return finalCmd, []string{}, ShellCompDirectiveFilterDirs, nil
return finalCmd, []CompletionChoice{}, ShellCompDirectiveFilterDirs, nil
}
}
var completions []string
var completions []CompletionChoice
var directive ShellCompDirective
// Enforce flag groups before doing flag completions
@ -542,7 +558,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
if completionFn != nil {
// Go custom completion defined for this flag or command.
// Call the registered completion function to get the completions.
var comps []string
var comps []CompletionChoice
comps, directive = completionFn(finalCmd, finalArgs, toComplete)
completions = append(completions, comps...)
}
@ -562,12 +578,12 @@ func helpOrVersionFlagPresent(cmd *Command) bool {
return false
}
func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []CompletionChoice {
if nonCompletableFlag(flag) {
return []string{}
return []CompletionChoice{}
}
var completions []string
var completions []CompletionChoice
flagName := "--" + flag.Name
if strings.HasPrefix(flagName, toComplete) {
// Flag without the =
@ -595,8 +611,8 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string {
return completions
}
func completeRequireFlags(finalCmd *Command, toComplete string) []string {
var completions []string
func completeRequireFlags(finalCmd *Command, toComplete string) []CompletionChoice {
var completions []CompletionChoice
doCompleteRequiredFlags := func(flag *pflag.Flag) {
if _, present := flag.Annotations[BashCompOneRequiredFlag]; present {