1package cobra 2 3import ( 4 "fmt" 5 "os" 6 "strings" 7 "sync" 8 9 "github.com/spf13/pflag" 10) 11 12const ( 13 // ShellCompRequestCmd is the name of the hidden command that is used to request 14 // completion results from the program. It is used by the shell completion scripts. 15 ShellCompRequestCmd = "__complete" 16 // ShellCompNoDescRequestCmd is the name of the hidden command that is used to request 17 // completion results without their description. It is used by the shell completion scripts. 18 ShellCompNoDescRequestCmd = "__completeNoDesc" 19) 20 21// Global map of flag completion functions. Make sure to use flagCompletionMutex before you try to read and write from it. 22var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){} 23 24// lock for reading and writing from flagCompletionFunctions 25var flagCompletionMutex = &sync.RWMutex{} 26 27// ShellCompDirective is a bit map representing the different behaviors the shell 28// can be instructed to have once completions have been provided. 29type ShellCompDirective int 30 31type flagCompError struct { 32 subCommand string 33 flagName string 34} 35 36func (e *flagCompError) Error() string { 37 return "Subcommand '" + e.subCommand + "' does not support flag '" + e.flagName + "'" 38} 39 40const ( 41 // ShellCompDirectiveError indicates an error occurred and completions should be ignored. 42 ShellCompDirectiveError ShellCompDirective = 1 << iota 43 44 // ShellCompDirectiveNoSpace indicates that the shell should not add a space 45 // after the completion even if there is a single completion provided. 46 ShellCompDirectiveNoSpace 47 48 // ShellCompDirectiveNoFileComp indicates that the shell should not provide 49 // file completion even when no completion is provided. 50 ShellCompDirectiveNoFileComp 51 52 // ShellCompDirectiveFilterFileExt indicates that the provided completions 53 // should be used as file extension filters. 54 // For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename() 55 // is a shortcut to using this directive explicitly. The BashCompFilenameExt 56 // annotation can also be used to obtain the same behavior for flags. 57 ShellCompDirectiveFilterFileExt 58 59 // ShellCompDirectiveFilterDirs indicates that only directory names should 60 // be provided in file completion. To request directory names within another 61 // directory, the returned completions should specify the directory within 62 // which to search. The BashCompSubdirsInDir annotation can be used to 63 // obtain the same behavior but only for flags. 64 ShellCompDirectiveFilterDirs 65 66 // =========================================================================== 67 68 // All directives using iota should be above this one. 69 // For internal use. 70 shellCompDirectiveMaxValue 71 72 // ShellCompDirectiveDefault indicates to let the shell perform its default 73 // behavior after completions have been provided. 74 // This one must be last to avoid messing up the iota count. 75 ShellCompDirectiveDefault ShellCompDirective = 0 76) 77 78const ( 79 // Constants for the completion command 80 compCmdName = "completion" 81 compCmdNoDescFlagName = "no-descriptions" 82 compCmdNoDescFlagDesc = "disable completion descriptions" 83 compCmdNoDescFlagDefault = false 84) 85 86// CompletionOptions are the options to control shell completion 87type CompletionOptions struct { 88 // DisableDefaultCmd prevents Cobra from creating a default 'completion' command 89 DisableDefaultCmd bool 90 // DisableNoDescFlag prevents Cobra from creating the '--no-descriptions' flag 91 // for shells that support completion descriptions 92 DisableNoDescFlag bool 93 // DisableDescriptions turns off all completion descriptions for shells 94 // that support them 95 DisableDescriptions bool 96} 97 98// NoFileCompletions can be used to disable file completion for commands that should 99// not trigger file completions. 100func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { 101 return nil, ShellCompDirectiveNoFileComp 102} 103 104// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag. 105func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)) error { 106 flag := c.Flag(flagName) 107 if flag == nil { 108 return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName) 109 } 110 flagCompletionMutex.Lock() 111 defer flagCompletionMutex.Unlock() 112 113 if _, exists := flagCompletionFunctions[flag]; exists { 114 return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' already registered", flagName) 115 } 116 flagCompletionFunctions[flag] = f 117 return nil 118} 119 120// Returns a string listing the different directive enabled in the specified parameter 121func (d ShellCompDirective) string() string { 122 var directives []string 123 if d&ShellCompDirectiveError != 0 { 124 directives = append(directives, "ShellCompDirectiveError") 125 } 126 if d&ShellCompDirectiveNoSpace != 0 { 127 directives = append(directives, "ShellCompDirectiveNoSpace") 128 } 129 if d&ShellCompDirectiveNoFileComp != 0 { 130 directives = append(directives, "ShellCompDirectiveNoFileComp") 131 } 132 if d&ShellCompDirectiveFilterFileExt != 0 { 133 directives = append(directives, "ShellCompDirectiveFilterFileExt") 134 } 135 if d&ShellCompDirectiveFilterDirs != 0 { 136 directives = append(directives, "ShellCompDirectiveFilterDirs") 137 } 138 if len(directives) == 0 { 139 directives = append(directives, "ShellCompDirectiveDefault") 140 } 141 142 if d >= shellCompDirectiveMaxValue { 143 return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d) 144 } 145 return strings.Join(directives, ", ") 146} 147 148// Adds a special hidden command that can be used to request custom completions. 149func (c *Command) initCompleteCmd(args []string) { 150 completeCmd := &Command{ 151 Use: fmt.Sprintf("%s [command-line]", ShellCompRequestCmd), 152 Aliases: []string{ShellCompNoDescRequestCmd}, 153 DisableFlagsInUseLine: true, 154 Hidden: true, 155 DisableFlagParsing: true, 156 Args: MinimumNArgs(1), 157 Short: "Request shell completion choices for the specified command-line", 158 Long: fmt.Sprintf("%[2]s is a special command that is used by the shell completion logic\n%[1]s", 159 "to request completion choices for the specified command-line.", ShellCompRequestCmd), 160 Run: func(cmd *Command, args []string) { 161 finalCmd, completions, directive, err := cmd.getCompletions(args) 162 if err != nil { 163 CompErrorln(err.Error()) 164 // Keep going for multiple reasons: 165 // 1- There could be some valid completions even though there was an error 166 // 2- Even without completions, we need to print the directive 167 } 168 169 noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd) 170 for _, comp := range completions { 171 if noDescriptions { 172 // Remove any description that may be included following a tab character. 173 comp = strings.Split(comp, "\t")[0] 174 } 175 176 // Make sure we only write the first line to the output. 177 // This is needed if a description contains a linebreak. 178 // Otherwise the shell scripts will interpret the other lines as new flags 179 // and could therefore provide a wrong completion. 180 comp = strings.Split(comp, "\n")[0] 181 182 // Finally trim the completion. This is especially important to get rid 183 // of a trailing tab when there are no description following it. 184 // For example, a sub-command without a description should not be completed 185 // with a tab at the end (or else zsh will show a -- following it 186 // although there is no description). 187 comp = strings.TrimSpace(comp) 188 189 // Print each possible completion to stdout for the completion script to consume. 190 fmt.Fprintln(finalCmd.OutOrStdout(), comp) 191 } 192 193 // As the last printout, print the completion directive for the completion script to parse. 194 // The directive integer must be that last character following a single colon (:). 195 // The completion script expects :<directive> 196 fmt.Fprintf(finalCmd.OutOrStdout(), ":%d\n", directive) 197 198 // Print some helpful info to stderr for the user to understand. 199 // Output from stderr must be ignored by the completion script. 200 fmt.Fprintf(finalCmd.ErrOrStderr(), "Completion ended with directive: %s\n", directive.string()) 201 }, 202 } 203 c.AddCommand(completeCmd) 204 subCmd, _, err := c.Find(args) 205 if err != nil || subCmd.Name() != ShellCompRequestCmd { 206 // Only create this special command if it is actually being called. 207 // This reduces possible side-effects of creating such a command; 208 // for example, having this command would cause problems to a 209 // cobra program that only consists of the root command, since this 210 // command would cause the root command to suddenly have a subcommand. 211 c.RemoveCommand(completeCmd) 212 } 213} 214 215func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) { 216 // The last argument, which is not completely typed by the user, 217 // should not be part of the list of arguments 218 toComplete := args[len(args)-1] 219 trimmedArgs := args[:len(args)-1] 220 221 var finalCmd *Command 222 var finalArgs []string 223 var err error 224 // Find the real command for which completion must be performed 225 // check if we need to traverse here to parse local flags on parent commands 226 if c.Root().TraverseChildren { 227 finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs) 228 } else { 229 finalCmd, finalArgs, err = c.Root().Find(trimmedArgs) 230 } 231 if err != nil { 232 // Unable to find the real command. E.g., <program> someInvalidCmd <TAB> 233 return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs) 234 } 235 finalCmd.ctx = c.ctx 236 237 // Check if we are doing flag value completion before parsing the flags. 238 // This is important because if we are completing a flag value, we need to also 239 // remove the flag name argument from the list of finalArgs or else the parsing 240 // could fail due to an invalid value (incomplete) for the flag. 241 flag, finalArgs, toComplete, flagErr := checkIfFlagCompletion(finalCmd, finalArgs, toComplete) 242 243 // Check if interspersed is false or -- was set on a previous arg. 244 // This works by counting the arguments. Normally -- is not counted as arg but 245 // if -- was already set or interspersed is false and there is already one arg then 246 // the extra added -- is counted as arg. 247 flagCompletion := true 248 _ = finalCmd.ParseFlags(append(finalArgs, "--")) 249 newArgCount := finalCmd.Flags().NArg() 250 251 // Parse the flags early so we can check if required flags are set 252 if err = finalCmd.ParseFlags(finalArgs); err != nil { 253 return finalCmd, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error()) 254 } 255 256 realArgCount := finalCmd.Flags().NArg() 257 if newArgCount > realArgCount { 258 // don't do flag completion (see above) 259 flagCompletion = false 260 } 261 // Error while attempting to parse flags 262 if flagErr != nil { 263 // If error type is flagCompError and we don't want flagCompletion we should ignore the error 264 if _, ok := flagErr.(*flagCompError); !(ok && !flagCompletion) { 265 return finalCmd, []string{}, ShellCompDirectiveDefault, flagErr 266 } 267 } 268 269 if flag != nil && flagCompletion { 270 // Check if we are completing a flag value subject to annotations 271 if validExts, present := flag.Annotations[BashCompFilenameExt]; present { 272 if len(validExts) != 0 { 273 // File completion filtered by extensions 274 return finalCmd, validExts, ShellCompDirectiveFilterFileExt, nil 275 } 276 277 // The annotation requests simple file completion. There is no reason to do 278 // that since it is the default behavior anyway. Let's ignore this annotation 279 // in case the program also registered a completion function for this flag. 280 // Even though it is a mistake on the program's side, let's be nice when we can. 281 } 282 283 if subDir, present := flag.Annotations[BashCompSubdirsInDir]; present { 284 if len(subDir) == 1 { 285 // Directory completion from within a directory 286 return finalCmd, subDir, ShellCompDirectiveFilterDirs, nil 287 } 288 // Directory completion 289 return finalCmd, []string{}, ShellCompDirectiveFilterDirs, nil 290 } 291 } 292 293 // When doing completion of a flag name, as soon as an argument starts with 294 // a '-' we know it is a flag. We cannot use isFlagArg() here as it requires 295 // the flag name to be complete 296 if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") && flagCompletion { 297 var completions []string 298 299 // First check for required flags 300 completions = completeRequireFlags(finalCmd, toComplete) 301 302 // If we have not found any required flags, only then can we show regular flags 303 if len(completions) == 0 { 304 doCompleteFlags := func(flag *pflag.Flag) { 305 if !flag.Changed || 306 strings.Contains(flag.Value.Type(), "Slice") || 307 strings.Contains(flag.Value.Type(), "Array") { 308 // If the flag is not already present, or if it can be specified multiple times (Array or Slice) 309 // we suggest it as a completion 310 completions = append(completions, getFlagNameCompletions(flag, toComplete)...) 311 } 312 } 313 314 // We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands 315 // that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and 316 // non-inherited flags. 317 finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { 318 doCompleteFlags(flag) 319 }) 320 finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { 321 doCompleteFlags(flag) 322 }) 323 } 324 325 directive := ShellCompDirectiveNoFileComp 326 if len(completions) == 1 && strings.HasSuffix(completions[0], "=") { 327 // If there is a single completion, the shell usually adds a space 328 // after the completion. We don't want that if the flag ends with an = 329 directive = ShellCompDirectiveNoSpace 330 } 331 return finalCmd, completions, directive, nil 332 } 333 334 // We only remove the flags from the arguments if DisableFlagParsing is not set. 335 // This is important for commands which have requested to do their own flag completion. 336 if !finalCmd.DisableFlagParsing { 337 finalArgs = finalCmd.Flags().Args() 338 } 339 340 var completions []string 341 directive := ShellCompDirectiveDefault 342 if flag == nil { 343 foundLocalNonPersistentFlag := false 344 // If TraverseChildren is true on the root command we don't check for 345 // local flags because we can use a local flag on a parent command 346 if !finalCmd.Root().TraverseChildren { 347 // Check if there are any local, non-persistent flags on the command-line 348 localNonPersistentFlags := finalCmd.LocalNonPersistentFlags() 349 finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { 350 if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed { 351 foundLocalNonPersistentFlag = true 352 } 353 }) 354 } 355 356 // Complete subcommand names, including the help command 357 if len(finalArgs) == 0 && !foundLocalNonPersistentFlag { 358 // We only complete sub-commands if: 359 // - there are no arguments on the command-line and 360 // - there are no local, non-persistent flags on the command-line or TraverseChildren is true 361 for _, subCmd := range finalCmd.Commands() { 362 if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand { 363 if strings.HasPrefix(subCmd.Name(), toComplete) { 364 completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short)) 365 } 366 directive = ShellCompDirectiveNoFileComp 367 } 368 } 369 } 370 371 // Complete required flags even without the '-' prefix 372 completions = append(completions, completeRequireFlags(finalCmd, toComplete)...) 373 374 // Always complete ValidArgs, even if we are completing a subcommand name. 375 // This is for commands that have both subcommands and ValidArgs. 376 if len(finalCmd.ValidArgs) > 0 { 377 if len(finalArgs) == 0 { 378 // ValidArgs are only for the first argument 379 for _, validArg := range finalCmd.ValidArgs { 380 if strings.HasPrefix(validArg, toComplete) { 381 completions = append(completions, validArg) 382 } 383 } 384 directive = ShellCompDirectiveNoFileComp 385 386 // If no completions were found within commands or ValidArgs, 387 // see if there are any ArgAliases that should be completed. 388 if len(completions) == 0 { 389 for _, argAlias := range finalCmd.ArgAliases { 390 if strings.HasPrefix(argAlias, toComplete) { 391 completions = append(completions, argAlias) 392 } 393 } 394 } 395 } 396 397 // If there are ValidArgs specified (even if they don't match), we stop completion. 398 // Only one of ValidArgs or ValidArgsFunction can be used for a single command. 399 return finalCmd, completions, directive, nil 400 } 401 402 // Let the logic continue so as to add any ValidArgsFunction completions, 403 // even if we already found sub-commands. 404 // This is for commands that have subcommands but also specify a ValidArgsFunction. 405 } 406 407 // Find the completion function for the flag or command 408 var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) 409 if flag != nil && flagCompletion { 410 flagCompletionMutex.RLock() 411 completionFn = flagCompletionFunctions[flag] 412 flagCompletionMutex.RUnlock() 413 } else { 414 completionFn = finalCmd.ValidArgsFunction 415 } 416 if completionFn != nil { 417 // Go custom completion defined for this flag or command. 418 // Call the registered completion function to get the completions. 419 var comps []string 420 comps, directive = completionFn(finalCmd, finalArgs, toComplete) 421 completions = append(completions, comps...) 422 } 423 424 return finalCmd, completions, directive, nil 425} 426 427func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string { 428 if nonCompletableFlag(flag) { 429 return []string{} 430 } 431 432 var completions []string 433 flagName := "--" + flag.Name 434 if strings.HasPrefix(flagName, toComplete) { 435 // Flag without the = 436 completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage)) 437 438 // Why suggest both long forms: --flag and --flag= ? 439 // This forces the user to *always* have to type either an = or a space after the flag name. 440 // Let's be nice and avoid making users have to do that. 441 // Since boolean flags and shortname flags don't show the = form, let's go that route and never show it. 442 // The = form will still work, we just won't suggest it. 443 // This also makes the list of suggested flags shorter as we avoid all the = forms. 444 // 445 // if len(flag.NoOptDefVal) == 0 { 446 // // Flag requires a value, so it can be suffixed with = 447 // flagName += "=" 448 // completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage)) 449 // } 450 } 451 452 flagName = "-" + flag.Shorthand 453 if len(flag.Shorthand) > 0 && strings.HasPrefix(flagName, toComplete) { 454 completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage)) 455 } 456 457 return completions 458} 459 460func completeRequireFlags(finalCmd *Command, toComplete string) []string { 461 var completions []string 462 463 doCompleteRequiredFlags := func(flag *pflag.Flag) { 464 if _, present := flag.Annotations[BashCompOneRequiredFlag]; present { 465 if !flag.Changed { 466 // If the flag is not already present, we suggest it as a completion 467 completions = append(completions, getFlagNameCompletions(flag, toComplete)...) 468 } 469 } 470 } 471 472 // We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands 473 // that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and 474 // non-inherited flags. 475 finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { 476 doCompleteRequiredFlags(flag) 477 }) 478 finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { 479 doCompleteRequiredFlags(flag) 480 }) 481 482 return completions 483} 484 485func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) { 486 if finalCmd.DisableFlagParsing { 487 // We only do flag completion if we are allowed to parse flags 488 // This is important for commands which have requested to do their own flag completion. 489 return nil, args, lastArg, nil 490 } 491 492 var flagName string 493 trimmedArgs := args 494 flagWithEqual := false 495 orgLastArg := lastArg 496 497 // When doing completion of a flag name, as soon as an argument starts with 498 // a '-' we know it is a flag. We cannot use isFlagArg() here as that function 499 // requires the flag name to be complete 500 if len(lastArg) > 0 && lastArg[0] == '-' { 501 if index := strings.Index(lastArg, "="); index >= 0 { 502 // Flag with an = 503 if strings.HasPrefix(lastArg[:index], "--") { 504 // Flag has full name 505 flagName = lastArg[2:index] 506 } else { 507 // Flag is shorthand 508 // We have to get the last shorthand flag name 509 // e.g. `-asd` => d to provide the correct completion 510 // https://github.com/spf13/cobra/issues/1257 511 flagName = lastArg[index-1 : index] 512 } 513 lastArg = lastArg[index+1:] 514 flagWithEqual = true 515 } else { 516 // Normal flag completion 517 return nil, args, lastArg, nil 518 } 519 } 520 521 if len(flagName) == 0 { 522 if len(args) > 0 { 523 prevArg := args[len(args)-1] 524 if isFlagArg(prevArg) { 525 // Only consider the case where the flag does not contain an =. 526 // If the flag contains an = it means it has already been fully processed, 527 // so we don't need to deal with it here. 528 if index := strings.Index(prevArg, "="); index < 0 { 529 if strings.HasPrefix(prevArg, "--") { 530 // Flag has full name 531 flagName = prevArg[2:] 532 } else { 533 // Flag is shorthand 534 // We have to get the last shorthand flag name 535 // e.g. `-asd` => d to provide the correct completion 536 // https://github.com/spf13/cobra/issues/1257 537 flagName = prevArg[len(prevArg)-1:] 538 } 539 // Remove the uncompleted flag or else there could be an error created 540 // for an invalid value for that flag 541 trimmedArgs = args[:len(args)-1] 542 } 543 } 544 } 545 } 546 547 if len(flagName) == 0 { 548 // Not doing flag completion 549 return nil, trimmedArgs, lastArg, nil 550 } 551 552 flag := findFlag(finalCmd, flagName) 553 if flag == nil { 554 // Flag not supported by this command, the interspersed option might be set so return the original args 555 return nil, args, orgLastArg, &flagCompError{subCommand: finalCmd.Name(), flagName: flagName} 556 } 557 558 if !flagWithEqual { 559 if len(flag.NoOptDefVal) != 0 { 560 // We had assumed dealing with a two-word flag but the flag is a boolean flag. 561 // In that case, there is no value following it, so we are not really doing flag completion. 562 // Reset everything to do noun completion. 563 trimmedArgs = args 564 flag = nil 565 } 566 } 567 568 return flag, trimmedArgs, lastArg, nil 569} 570 571// initDefaultCompletionCmd adds a default 'completion' command to c. 572// This function will do nothing if any of the following is true: 573// 1- the feature has been explicitly disabled by the program, 574// 2- c has no subcommands (to avoid creating one), 575// 3- c already has a 'completion' command provided by the program. 576func (c *Command) initDefaultCompletionCmd() { 577 if c.CompletionOptions.DisableDefaultCmd || !c.HasSubCommands() { 578 return 579 } 580 581 for _, cmd := range c.commands { 582 if cmd.Name() == compCmdName || cmd.HasAlias(compCmdName) { 583 // A completion command is already available 584 return 585 } 586 } 587 588 haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions 589 590 completionCmd := &Command{ 591 Use: compCmdName, 592 Short: "generate the autocompletion script for the specified shell", 593 Long: fmt.Sprintf(` 594Generate the autocompletion script for %[1]s for the specified shell. 595See each sub-command's help for details on how to use the generated script. 596`, c.Root().Name()), 597 Args: NoArgs, 598 ValidArgsFunction: NoFileCompletions, 599 } 600 c.AddCommand(completionCmd) 601 602 out := c.OutOrStdout() 603 noDesc := c.CompletionOptions.DisableDescriptions 604 shortDesc := "generate the autocompletion script for %s" 605 bash := &Command{ 606 Use: "bash", 607 Short: fmt.Sprintf(shortDesc, "bash"), 608 Long: fmt.Sprintf(` 609Generate the autocompletion script for the bash shell. 610 611This script depends on the 'bash-completion' package. 612If it is not installed already, you can install it via your OS's package manager. 613 614To load completions in your current shell session: 615$ source <(%[1]s completion bash) 616 617To load completions for every new session, execute once: 618Linux: 619 $ %[1]s completion bash > /etc/bash_completion.d/%[1]s 620MacOS: 621 $ %[1]s completion bash > /usr/local/etc/bash_completion.d/%[1]s 622 623You will need to start a new shell for this setup to take effect. 624 `, c.Root().Name()), 625 Args: NoArgs, 626 DisableFlagsInUseLine: true, 627 ValidArgsFunction: NoFileCompletions, 628 RunE: func(cmd *Command, args []string) error { 629 return cmd.Root().GenBashCompletionV2(out, !noDesc) 630 }, 631 } 632 if haveNoDescFlag { 633 bash.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) 634 } 635 636 zsh := &Command{ 637 Use: "zsh", 638 Short: fmt.Sprintf(shortDesc, "zsh"), 639 Long: fmt.Sprintf(` 640Generate the autocompletion script for the zsh shell. 641 642If shell completion is not already enabled in your environment you will need 643to enable it. You can execute the following once: 644 645$ echo "autoload -U compinit; compinit" >> ~/.zshrc 646 647To load completions for every new session, execute once: 648# Linux: 649$ %[1]s completion zsh > "${fpath[1]}/_%[1]s" 650# macOS: 651$ %[1]s completion zsh > /usr/local/share/zsh/site-functions/_%[1]s 652 653You will need to start a new shell for this setup to take effect. 654`, c.Root().Name()), 655 Args: NoArgs, 656 ValidArgsFunction: NoFileCompletions, 657 RunE: func(cmd *Command, args []string) error { 658 if noDesc { 659 return cmd.Root().GenZshCompletionNoDesc(out) 660 } 661 return cmd.Root().GenZshCompletion(out) 662 }, 663 } 664 if haveNoDescFlag { 665 zsh.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) 666 } 667 668 fish := &Command{ 669 Use: "fish", 670 Short: fmt.Sprintf(shortDesc, "fish"), 671 Long: fmt.Sprintf(` 672Generate the autocompletion script for the fish shell. 673 674To load completions in your current shell session: 675$ %[1]s completion fish | source 676 677To load completions for every new session, execute once: 678$ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish 679 680You will need to start a new shell for this setup to take effect. 681`, c.Root().Name()), 682 Args: NoArgs, 683 ValidArgsFunction: NoFileCompletions, 684 RunE: func(cmd *Command, args []string) error { 685 return cmd.Root().GenFishCompletion(out, !noDesc) 686 }, 687 } 688 if haveNoDescFlag { 689 fish.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) 690 } 691 692 powershell := &Command{ 693 Use: "powershell", 694 Short: fmt.Sprintf(shortDesc, "powershell"), 695 Long: fmt.Sprintf(` 696Generate the autocompletion script for powershell. 697 698To load completions in your current shell session: 699PS C:\> %[1]s completion powershell | Out-String | Invoke-Expression 700 701To load completions for every new session, add the output of the above command 702to your powershell profile. 703`, c.Root().Name()), 704 Args: NoArgs, 705 ValidArgsFunction: NoFileCompletions, 706 RunE: func(cmd *Command, args []string) error { 707 if noDesc { 708 return cmd.Root().GenPowerShellCompletion(out) 709 } 710 return cmd.Root().GenPowerShellCompletionWithDesc(out) 711 712 }, 713 } 714 if haveNoDescFlag { 715 powershell.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) 716 } 717 718 completionCmd.AddCommand(bash, zsh, fish, powershell) 719} 720 721func findFlag(cmd *Command, name string) *pflag.Flag { 722 flagSet := cmd.Flags() 723 if len(name) == 1 { 724 // First convert the short flag into a long flag 725 // as the cmd.Flag() search only accepts long flags 726 if short := flagSet.ShorthandLookup(name); short != nil { 727 name = short.Name 728 } else { 729 set := cmd.InheritedFlags() 730 if short = set.ShorthandLookup(name); short != nil { 731 name = short.Name 732 } else { 733 return nil 734 } 735 } 736 } 737 return cmd.Flag(name) 738} 739 740// CompDebug prints the specified string to the same file as where the 741// completion script prints its logs. 742// Note that completion printouts should never be on stdout as they would 743// be wrongly interpreted as actual completion choices by the completion script. 744func CompDebug(msg string, printToStdErr bool) { 745 msg = fmt.Sprintf("[Debug] %s", msg) 746 747 // Such logs are only printed when the user has set the environment 748 // variable BASH_COMP_DEBUG_FILE to the path of some file to be used. 749 if path := os.Getenv("BASH_COMP_DEBUG_FILE"); path != "" { 750 f, err := os.OpenFile(path, 751 os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 752 if err == nil { 753 defer f.Close() 754 WriteStringAndCheck(f, msg) 755 } 756 } 757 758 if printToStdErr { 759 // Must print to stderr for this not to be read by the completion script. 760 fmt.Fprint(os.Stderr, msg) 761 } 762} 763 764// CompDebugln prints the specified string with a newline at the end 765// to the same file as where the completion script prints its logs. 766// Such logs are only printed when the user has set the environment 767// variable BASH_COMP_DEBUG_FILE to the path of some file to be used. 768func CompDebugln(msg string, printToStdErr bool) { 769 CompDebug(fmt.Sprintf("%s\n", msg), printToStdErr) 770} 771 772// CompError prints the specified completion message to stderr. 773func CompError(msg string) { 774 msg = fmt.Sprintf("[Error] %s", msg) 775 CompDebug(msg, true) 776} 777 778// CompErrorln prints the specified completion message to stderr with a newline at the end. 779func CompErrorln(msg string) { 780 CompError(fmt.Sprintf("%s\n", msg)) 781} 782