complete aliases for subcommands

When completing a subcommand, also take its aliases into consideration
instead of only its name.

fixes #1852
This commit is contained in:
Ingo Bürk 2022-11-15 16:21:11 +01:00
parent ad6db7f8f6
commit ce30e98be2
2 changed files with 74 additions and 1 deletions

View file

@ -424,10 +424,20 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi
// - there are no local, non-persistent flags on the command-line or TraverseChildren is true // - there are no local, non-persistent flags on the command-line or TraverseChildren is true
for _, subCmd := range finalCmd.Commands() { for _, subCmd := range finalCmd.Commands() {
if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand { if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand {
directive = ShellCompDirectiveNoFileComp
// Only ever complete the name OR one of the aliases, no need to offer multiple matching ones
// for the same command.
if strings.HasPrefix(subCmd.Name(), toComplete) { if strings.HasPrefix(subCmd.Name(), toComplete) {
completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short)) completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short))
} else {
for _, alias := range subCmd.Aliases {
if strings.HasPrefix(alias, toComplete) {
completions = append(completions, fmt.Sprintf("%s\t%s", alias, subCmd.Short))
break
}
}
} }
directive = ShellCompDirectiveNoFileComp
} }
} }
} }

View file

@ -2176,6 +2176,69 @@ func TestValidArgsNotValidArgsFunc(t *testing.T) {
} }
} }
func TestCommandAliasesCompletionInGo(t *testing.T) {
rootCmd := &Command{
Use: "root",
Run: emptyRun,
}
subCmd := &Command{
Use: "sandstone",
Aliases: []string{"slate", "pumice", "pegmatite"},
Run: emptyRun,
}
rootCmd.AddCommand(subCmd)
testcases := []struct {
desc string
toComplete string
expectedCompletion string
}{
{
desc: "command name",
toComplete: "sand",
expectedCompletion: "sandstone",
},
{
desc: "command name if an alias also matches",
toComplete: "s",
expectedCompletion: "sandstone",
},
{
desc: "alias if command name does not match",
toComplete: "sla",
expectedCompletion: "slate",
},
{
desc: "only one alias if multiple match",
toComplete: "p",
expectedCompletion: "pumice",
},
}
for _, tc := range testcases {
t.Run(tc.desc, func(t *testing.T) {
args := append([]string{ShellCompRequestCmd}, tc.toComplete)
output, err := executeCommand(rootCmd, args...)
expectedCompletion := strings.Join([]string{
tc.expectedCompletion,
":4",
"Completion ended with directive: ShellCompDirectiveNoFileComp",
"",
}, "\n")
switch {
case err == nil && output != expectedCompletion:
t.Errorf("expected: %q, got: %q", expectedCompletion, output)
case err != nil:
t.Errorf("Unexpected error %q", err)
}
})
}
}
func TestArgAliasesCompletionInGo(t *testing.T) { func TestArgAliasesCompletionInGo(t *testing.T) {
rootCmd := &Command{ rootCmd := &Command{
Use: "root", Use: "root",