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 <marc.khouzam@montreal.ca>
This commit is contained in:
parent
19c9c74384
commit
9054739e08
@ -228,7 +228,17 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
|
|||||||
if c.Root().TraverseChildren {
|
if c.Root().TraverseChildren {
|
||||||
finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs)
|
finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs)
|
||||||
} else {
|
} else {
|
||||||
finalCmd, finalArgs, err = c.Root().Find(trimmedArgs)
|
// For Root commands that don't specify any value for their Args fields, when we call
|
||||||
|
// Find(), if those Root commands don't have any sub-commands, they will accept arguments.
|
||||||
|
// However, because we have added the __complete sub-command in the current code path, the
|
||||||
|
// call to Find() -> legacyArgs() will return an error if there are any arguments.
|
||||||
|
// To avoid this, we first remove the __complete command to get back to having no sub-commands.
|
||||||
|
rootCmd := c.Root()
|
||||||
|
if len(rootCmd.Commands()) == 1 {
|
||||||
|
rootCmd.RemoveCommand(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
finalCmd, finalArgs, err = rootCmd.Find(trimmedArgs)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
|
// Unable to find the real command. E.g., <program> someInvalidCmd <TAB>
|
||||||
|
@ -2619,3 +2619,48 @@ func TestCompleteWithDisableFlagParsing(t *testing.T) {
|
|||||||
t.Errorf("expected: %q, got: %q", expected, output)
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompleteWithRootAndLegacyArgs(t *testing.T) {
|
||||||
|
// Test a lonely root command which uses legacyArgs(). In such a case, the root
|
||||||
|
// command should accept any number of arguments and completion should behave accordingly.
|
||||||
|
rootCmd := &Command{
|
||||||
|
Use: "root",
|
||||||
|
Args: nil, // Args must be nil to trigger the legacyArgs() function
|
||||||
|
Run: emptyRun,
|
||||||
|
ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
|
||||||
|
return []string{"arg1", "arg2"}, ShellCompDirectiveNoFileComp
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the first arg is completed
|
||||||
|
output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := strings.Join([]string{
|
||||||
|
"arg1",
|
||||||
|
"arg2",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the completion of arguments continues
|
||||||
|
output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "arg1", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = strings.Join([]string{
|
||||||
|
"arg1",
|
||||||
|
"arg2",
|
||||||
|
":4",
|
||||||
|
"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
|
||||||
|
|
||||||
|
if output != expected {
|
||||||
|
t.Errorf("expected: %q, got: %q", expected, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user