From ce30e98be22bd54f259e680dface9fa511ddc5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingo=20B=C3=BCrk?= Date: Tue, 15 Nov 2022 16:21:11 +0100 Subject: [PATCH] complete aliases for subcommands When completing a subcommand, also take its aliases into consideration instead of only its name. fixes #1852 --- completions.go | 12 ++++++++- completions_test.go | 63 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/completions.go b/completions.go index e8a0206d..4e2d77b5 100644 --- a/completions.go +++ b/completions.go @@ -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 for _, subCmd := range finalCmd.Commands() { 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) { 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 } } } diff --git a/completions_test.go b/completions_test.go index abac12e4..af54fac3 100644 --- a/completions_test.go +++ b/completions_test.go @@ -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) { rootCmd := &Command{ Use: "root",