91 // for shells that support completion descriptions |
91 // for shells that support completion descriptions |
92 DisableNoDescFlag bool |
92 DisableNoDescFlag bool |
93 // DisableDescriptions turns off all completion descriptions for shells |
93 // DisableDescriptions turns off all completion descriptions for shells |
94 // that support them |
94 // that support them |
95 DisableDescriptions bool |
95 DisableDescriptions bool |
|
96 // HiddenDefaultCmd makes the default 'completion' command hidden |
|
97 HiddenDefaultCmd bool |
96 } |
98 } |
97 |
99 |
98 // NoFileCompletions can be used to disable file completion for commands that should |
100 // NoFileCompletions can be used to disable file completion for commands that should |
99 // not trigger file completions. |
101 // not trigger file completions. |
100 func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { |
102 func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { |
101 return nil, ShellCompDirectiveNoFileComp |
103 return nil, ShellCompDirectiveNoFileComp |
|
104 } |
|
105 |
|
106 // FixedCompletions can be used to create a completion function which always |
|
107 // returns the same results. |
|
108 func FixedCompletions(choices []string, directive ShellCompDirective) func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { |
|
109 return func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { |
|
110 return choices, directive |
|
111 } |
102 } |
112 } |
103 |
113 |
104 // RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag. |
114 // RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag. |
105 func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)) error { |
115 func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)) error { |
106 flag := c.Flag(flagName) |
116 flag := c.Flag(flagName) |
224 // Find the real command for which completion must be performed |
240 // 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 |
241 // check if we need to traverse here to parse local flags on parent commands |
226 if c.Root().TraverseChildren { |
242 if c.Root().TraverseChildren { |
227 finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs) |
243 finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs) |
228 } else { |
244 } else { |
229 finalCmd, finalArgs, err = c.Root().Find(trimmedArgs) |
245 // For Root commands that don't specify any value for their Args fields, when we call |
|
246 // Find(), if those Root commands don't have any sub-commands, they will accept arguments. |
|
247 // However, because we have added the __complete sub-command in the current code path, the |
|
248 // call to Find() -> legacyArgs() will return an error if there are any arguments. |
|
249 // To avoid this, we first remove the __complete command to get back to having no sub-commands. |
|
250 rootCmd := c.Root() |
|
251 if len(rootCmd.Commands()) == 1 { |
|
252 rootCmd.RemoveCommand(c) |
|
253 } |
|
254 |
|
255 finalCmd, finalArgs, err = rootCmd.Find(trimmedArgs) |
230 } |
256 } |
231 if err != nil { |
257 if err != nil { |
232 // Unable to find the real command. E.g., <program> someInvalidCmd <TAB> |
258 // 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) |
259 return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs) |
234 } |
260 } |
288 // Directory completion |
320 // Directory completion |
289 return finalCmd, []string{}, ShellCompDirectiveFilterDirs, nil |
321 return finalCmd, []string{}, ShellCompDirectiveFilterDirs, nil |
290 } |
322 } |
291 } |
323 } |
292 |
324 |
|
325 var completions []string |
|
326 var directive ShellCompDirective |
|
327 |
|
328 // Enforce flag groups before doing flag completions |
|
329 finalCmd.enforceFlagGroupsForCompletion() |
|
330 |
|
331 // Note that we want to perform flagname completion even if finalCmd.DisableFlagParsing==true; |
|
332 // doing this allows for completion of persistent flag names even for commands that disable flag parsing. |
|
333 // |
293 // When doing completion of a flag name, as soon as an argument starts with |
334 // 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 |
335 // a '-' we know it is a flag. We cannot use isFlagArg() here as it requires |
295 // the flag name to be complete |
336 // the flag name to be complete |
296 if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") && flagCompletion { |
337 if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") && flagCompletion { |
297 var completions []string |
|
298 |
|
299 // First check for required flags |
338 // First check for required flags |
300 completions = completeRequireFlags(finalCmd, toComplete) |
339 completions = completeRequireFlags(finalCmd, toComplete) |
301 |
340 |
302 // If we have not found any required flags, only then can we show regular flags |
341 // If we have not found any required flags, only then can we show regular flags |
303 if len(completions) == 0 { |
342 if len(completions) == 0 { |
320 finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { |
359 finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { |
321 doCompleteFlags(flag) |
360 doCompleteFlags(flag) |
322 }) |
361 }) |
323 } |
362 } |
324 |
363 |
325 directive := ShellCompDirectiveNoFileComp |
364 directive = ShellCompDirectiveNoFileComp |
326 if len(completions) == 1 && strings.HasSuffix(completions[0], "=") { |
365 if len(completions) == 1 && strings.HasSuffix(completions[0], "=") { |
327 // If there is a single completion, the shell usually adds a space |
366 // 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 = |
367 // after the completion. We don't want that if the flag ends with an = |
329 directive = ShellCompDirectiveNoSpace |
368 directive = ShellCompDirectiveNoSpace |
330 } |
369 } |
331 return finalCmd, completions, directive, nil |
370 |
332 } |
371 if !finalCmd.DisableFlagParsing { |
333 |
372 // If DisableFlagParsing==false, we have completed the flags as known by Cobra; |
334 // We only remove the flags from the arguments if DisableFlagParsing is not set. |
373 // we can return what we found. |
335 // This is important for commands which have requested to do their own flag completion. |
374 // If DisableFlagParsing==true, Cobra may not be aware of all flags, so we |
336 if !finalCmd.DisableFlagParsing { |
375 // let the logic continue to see if ValidArgsFunction needs to be called. |
337 finalArgs = finalCmd.Flags().Args() |
376 return finalCmd, completions, directive, nil |
338 } |
377 } |
339 |
378 } else { |
340 var completions []string |
379 directive = ShellCompDirectiveDefault |
341 directive := ShellCompDirectiveDefault |
380 if flag == nil { |
342 if flag == nil { |
381 foundLocalNonPersistentFlag := false |
343 foundLocalNonPersistentFlag := false |
382 // If TraverseChildren is true on the root command we don't check for |
344 // If TraverseChildren is true on the root command we don't check for |
383 // local flags because we can use a local flag on a parent command |
345 // local flags because we can use a local flag on a parent command |
384 if !finalCmd.Root().TraverseChildren { |
346 if !finalCmd.Root().TraverseChildren { |
385 // Check if there are any local, non-persistent flags on the command-line |
347 // Check if there are any local, non-persistent flags on the command-line |
386 localNonPersistentFlags := finalCmd.LocalNonPersistentFlags() |
348 localNonPersistentFlags := finalCmd.LocalNonPersistentFlags() |
387 finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { |
349 finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { |
388 if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed { |
350 if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed { |
389 foundLocalNonPersistentFlag = true |
351 foundLocalNonPersistentFlag = true |
390 } |
|
391 }) |
|
392 } |
|
393 |
|
394 // Complete subcommand names, including the help command |
|
395 if len(finalArgs) == 0 && !foundLocalNonPersistentFlag { |
|
396 // We only complete sub-commands if: |
|
397 // - there are no arguments on the command-line and |
|
398 // - there are no local, non-persistent flags on the command-line or TraverseChildren is true |
|
399 for _, subCmd := range finalCmd.Commands() { |
|
400 if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand { |
|
401 if strings.HasPrefix(subCmd.Name(), toComplete) { |
|
402 completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short)) |
|
403 } |
|
404 directive = ShellCompDirectiveNoFileComp |
|
405 } |
352 } |
406 } |
353 }) |
407 } |
354 } |
408 |
355 |
409 // Complete required flags even without the '-' prefix |
356 // Complete subcommand names, including the help command |
410 completions = append(completions, completeRequireFlags(finalCmd, toComplete)...) |
357 if len(finalArgs) == 0 && !foundLocalNonPersistentFlag { |
411 |
358 // We only complete sub-commands if: |
412 // Always complete ValidArgs, even if we are completing a subcommand name. |
359 // - there are no arguments on the command-line and |
413 // This is for commands that have both subcommands and ValidArgs. |
360 // - there are no local, non-persistent flags on the command-line or TraverseChildren is true |
414 if len(finalCmd.ValidArgs) > 0 { |
361 for _, subCmd := range finalCmd.Commands() { |
415 if len(finalArgs) == 0 { |
362 if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand { |
416 // ValidArgs are only for the first argument |
363 if strings.HasPrefix(subCmd.Name(), toComplete) { |
417 for _, validArg := range finalCmd.ValidArgs { |
364 completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short)) |
418 if strings.HasPrefix(validArg, toComplete) { |
|
419 completions = append(completions, validArg) |
|
420 } |
365 } |
421 } |
366 directive = ShellCompDirectiveNoFileComp |
422 directive = ShellCompDirectiveNoFileComp |
367 } |
423 |
368 } |
424 // If no completions were found within commands or ValidArgs, |
369 } |
425 // see if there are any ArgAliases that should be completed. |
370 |
426 if len(completions) == 0 { |
371 // Complete required flags even without the '-' prefix |
427 for _, argAlias := range finalCmd.ArgAliases { |
372 completions = append(completions, completeRequireFlags(finalCmd, toComplete)...) |
428 if strings.HasPrefix(argAlias, toComplete) { |
373 |
429 completions = append(completions, argAlias) |
374 // Always complete ValidArgs, even if we are completing a subcommand name. |
430 } |
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 } |
431 } |
393 } |
432 } |
394 } |
433 } |
395 } |
434 |
396 |
435 // If there are ValidArgs specified (even if they don't match), we stop completion. |
397 // If there are ValidArgs specified (even if they don't match), we stop completion. |
436 // Only one of ValidArgs or ValidArgsFunction can be used for a single command. |
398 // Only one of ValidArgs or ValidArgsFunction can be used for a single command. |
437 return finalCmd, completions, directive, nil |
399 return finalCmd, completions, directive, nil |
438 } |
400 } |
439 |
401 |
440 // Let the logic continue so as to add any ValidArgsFunction completions, |
402 // Let the logic continue so as to add any ValidArgsFunction completions, |
441 // even if we already found sub-commands. |
403 // even if we already found sub-commands. |
442 // This is for commands that have subcommands but also specify a ValidArgsFunction. |
404 // This is for commands that have subcommands but also specify a ValidArgsFunction. |
443 } |
405 } |
444 } |
406 |
445 |
407 // Find the completion function for the flag or command |
446 // Find the completion function for the flag or command |
408 var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) |
447 var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) |
409 if flag != nil && flagCompletion { |
448 if flag != nil && flagCompletion { |
587 |
626 |
588 haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions |
627 haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions |
589 |
628 |
590 completionCmd := &Command{ |
629 completionCmd := &Command{ |
591 Use: compCmdName, |
630 Use: compCmdName, |
592 Short: "generate the autocompletion script for the specified shell", |
631 Short: "Generate the autocompletion script for the specified shell", |
593 Long: fmt.Sprintf(` |
632 Long: fmt.Sprintf(`Generate the autocompletion script for %[1]s for the specified shell. |
594 Generate the autocompletion script for %[1]s for the specified shell. |
|
595 See each sub-command's help for details on how to use the generated script. |
633 See each sub-command's help for details on how to use the generated script. |
596 `, c.Root().Name()), |
634 `, c.Root().Name()), |
597 Args: NoArgs, |
635 Args: NoArgs, |
598 ValidArgsFunction: NoFileCompletions, |
636 ValidArgsFunction: NoFileCompletions, |
|
637 Hidden: c.CompletionOptions.HiddenDefaultCmd, |
599 } |
638 } |
600 c.AddCommand(completionCmd) |
639 c.AddCommand(completionCmd) |
601 |
640 |
602 out := c.OutOrStdout() |
641 out := c.OutOrStdout() |
603 noDesc := c.CompletionOptions.DisableDescriptions |
642 noDesc := c.CompletionOptions.DisableDescriptions |
604 shortDesc := "generate the autocompletion script for %s" |
643 shortDesc := "Generate the autocompletion script for %s" |
605 bash := &Command{ |
644 bash := &Command{ |
606 Use: "bash", |
645 Use: "bash", |
607 Short: fmt.Sprintf(shortDesc, "bash"), |
646 Short: fmt.Sprintf(shortDesc, "bash"), |
608 Long: fmt.Sprintf(` |
647 Long: fmt.Sprintf(`Generate the autocompletion script for the bash shell. |
609 Generate the autocompletion script for the bash shell. |
|
610 |
648 |
611 This script depends on the 'bash-completion' package. |
649 This script depends on the 'bash-completion' package. |
612 If it is not installed already, you can install it via your OS's package manager. |
650 If it is not installed already, you can install it via your OS's package manager. |
613 |
651 |
614 To load completions in your current shell session: |
652 To load completions in your current shell session: |
615 $ source <(%[1]s completion bash) |
653 |
|
654 source <(%[1]s completion bash) |
616 |
655 |
617 To load completions for every new session, execute once: |
656 To load completions for every new session, execute once: |
618 Linux: |
657 |
619 $ %[1]s completion bash > /etc/bash_completion.d/%[1]s |
658 #### Linux: |
620 MacOS: |
659 |
621 $ %[1]s completion bash > /usr/local/etc/bash_completion.d/%[1]s |
660 %[1]s completion bash > /etc/bash_completion.d/%[1]s |
|
661 |
|
662 #### macOS: |
|
663 |
|
664 %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s |
622 |
665 |
623 You will need to start a new shell for this setup to take effect. |
666 You will need to start a new shell for this setup to take effect. |
624 `, c.Root().Name()), |
667 `, c.Root().Name()), |
625 Args: NoArgs, |
668 Args: NoArgs, |
626 DisableFlagsInUseLine: true, |
669 DisableFlagsInUseLine: true, |
627 ValidArgsFunction: NoFileCompletions, |
670 ValidArgsFunction: NoFileCompletions, |
628 RunE: func(cmd *Command, args []string) error { |
671 RunE: func(cmd *Command, args []string) error { |
629 return cmd.Root().GenBashCompletionV2(out, !noDesc) |
672 return cmd.Root().GenBashCompletionV2(out, !noDesc) |