Skip to content

Commit 9054739

Browse files
authored
Remove __complete cmd for program without subcmds (#1563)
Fixes #1562 Programs that don't have sub-commands can accept any number of args. However, when doing shell completion for such programs, within the __complete code this very __complete command makes it that the program suddenly has a sub-command, and the call to Find() -> legacyArgs() will then return an error if there are more than one argument on the command-line being completed. To avoid this, we first remove the __complete command in such a case so as to get back to having no sub-commands. Signed-off-by: Marc Khouzam <[email protected]>
1 parent 19c9c74 commit 9054739

File tree

2 files changed

+56
-1
lines changed

2 files changed

+56
-1
lines changed

completions.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,17 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
228228
if c.Root().TraverseChildren {
229229
finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs)
230230
} else {
231-
finalCmd, finalArgs, err = c.Root().Find(trimmedArgs)
231+
// For Root commands that don't specify any value for their Args fields, when we call
232+
// Find(), if those Root commands don't have any sub-commands, they will accept arguments.
233+
// However, because we have added the __complete sub-command in the current code path, the
234+
// call to Find() -> legacyArgs() will return an error if there are any arguments.
235+
// To avoid this, we first remove the __complete command to get back to having no sub-commands.
236+
rootCmd := c.Root()
237+
if len(rootCmd.Commands()) == 1 {
238+
rootCmd.RemoveCommand(c)
239+
}
240+
241+
finalCmd, finalArgs, err = rootCmd.Find(trimmedArgs)
232242
}
233243
if err != nil {
234244
// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>

completions_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2619,3 +2619,48 @@ func TestCompleteWithDisableFlagParsing(t *testing.T) {
26192619
t.Errorf("expected: %q, got: %q", expected, output)
26202620
}
26212621
}
2622+
2623+
func TestCompleteWithRootAndLegacyArgs(t *testing.T) {
2624+
// Test a lonely root command which uses legacyArgs(). In such a case, the root
2625+
// command should accept any number of arguments and completion should behave accordingly.
2626+
rootCmd := &Command{
2627+
Use: "root",
2628+
Args: nil, // Args must be nil to trigger the legacyArgs() function
2629+
Run: emptyRun,
2630+
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
2631+
return []string{"arg1", "arg2"}, ShellCompDirectiveNoFileComp
2632+
},
2633+
}
2634+
2635+
// Make sure the first arg is completed
2636+
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
2637+
if err != nil {
2638+
t.Errorf("Unexpected error: %v", err)
2639+
}
2640+
2641+
expected := strings.Join([]string{
2642+
"arg1",
2643+
"arg2",
2644+
":4",
2645+
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2646+
2647+
if output != expected {
2648+
t.Errorf("expected: %q, got: %q", expected, output)
2649+
}
2650+
2651+
// Make sure the completion of arguments continues
2652+
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "arg1", "")
2653+
if err != nil {
2654+
t.Errorf("Unexpected error: %v", err)
2655+
}
2656+
2657+
expected = strings.Join([]string{
2658+
"arg1",
2659+
"arg2",
2660+
":4",
2661+
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2662+
2663+
if output != expected {
2664+
t.Errorf("expected: %q, got: %q", expected, output)
2665+
}
2666+
}

0 commit comments

Comments
 (0)