Skip to content

Commit

Permalink
The current custom completion logic does not complete
Browse files Browse the repository at this point in the history
subcommands when a local flag is set. This is good unless
TraverseChildren is set to true where local flags
can be set on parent commands.

This commit allows subcommands to be completed only
if TraverseChildren is true.

Closes #1170

Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
  • Loading branch information
Luap99 committed Jul 18, 2020
1 parent 19e41cf commit 1d97c85
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 9 deletions.
23 changes: 14 additions & 9 deletions custom_completions.go
Expand Up @@ -176,7 +176,8 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
trimmedArgs := args[:len(args)-1]

// Find the real command for which completion must be performed
finalCmd, finalArgs, err := c.Root().Find(trimmedArgs)
// use Traverse here to parse local flags on parent commands
finalCmd, finalArgs, err := c.Root().Traverse(trimmedArgs)
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)
Expand Down Expand Up @@ -271,20 +272,24 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
var completions []string
directive := ShellCompDirectiveDefault
if flag == nil {
// Check if there are any local, non-persistent flags on the command-line
foundLocalNonPersistentFlag := false
localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()
finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {
foundLocalNonPersistentFlag = true
}
})
// If TraverseChildren is true we dont check for local flags
// because we can use a local flag on a parent command
if !finalCmd.TraverseChildren {
// Check if there are any local, non-persistent flags on the command-line
localNonPersistentFlags := finalCmd.LocalNonPersistentFlags()
finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed {
foundLocalNonPersistentFlag = true
}
})
}

// Complete subcommand names, including the help command
if len(finalArgs) == 0 && !foundLocalNonPersistentFlag {
// We only complete sub-commands if:
// - there are no arguments on the command-line and
// - there are no local, non-peristent flag on the command-line
// - there are no local, non-peristent flag on the command-line or TraverseChildren is true
for _, subCmd := range finalCmd.Commands() {
if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
if strings.HasPrefix(subCmd.Name(), toComplete) {
Expand Down
45 changes: 45 additions & 0 deletions custom_completions_test.go
Expand Up @@ -139,6 +139,8 @@ func TestNoCmdNameCompletionInGo(t *testing.T) {
Use: "root",
Run: emptyRun,
}
rootCmd.Flags().String("localroot", "", "local root flag")

childCmd1 := &Command{
Use: "childCmd1",
Short: "First command",
Expand Down Expand Up @@ -187,6 +189,49 @@ func TestNoCmdNameCompletionInGo(t *testing.T) {
t.Errorf("expected: %q, got: %q", expected, output)
}

// Test that sub-command names are completed if a local non-persistent flag is present and TraverChildren is set to true
// set TraverChildren to true on the root cmd
rootCmd.TraverseChildren = true

output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset TraverseChildren for next command
rootCmd.TraverseChildren = false

expected = strings.Join([]string{
"childCmd1",
"help",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")

if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}

// Test that sub-command names are completed if a local non-persistent flag is present
// and TraverChildren is set to true on a child command
childCmd1.TraverseChildren = true

output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "--nonPersistent", "value", "")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Reset TraverseChildren for next command
childCmd1.TraverseChildren = false
// Reset the flag for the next command
nonPersistentFlag.Changed = false

expected = strings.Join([]string{
"childCmd2",
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")

if output != expected {
t.Errorf("expected: %q, got: %q", expected, output)
}

// Test that sub-command names are not completed if a local non-persistent short flag is present
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "-n", "value", "")
if err != nil {
Expand Down

0 comments on commit 1d97c85

Please sign in to comment.