completions.go

   1// Copyright 2013-2023 The Cobra Authors
   2//
   3// Licensed under the Apache License, Version 2.0 (the "License");
   4// you may not use this file except in compliance with the License.
   5// You may obtain a copy of the License at
   6//
   7//      http://www.apache.org/licenses/LICENSE-2.0
   8//
   9// Unless required by applicable law or agreed to in writing, software
  10// distributed under the License is distributed on an "AS IS" BASIS,
  11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12// See the License for the specific language governing permissions and
  13// limitations under the License.
  14
  15package cobra
  16
  17import (
  18	"fmt"
  19	"os"
  20	"regexp"
  21	"strconv"
  22	"strings"
  23	"sync"
  24
  25	"github.com/spf13/pflag"
  26)
  27
  28const (
  29	// ShellCompRequestCmd is the name of the hidden command that is used to request
  30	// completion results from the program.  It is used by the shell completion scripts.
  31	ShellCompRequestCmd = "__complete"
  32	// ShellCompNoDescRequestCmd is the name of the hidden command that is used to request
  33	// completion results without their description.  It is used by the shell completion scripts.
  34	ShellCompNoDescRequestCmd = "__completeNoDesc"
  35)
  36
  37// Global map of flag completion functions. Make sure to use flagCompletionMutex before you try to read and write from it.
  38var flagCompletionFunctions = map[*pflag.Flag]CompletionFunc{}
  39
  40// lock for reading and writing from flagCompletionFunctions
  41var flagCompletionMutex = &sync.RWMutex{}
  42
  43// ShellCompDirective is a bit map representing the different behaviors the shell
  44// can be instructed to have once completions have been provided.
  45type ShellCompDirective int
  46
  47type flagCompError struct {
  48	subCommand string
  49	flagName   string
  50}
  51
  52func (e *flagCompError) Error() string {
  53	return "Subcommand '" + e.subCommand + "' does not support flag '" + e.flagName + "'"
  54}
  55
  56const (
  57	// ShellCompDirectiveError indicates an error occurred and completions should be ignored.
  58	ShellCompDirectiveError ShellCompDirective = 1 << iota
  59
  60	// ShellCompDirectiveNoSpace indicates that the shell should not add a space
  61	// after the completion even if there is a single completion provided.
  62	ShellCompDirectiveNoSpace
  63
  64	// ShellCompDirectiveNoFileComp indicates that the shell should not provide
  65	// file completion even when no completion is provided.
  66	ShellCompDirectiveNoFileComp
  67
  68	// ShellCompDirectiveFilterFileExt indicates that the provided completions
  69	// should be used as file extension filters.
  70	// For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename()
  71	// is a shortcut to using this directive explicitly.  The BashCompFilenameExt
  72	// annotation can also be used to obtain the same behavior for flags.
  73	ShellCompDirectiveFilterFileExt
  74
  75	// ShellCompDirectiveFilterDirs indicates that only directory names should
  76	// be provided in file completion.  To request directory names within another
  77	// directory, the returned completions should specify the directory within
  78	// which to search.  The BashCompSubdirsInDir annotation can be used to
  79	// obtain the same behavior but only for flags.
  80	ShellCompDirectiveFilterDirs
  81
  82	// ShellCompDirectiveKeepOrder indicates that the shell should preserve the order
  83	// in which the completions are provided
  84	ShellCompDirectiveKeepOrder
  85
  86	// ===========================================================================
  87
  88	// All directives using iota should be above this one.
  89	// For internal use.
  90	shellCompDirectiveMaxValue
  91
  92	// ShellCompDirectiveDefault indicates to let the shell perform its default
  93	// behavior after completions have been provided.
  94	// This one must be last to avoid messing up the iota count.
  95	ShellCompDirectiveDefault ShellCompDirective = 0
  96)
  97
  98const (
  99	// Constants for the completion command
 100	compCmdName              = "completion"
 101	compCmdNoDescFlagName    = "no-descriptions"
 102	compCmdNoDescFlagDesc    = "disable completion descriptions"
 103	compCmdNoDescFlagDefault = false
 104)
 105
 106// CompletionOptions are the options to control shell completion
 107type CompletionOptions struct {
 108	// DisableDefaultCmd prevents Cobra from creating a default 'completion' command
 109	DisableDefaultCmd bool
 110	// DisableNoDescFlag prevents Cobra from creating the '--no-descriptions' flag
 111	// for shells that support completion descriptions
 112	DisableNoDescFlag bool
 113	// DisableDescriptions turns off all completion descriptions for shells
 114	// that support them
 115	DisableDescriptions bool
 116	// HiddenDefaultCmd makes the default 'completion' command hidden
 117	HiddenDefaultCmd bool
 118}
 119
 120// Completion is a string that can be used for completions
 121//
 122// two formats are supported:
 123//   - the completion choice
 124//   - the completion choice with a textual description (separated by a TAB).
 125//
 126// [CompletionWithDesc] can be used to create a completion string with a textual description.
 127//
 128// Note: Go type alias is used to provide a more descriptive name in the documentation, but any string can be used.
 129type Completion = string
 130
 131// CompletionFunc is a function that provides completion results.
 132type CompletionFunc = func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective)
 133
 134// CompletionWithDesc returns a [Completion] with a description by using the TAB delimited format.
 135func CompletionWithDesc(choice string, description string) Completion {
 136	return choice + "\t" + description
 137}
 138
 139// NoFileCompletions can be used to disable file completion for commands that should
 140// not trigger file completions.
 141//
 142// This method satisfies [CompletionFunc].
 143// It can be used with [Command.RegisterFlagCompletionFunc] and for [Command.ValidArgsFunction].
 144func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) {
 145	return nil, ShellCompDirectiveNoFileComp
 146}
 147
 148// FixedCompletions can be used to create a completion function which always
 149// returns the same results.
 150//
 151// This method returns a function that satisfies [CompletionFunc]
 152// It can be used with [Command.RegisterFlagCompletionFunc] and for [Command.ValidArgsFunction].
 153func FixedCompletions(choices []Completion, directive ShellCompDirective) CompletionFunc {
 154	return func(cmd *Command, args []string, toComplete string) ([]Completion, ShellCompDirective) {
 155		return choices, directive
 156	}
 157}
 158
 159// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag.
 160//
 161// You can use pre-defined completion functions such as [FixedCompletions] or [NoFileCompletions],
 162// or you can define your own.
 163func (c *Command) RegisterFlagCompletionFunc(flagName string, f CompletionFunc) error {
 164	flag := c.Flag(flagName)
 165	if flag == nil {
 166		return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName)
 167	}
 168	flagCompletionMutex.Lock()
 169	defer flagCompletionMutex.Unlock()
 170
 171	if _, exists := flagCompletionFunctions[flag]; exists {
 172		return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' already registered", flagName)
 173	}
 174	flagCompletionFunctions[flag] = f
 175	return nil
 176}
 177
 178// GetFlagCompletionFunc returns the completion function for the given flag of the command, if available.
 179func (c *Command) GetFlagCompletionFunc(flagName string) (CompletionFunc, bool) {
 180	flag := c.Flag(flagName)
 181	if flag == nil {
 182		return nil, false
 183	}
 184
 185	flagCompletionMutex.RLock()
 186	defer flagCompletionMutex.RUnlock()
 187
 188	completionFunc, exists := flagCompletionFunctions[flag]
 189	return completionFunc, exists
 190}
 191
 192// Returns a string listing the different directive enabled in the specified parameter
 193func (d ShellCompDirective) string() string {
 194	var directives []string
 195	if d&ShellCompDirectiveError != 0 {
 196		directives = append(directives, "ShellCompDirectiveError")
 197	}
 198	if d&ShellCompDirectiveNoSpace != 0 {
 199		directives = append(directives, "ShellCompDirectiveNoSpace")
 200	}
 201	if d&ShellCompDirectiveNoFileComp != 0 {
 202		directives = append(directives, "ShellCompDirectiveNoFileComp")
 203	}
 204	if d&ShellCompDirectiveFilterFileExt != 0 {
 205		directives = append(directives, "ShellCompDirectiveFilterFileExt")
 206	}
 207	if d&ShellCompDirectiveFilterDirs != 0 {
 208		directives = append(directives, "ShellCompDirectiveFilterDirs")
 209	}
 210	if d&ShellCompDirectiveKeepOrder != 0 {
 211		directives = append(directives, "ShellCompDirectiveKeepOrder")
 212	}
 213	if len(directives) == 0 {
 214		directives = append(directives, "ShellCompDirectiveDefault")
 215	}
 216
 217	if d >= shellCompDirectiveMaxValue {
 218		return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d)
 219	}
 220	return strings.Join(directives, ", ")
 221}
 222
 223// initCompleteCmd adds a special hidden command that can be used to request custom completions.
 224func (c *Command) initCompleteCmd(args []string) {
 225	completeCmd := &Command{
 226		Use:                   fmt.Sprintf("%s [command-line]", ShellCompRequestCmd),
 227		Aliases:               []string{ShellCompNoDescRequestCmd},
 228		DisableFlagsInUseLine: true,
 229		Hidden:                true,
 230		DisableFlagParsing:    true,
 231		Args:                  MinimumNArgs(1),
 232		Short:                 "Request shell completion choices for the specified command-line",
 233		Long: fmt.Sprintf("%[2]s is a special command that is used by the shell completion logic\n%[1]s",
 234			"to request completion choices for the specified command-line.", ShellCompRequestCmd),
 235		Run: func(cmd *Command, args []string) {
 236			finalCmd, completions, directive, err := cmd.getCompletions(args)
 237			if err != nil {
 238				CompErrorln(err.Error())
 239				// Keep going for multiple reasons:
 240				// 1- There could be some valid completions even though there was an error
 241				// 2- Even without completions, we need to print the directive
 242			}
 243
 244			noDescriptions := cmd.CalledAs() == ShellCompNoDescRequestCmd
 245			if !noDescriptions {
 246				if doDescriptions, err := strconv.ParseBool(getEnvConfig(cmd, configEnvVarSuffixDescriptions)); err == nil {
 247					noDescriptions = !doDescriptions
 248				}
 249			}
 250			noActiveHelp := GetActiveHelpConfig(finalCmd) == activeHelpGlobalDisable
 251			out := finalCmd.OutOrStdout()
 252			for _, comp := range completions {
 253				if noActiveHelp && strings.HasPrefix(comp, activeHelpMarker) {
 254					// Remove all activeHelp entries if it's disabled.
 255					continue
 256				}
 257				if noDescriptions {
 258					// Remove any description that may be included following a tab character.
 259					comp = strings.SplitN(comp, "\t", 2)[0]
 260				}
 261
 262				// Make sure we only write the first line to the output.
 263				// This is needed if a description contains a linebreak.
 264				// Otherwise the shell scripts will interpret the other lines as new flags
 265				// and could therefore provide a wrong completion.
 266				comp = strings.SplitN(comp, "\n", 2)[0]
 267
 268				// Finally trim the completion.  This is especially important to get rid
 269				// of a trailing tab when there are no description following it.
 270				// For example, a sub-command without a description should not be completed
 271				// with a tab at the end (or else zsh will show a -- following it
 272				// although there is no description).
 273				comp = strings.TrimSpace(comp)
 274
 275				// Print each possible completion to the output for the completion script to consume.
 276				fmt.Fprintln(out, comp)
 277			}
 278
 279			// As the last printout, print the completion directive for the completion script to parse.
 280			// The directive integer must be that last character following a single colon (:).
 281			// The completion script expects :<directive>
 282			fmt.Fprintf(out, ":%d\n", directive)
 283
 284			// Print some helpful info to stderr for the user to understand.
 285			// Output from stderr must be ignored by the completion script.
 286			fmt.Fprintf(finalCmd.ErrOrStderr(), "Completion ended with directive: %s\n", directive.string())
 287		},
 288	}
 289	c.AddCommand(completeCmd)
 290	subCmd, _, err := c.Find(args)
 291	if err != nil || subCmd.Name() != ShellCompRequestCmd {
 292		// Only create this special command if it is actually being called.
 293		// This reduces possible side-effects of creating such a command;
 294		// for example, having this command would cause problems to a
 295		// cobra program that only consists of the root command, since this
 296		// command would cause the root command to suddenly have a subcommand.
 297		c.RemoveCommand(completeCmd)
 298	}
 299}
 300
 301// SliceValue is a reduced version of [pflag.SliceValue]. It is used to detect
 302// flags that accept multiple values and therefore can provide completion
 303// multiple times.
 304type SliceValue interface {
 305	// GetSlice returns the flag value list as an array of strings.
 306	GetSlice() []string
 307}
 308
 309func (c *Command) getCompletions(args []string) (*Command, []Completion, ShellCompDirective, error) {
 310	// The last argument, which is not completely typed by the user,
 311	// should not be part of the list of arguments
 312	toComplete := args[len(args)-1]
 313	trimmedArgs := args[:len(args)-1]
 314
 315	var finalCmd *Command
 316	var finalArgs []string
 317	var err error
 318	// Find the real command for which completion must be performed
 319	// check if we need to traverse here to parse local flags on parent commands
 320	if c.Root().TraverseChildren {
 321		finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs)
 322	} else {
 323		// For Root commands that don't specify any value for their Args fields, when we call
 324		// Find(), if those Root commands don't have any sub-commands, they will accept arguments.
 325		// However, because we have added the __complete sub-command in the current code path, the
 326		// call to Find() -> legacyArgs() will return an error if there are any arguments.
 327		// To avoid this, we first remove the __complete command to get back to having no sub-commands.
 328		rootCmd := c.Root()
 329		if len(rootCmd.Commands()) == 1 {
 330			rootCmd.RemoveCommand(c)
 331		}
 332
 333		finalCmd, finalArgs, err = rootCmd.Find(trimmedArgs)
 334	}
 335	if err != nil {
 336		// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
 337		return c, []Completion{}, ShellCompDirectiveDefault, fmt.Errorf("unable to find a command for arguments: %v", trimmedArgs)
 338	}
 339	finalCmd.ctx = c.ctx
 340
 341	// These flags are normally added when `execute()` is called on `finalCmd`,
 342	// however, when doing completion, we don't call `finalCmd.execute()`.
 343	// Let's add the --help and --version flag ourselves but only if the finalCmd
 344	// has not disabled flag parsing; if flag parsing is disabled, it is up to the
 345	// finalCmd itself to handle the completion of *all* flags.
 346	if !finalCmd.DisableFlagParsing {
 347		finalCmd.InitDefaultHelpFlag()
 348		finalCmd.InitDefaultVersionFlag()
 349	}
 350
 351	// Check if we are doing flag value completion before parsing the flags.
 352	// This is important because if we are completing a flag value, we need to also
 353	// remove the flag name argument from the list of finalArgs or else the parsing
 354	// could fail due to an invalid value (incomplete) for the flag.
 355	flag, finalArgs, toComplete, flagErr := checkIfFlagCompletion(finalCmd, finalArgs, toComplete)
 356
 357	// Check if interspersed is false or -- was set on a previous arg.
 358	// This works by counting the arguments. Normally -- is not counted as arg but
 359	// if -- was already set or interspersed is false and there is already one arg then
 360	// the extra added -- is counted as arg.
 361	flagCompletion := true
 362	_ = finalCmd.ParseFlags(append(finalArgs, "--"))
 363	newArgCount := finalCmd.Flags().NArg()
 364
 365	// Parse the flags early so we can check if required flags are set
 366	if err = finalCmd.ParseFlags(finalArgs); err != nil {
 367		return finalCmd, []Completion{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error())
 368	}
 369
 370	realArgCount := finalCmd.Flags().NArg()
 371	if newArgCount > realArgCount {
 372		// don't do flag completion (see above)
 373		flagCompletion = false
 374	}
 375	// Error while attempting to parse flags
 376	if flagErr != nil {
 377		// If error type is flagCompError and we don't want flagCompletion we should ignore the error
 378		if _, ok := flagErr.(*flagCompError); !(ok && !flagCompletion) {
 379			return finalCmd, []Completion{}, ShellCompDirectiveDefault, flagErr
 380		}
 381	}
 382
 383	// Look for the --help or --version flags.  If they are present,
 384	// there should be no further completions.
 385	if helpOrVersionFlagPresent(finalCmd) {
 386		return finalCmd, []Completion{}, ShellCompDirectiveNoFileComp, nil
 387	}
 388
 389	// We only remove the flags from the arguments if DisableFlagParsing is not set.
 390	// This is important for commands which have requested to do their own flag completion.
 391	if !finalCmd.DisableFlagParsing {
 392		finalArgs = finalCmd.Flags().Args()
 393	}
 394
 395	if flag != nil && flagCompletion {
 396		// Check if we are completing a flag value subject to annotations
 397		if validExts, present := flag.Annotations[BashCompFilenameExt]; present {
 398			if len(validExts) != 0 {
 399				// File completion filtered by extensions
 400				return finalCmd, validExts, ShellCompDirectiveFilterFileExt, nil
 401			}
 402
 403			// The annotation requests simple file completion.  There is no reason to do
 404			// that since it is the default behavior anyway.  Let's ignore this annotation
 405			// in case the program also registered a completion function for this flag.
 406			// Even though it is a mistake on the program's side, let's be nice when we can.
 407		}
 408
 409		if subDir, present := flag.Annotations[BashCompSubdirsInDir]; present {
 410			if len(subDir) == 1 {
 411				// Directory completion from within a directory
 412				return finalCmd, subDir, ShellCompDirectiveFilterDirs, nil
 413			}
 414			// Directory completion
 415			return finalCmd, []Completion{}, ShellCompDirectiveFilterDirs, nil
 416		}
 417	}
 418
 419	var completions []Completion
 420	var directive ShellCompDirective
 421
 422	// Enforce flag groups before doing flag completions
 423	finalCmd.enforceFlagGroupsForCompletion()
 424
 425	// Note that we want to perform flagname completion even if finalCmd.DisableFlagParsing==true;
 426	// doing this allows for completion of persistent flag names even for commands that disable flag parsing.
 427	//
 428	// When doing completion of a flag name, as soon as an argument starts with
 429	// a '-' we know it is a flag.  We cannot use isFlagArg() here as it requires
 430	// the flag name to be complete
 431	if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") && flagCompletion {
 432		// First check for required flags
 433		completions = completeRequireFlags(finalCmd, toComplete)
 434
 435		// If we have not found any required flags, only then can we show regular flags
 436		if len(completions) == 0 {
 437			doCompleteFlags := func(flag *pflag.Flag) {
 438				_, acceptsMultiple := flag.Value.(SliceValue)
 439				acceptsMultiple = acceptsMultiple ||
 440					strings.Contains(flag.Value.Type(), "Slice") ||
 441					strings.Contains(flag.Value.Type(), "Array") ||
 442					strings.HasPrefix(flag.Value.Type(), "stringTo")
 443
 444				if !flag.Changed || acceptsMultiple {
 445					// If the flag is not already present, or if it can be specified multiple times (Array, Slice, or stringTo)
 446					// we suggest it as a completion
 447					completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
 448				}
 449			}
 450
 451			// We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
 452			// that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
 453			// non-inherited flags.
 454			finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
 455				doCompleteFlags(flag)
 456			})
 457			// Try to complete non-inherited flags even if DisableFlagParsing==true.
 458			// This allows programs to tell Cobra about flags for completion even
 459			// if the actual parsing of flags is not done by Cobra.
 460			// For instance, Helm uses this to provide flag name completion for
 461			// some of its plugins.
 462			finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
 463				doCompleteFlags(flag)
 464			})
 465		}
 466
 467		directive = ShellCompDirectiveNoFileComp
 468		if len(completions) == 1 && strings.HasSuffix(completions[0], "=") {
 469			// If there is a single completion, the shell usually adds a space
 470			// after the completion.  We don't want that if the flag ends with an =
 471			directive = ShellCompDirectiveNoSpace
 472		}
 473
 474		if !finalCmd.DisableFlagParsing {
 475			// If DisableFlagParsing==false, we have completed the flags as known by Cobra;
 476			// we can return what we found.
 477			// If DisableFlagParsing==true, Cobra may not be aware of all flags, so we
 478			// let the logic continue to see if ValidArgsFunction needs to be called.
 479			return finalCmd, completions, directive, nil
 480		}
 481	} else {
 482		directive = ShellCompDirectiveDefault
 483		if flag == nil {
 484			foundLocalNonPersistentFlag := false
 485			// If TraverseChildren is true on the root command we don't check for
 486			// local flags because we can use a local flag on a parent command
 487			if !finalCmd.Root().TraverseChildren {
 488				// Check if there are any local, non-persistent flags on the command-line
 489				localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()
 490				finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
 491					if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {
 492						foundLocalNonPersistentFlag = true
 493					}
 494				})
 495			}
 496
 497			// Complete subcommand names, including the help command
 498			if len(finalArgs) == 0 && !foundLocalNonPersistentFlag {
 499				// We only complete sub-commands if:
 500				// - there are no arguments on the command-line and
 501				// - there are no local, non-persistent flags on the command-line or TraverseChildren is true
 502				for _, subCmd := range finalCmd.Commands() {
 503					if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
 504						if strings.HasPrefix(subCmd.Name(), toComplete) {
 505							completions = append(completions, CompletionWithDesc(subCmd.Name(), subCmd.Short))
 506						}
 507						directive = ShellCompDirectiveNoFileComp
 508					}
 509				}
 510			}
 511
 512			// Complete required flags even without the '-' prefix
 513			completions = append(completions, completeRequireFlags(finalCmd, toComplete)...)
 514
 515			// Always complete ValidArgs, even if we are completing a subcommand name.
 516			// This is for commands that have both subcommands and ValidArgs.
 517			if len(finalCmd.ValidArgs) > 0 {
 518				if len(finalArgs) == 0 {
 519					// ValidArgs are only for the first argument
 520					for _, validArg := range finalCmd.ValidArgs {
 521						if strings.HasPrefix(validArg, toComplete) {
 522							completions = append(completions, validArg)
 523						}
 524					}
 525					directive = ShellCompDirectiveNoFileComp
 526
 527					// If no completions were found within commands or ValidArgs,
 528					// see if there are any ArgAliases that should be completed.
 529					if len(completions) == 0 {
 530						for _, argAlias := range finalCmd.ArgAliases {
 531							if strings.HasPrefix(argAlias, toComplete) {
 532								completions = append(completions, argAlias)
 533							}
 534						}
 535					}
 536				}
 537
 538				// If there are ValidArgs specified (even if they don't match), we stop completion.
 539				// Only one of ValidArgs or ValidArgsFunction can be used for a single command.
 540				return finalCmd, completions, directive, nil
 541			}
 542
 543			// Let the logic continue so as to add any ValidArgsFunction completions,
 544			// even if we already found sub-commands.
 545			// This is for commands that have subcommands but also specify a ValidArgsFunction.
 546		}
 547	}
 548
 549	// Find the completion function for the flag or command
 550	var completionFn CompletionFunc
 551	if flag != nil && flagCompletion {
 552		flagCompletionMutex.RLock()
 553		completionFn = flagCompletionFunctions[flag]
 554		flagCompletionMutex.RUnlock()
 555	} else {
 556		completionFn = finalCmd.ValidArgsFunction
 557	}
 558	if completionFn != nil {
 559		// Go custom completion defined for this flag or command.
 560		// Call the registered completion function to get the completions.
 561		var comps []Completion
 562		comps, directive = completionFn(finalCmd, finalArgs, toComplete)
 563		completions = append(completions, comps...)
 564	}
 565
 566	return finalCmd, completions, directive, nil
 567}
 568
 569func helpOrVersionFlagPresent(cmd *Command) bool {
 570	if versionFlag := cmd.Flags().Lookup("version"); versionFlag != nil &&
 571		len(versionFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && versionFlag.Changed {
 572		return true
 573	}
 574	if helpFlag := cmd.Flags().Lookup(helpFlagName); helpFlag != nil &&
 575		len(helpFlag.Annotations[FlagSetByCobraAnnotation]) > 0 && helpFlag.Changed {
 576		return true
 577	}
 578	return false
 579}
 580
 581func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []Completion {
 582	if nonCompletableFlag(flag) {
 583		return []Completion{}
 584	}
 585
 586	var completions []Completion
 587	flagName := "--" + flag.Name
 588	if strings.HasPrefix(flagName, toComplete) {
 589		// Flag without the =
 590		completions = append(completions, CompletionWithDesc(flagName, flag.Usage))
 591
 592		// Why suggest both long forms: --flag and --flag= ?
 593		// This forces the user to *always* have to type either an = or a space after the flag name.
 594		// Let's be nice and avoid making users have to do that.
 595		// Since boolean flags and shortname flags don't show the = form, let's go that route and never show it.
 596		// The = form will still work, we just won't suggest it.
 597		// This also makes the list of suggested flags shorter as we avoid all the = forms.
 598		//
 599		// if len(flag.NoOptDefVal) == 0 {
 600		// 	// Flag requires a value, so it can be suffixed with =
 601		// 	flagName += "="
 602		// 	completions = append(completions, CompletionWithDesc(flagName, flag.Usage))
 603		// }
 604	}
 605
 606	flagName = "-" + flag.Shorthand
 607	if len(flag.Shorthand) > 0 && strings.HasPrefix(flagName, toComplete) {
 608		completions = append(completions, CompletionWithDesc(flagName, flag.Usage))
 609	}
 610
 611	return completions
 612}
 613
 614func completeRequireFlags(finalCmd *Command, toComplete string) []Completion {
 615	var completions []Completion
 616
 617	doCompleteRequiredFlags := func(flag *pflag.Flag) {
 618		if _, present := flag.Annotations[BashCompOneRequiredFlag]; present {
 619			if !flag.Changed {
 620				// If the flag is not already present, we suggest it as a completion
 621				completions = append(completions, getFlagNameCompletions(flag, toComplete)...)
 622			}
 623		}
 624	}
 625
 626	// We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands
 627	// that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and
 628	// non-inherited flags.
 629	finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
 630		doCompleteRequiredFlags(flag)
 631	})
 632	finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
 633		doCompleteRequiredFlags(flag)
 634	})
 635
 636	return completions
 637}
 638
 639func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) {
 640	if finalCmd.DisableFlagParsing {
 641		// We only do flag completion if we are allowed to parse flags
 642		// This is important for commands which have requested to do their own flag completion.
 643		return nil, args, lastArg, nil
 644	}
 645
 646	var flagName string
 647	trimmedArgs := args
 648	flagWithEqual := false
 649	orgLastArg := lastArg
 650
 651	// When doing completion of a flag name, as soon as an argument starts with
 652	// a '-' we know it is a flag.  We cannot use isFlagArg() here as that function
 653	// requires the flag name to be complete
 654	if len(lastArg) > 0 && lastArg[0] == '-' {
 655		if index := strings.Index(lastArg, "="); index >= 0 {
 656			// Flag with an =
 657			if strings.HasPrefix(lastArg[:index], "--") {
 658				// Flag has full name
 659				flagName = lastArg[2:index]
 660			} else {
 661				// Flag is shorthand
 662				// We have to get the last shorthand flag name
 663				// e.g. `-asd` => d to provide the correct completion
 664				// https://github.com/spf13/cobra/issues/1257
 665				flagName = lastArg[index-1 : index]
 666			}
 667			lastArg = lastArg[index+1:]
 668			flagWithEqual = true
 669		} else {
 670			// Normal flag completion
 671			return nil, args, lastArg, nil
 672		}
 673	}
 674
 675	if len(flagName) == 0 {
 676		if len(args) > 0 {
 677			prevArg := args[len(args)-1]
 678			if isFlagArg(prevArg) {
 679				// Only consider the case where the flag does not contain an =.
 680				// If the flag contains an = it means it has already been fully processed,
 681				// so we don't need to deal with it here.
 682				if index := strings.Index(prevArg, "="); index < 0 {
 683					if strings.HasPrefix(prevArg, "--") {
 684						// Flag has full name
 685						flagName = prevArg[2:]
 686					} else {
 687						// Flag is shorthand
 688						// We have to get the last shorthand flag name
 689						// e.g. `-asd` => d to provide the correct completion
 690						// https://github.com/spf13/cobra/issues/1257
 691						flagName = prevArg[len(prevArg)-1:]
 692					}
 693					// Remove the uncompleted flag or else there could be an error created
 694					// for an invalid value for that flag
 695					trimmedArgs = args[:len(args)-1]
 696				}
 697			}
 698		}
 699	}
 700
 701	if len(flagName) == 0 {
 702		// Not doing flag completion
 703		return nil, trimmedArgs, lastArg, nil
 704	}
 705
 706	flag := findFlag(finalCmd, flagName)
 707	if flag == nil {
 708		// Flag not supported by this command, the interspersed option might be set so return the original args
 709		return nil, args, orgLastArg, &flagCompError{subCommand: finalCmd.Name(), flagName: flagName}
 710	}
 711
 712	if !flagWithEqual {
 713		if len(flag.NoOptDefVal) != 0 {
 714			// We had assumed dealing with a two-word flag but the flag is a boolean flag.
 715			// In that case, there is no value following it, so we are not really doing flag completion.
 716			// Reset everything to do noun completion.
 717			trimmedArgs = args
 718			flag = nil
 719		}
 720	}
 721
 722	return flag, trimmedArgs, lastArg, nil
 723}
 724
 725// InitDefaultCompletionCmd adds a default 'completion' command to c.
 726// This function will do nothing if any of the following is true:
 727// 1- the feature has been explicitly disabled by the program,
 728// 2- c has no subcommands (to avoid creating one),
 729// 3- c already has a 'completion' command provided by the program.
 730func (c *Command) InitDefaultCompletionCmd(args ...string) {
 731	if c.CompletionOptions.DisableDefaultCmd {
 732		return
 733	}
 734
 735	for _, cmd := range c.commands {
 736		if cmd.Name() == compCmdName || cmd.HasAlias(compCmdName) {
 737			// A completion command is already available
 738			return
 739		}
 740	}
 741
 742	haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions
 743
 744	// Special case to know if there are sub-commands or not.
 745	hasSubCommands := false
 746	for _, cmd := range c.commands {
 747		if cmd.Name() != ShellCompRequestCmd && cmd.Name() != helpCommandName {
 748			// We found a real sub-command (not 'help' or '__complete')
 749			hasSubCommands = true
 750			break
 751		}
 752	}
 753
 754	completionCmd := &Command{
 755		Use:   compCmdName,
 756		Short: "Generate the autocompletion script for the specified shell",
 757		Long: fmt.Sprintf(`Generate the autocompletion script for %[1]s for the specified shell.
 758See each sub-command's help for details on how to use the generated script.
 759`, c.Root().Name()),
 760		Args:              NoArgs,
 761		ValidArgsFunction: NoFileCompletions,
 762		Hidden:            c.CompletionOptions.HiddenDefaultCmd,
 763		GroupID:           c.completionCommandGroupID,
 764	}
 765	c.AddCommand(completionCmd)
 766
 767	if !hasSubCommands {
 768		// If the 'completion' command will be the only sub-command,
 769		// we only create it if it is actually being called.
 770		// This avoids breaking programs that would suddenly find themselves with
 771		// a subcommand, which would prevent them from accepting arguments.
 772		// We also create the 'completion' command if the user is triggering
 773		// shell completion for it (prog __complete completion '')
 774		subCmd, cmdArgs, err := c.Find(args)
 775		if err != nil || subCmd.Name() != compCmdName &&
 776			!(subCmd.Name() == ShellCompRequestCmd && len(cmdArgs) > 1 && cmdArgs[0] == compCmdName) {
 777			// The completion command is not being called or being completed so we remove it.
 778			c.RemoveCommand(completionCmd)
 779			return
 780		}
 781	}
 782
 783	out := c.OutOrStdout()
 784	noDesc := c.CompletionOptions.DisableDescriptions
 785	shortDesc := "Generate the autocompletion script for %s"
 786	bash := &Command{
 787		Use:   "bash",
 788		Short: fmt.Sprintf(shortDesc, "bash"),
 789		Long: fmt.Sprintf(`Generate the autocompletion script for the bash shell.
 790
 791This script depends on the 'bash-completion' package.
 792If it is not installed already, you can install it via your OS's package manager.
 793
 794To load completions in your current shell session:
 795
 796	source <(%[1]s completion bash)
 797
 798To load completions for every new session, execute once:
 799
 800#### Linux:
 801
 802	%[1]s completion bash > /etc/bash_completion.d/%[1]s
 803
 804#### macOS:
 805
 806	%[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s
 807
 808You will need to start a new shell for this setup to take effect.
 809`, c.Root().Name()),
 810		Args:                  NoArgs,
 811		DisableFlagsInUseLine: true,
 812		ValidArgsFunction:     NoFileCompletions,
 813		RunE: func(cmd *Command, args []string) error {
 814			return cmd.Root().GenBashCompletionV2(out, !noDesc)
 815		},
 816	}
 817	if haveNoDescFlag {
 818		bash.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
 819	}
 820
 821	zsh := &Command{
 822		Use:   "zsh",
 823		Short: fmt.Sprintf(shortDesc, "zsh"),
 824		Long: fmt.Sprintf(`Generate the autocompletion script for the zsh shell.
 825
 826If shell completion is not already enabled in your environment you will need
 827to enable it.  You can execute the following once:
 828
 829	echo "autoload -U compinit; compinit" >> ~/.zshrc
 830
 831To load completions in your current shell session:
 832
 833	source <(%[1]s completion zsh)
 834
 835To load completions for every new session, execute once:
 836
 837#### Linux:
 838
 839	%[1]s completion zsh > "${fpath[1]}/_%[1]s"
 840
 841#### macOS:
 842
 843	%[1]s completion zsh > $(brew --prefix)/share/zsh/site-functions/_%[1]s
 844
 845You will need to start a new shell for this setup to take effect.
 846`, c.Root().Name()),
 847		Args:              NoArgs,
 848		ValidArgsFunction: NoFileCompletions,
 849		RunE: func(cmd *Command, args []string) error {
 850			if noDesc {
 851				return cmd.Root().GenZshCompletionNoDesc(out)
 852			}
 853			return cmd.Root().GenZshCompletion(out)
 854		},
 855	}
 856	if haveNoDescFlag {
 857		zsh.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
 858	}
 859
 860	fish := &Command{
 861		Use:   "fish",
 862		Short: fmt.Sprintf(shortDesc, "fish"),
 863		Long: fmt.Sprintf(`Generate the autocompletion script for the fish shell.
 864
 865To load completions in your current shell session:
 866
 867	%[1]s completion fish | source
 868
 869To load completions for every new session, execute once:
 870
 871	%[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
 872
 873You will need to start a new shell for this setup to take effect.
 874`, c.Root().Name()),
 875		Args:              NoArgs,
 876		ValidArgsFunction: NoFileCompletions,
 877		RunE: func(cmd *Command, args []string) error {
 878			return cmd.Root().GenFishCompletion(out, !noDesc)
 879		},
 880	}
 881	if haveNoDescFlag {
 882		fish.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
 883	}
 884
 885	powershell := &Command{
 886		Use:   "powershell",
 887		Short: fmt.Sprintf(shortDesc, "powershell"),
 888		Long: fmt.Sprintf(`Generate the autocompletion script for powershell.
 889
 890To load completions in your current shell session:
 891
 892	%[1]s completion powershell | Out-String | Invoke-Expression
 893
 894To load completions for every new session, add the output of the above command
 895to your powershell profile.
 896`, c.Root().Name()),
 897		Args:              NoArgs,
 898		ValidArgsFunction: NoFileCompletions,
 899		RunE: func(cmd *Command, args []string) error {
 900			if noDesc {
 901				return cmd.Root().GenPowerShellCompletion(out)
 902			}
 903			return cmd.Root().GenPowerShellCompletionWithDesc(out)
 904
 905		},
 906	}
 907	if haveNoDescFlag {
 908		powershell.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc)
 909	}
 910
 911	completionCmd.AddCommand(bash, zsh, fish, powershell)
 912}
 913
 914func findFlag(cmd *Command, name string) *pflag.Flag {
 915	flagSet := cmd.Flags()
 916	if len(name) == 1 {
 917		// First convert the short flag into a long flag
 918		// as the cmd.Flag() search only accepts long flags
 919		if short := flagSet.ShorthandLookup(name); short != nil {
 920			name = short.Name
 921		} else {
 922			set := cmd.InheritedFlags()
 923			if short = set.ShorthandLookup(name); short != nil {
 924				name = short.Name
 925			} else {
 926				return nil
 927			}
 928		}
 929	}
 930	return cmd.Flag(name)
 931}
 932
 933// CompDebug prints the specified string to the same file as where the
 934// completion script prints its logs.
 935// Note that completion printouts should never be on stdout as they would
 936// be wrongly interpreted as actual completion choices by the completion script.
 937func CompDebug(msg string, printToStdErr bool) {
 938	msg = fmt.Sprintf("[Debug] %s", msg)
 939
 940	// Such logs are only printed when the user has set the environment
 941	// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
 942	if path := os.Getenv("BASH_COMP_DEBUG_FILE"); path != "" {
 943		f, err := os.OpenFile(path,
 944			os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
 945		if err == nil {
 946			defer f.Close()
 947			WriteStringAndCheck(f, msg)
 948		}
 949	}
 950
 951	if printToStdErr {
 952		// Must print to stderr for this not to be read by the completion script.
 953		fmt.Fprint(os.Stderr, msg)
 954	}
 955}
 956
 957// CompDebugln prints the specified string with a newline at the end
 958// to the same file as where the completion script prints its logs.
 959// Such logs are only printed when the user has set the environment
 960// variable BASH_COMP_DEBUG_FILE to the path of some file to be used.
 961func CompDebugln(msg string, printToStdErr bool) {
 962	CompDebug(fmt.Sprintf("%s\n", msg), printToStdErr)
 963}
 964
 965// CompError prints the specified completion message to stderr.
 966func CompError(msg string) {
 967	msg = fmt.Sprintf("[Error] %s", msg)
 968	CompDebug(msg, true)
 969}
 970
 971// CompErrorln prints the specified completion message to stderr with a newline at the end.
 972func CompErrorln(msg string) {
 973	CompError(fmt.Sprintf("%s\n", msg))
 974}
 975
 976// These values should not be changed: users will be using them explicitly.
 977const (
 978	configEnvVarGlobalPrefix       = "COBRA"
 979	configEnvVarSuffixDescriptions = "COMPLETION_DESCRIPTIONS"
 980)
 981
 982var configEnvVarPrefixSubstRegexp = regexp.MustCompile(`[^A-Z0-9_]`)
 983
 984// configEnvVar returns the name of the program-specific configuration environment
 985// variable.  It has the format <PROGRAM>_<SUFFIX> where <PROGRAM> is the name of the
 986// root command in upper case, with all non-ASCII-alphanumeric characters replaced by `_`.
 987func configEnvVar(name, suffix string) string {
 988	// This format should not be changed: users will be using it explicitly.
 989	v := strings.ToUpper(fmt.Sprintf("%s_%s", name, suffix))
 990	v = configEnvVarPrefixSubstRegexp.ReplaceAllString(v, "_")
 991	return v
 992}
 993
 994// getEnvConfig returns the value of the configuration environment variable
 995// <PROGRAM>_<SUFFIX> where <PROGRAM> is the name of the root command in upper
 996// case, with all non-ASCII-alphanumeric characters replaced by `_`.
 997// If the value is empty or not set, the value of the environment variable
 998// COBRA_<SUFFIX> is returned instead.
 999func getEnvConfig(cmd *Command, suffix string) string {
1000	v := os.Getenv(configEnvVar(cmd.Root().Name(), suffix))
1001	if v == "" {
1002		v = os.Getenv(configEnvVar(configEnvVarGlobalPrefix, suffix))
1003	}
1004	return v
1005}