From 2f3036b58d535702c533d121259a76826fbc3731 Mon Sep 17 00:00:00 2001 From: Naveen Gogineni Date: Sun, 26 Nov 2023 10:07:46 -0500 Subject: [PATCH] Fix:(issue_1814) Fix completions for subcommands --- command.go | 31 ++++++++++++++++++------------- completion_test.go | 27 +++++++++++++++++++++++++++ help.go | 20 ++++++++++++++------ 3 files changed, 59 insertions(+), 19 deletions(-) diff --git a/command.go b/command.go index b847789c89..f56ab607da 100644 --- a/command.go +++ b/command.go @@ -139,6 +139,8 @@ type Command struct { isInError bool // track state of defaults didSetupDefaults bool + // whether in shell completion mode + shellCompletion bool } // FullName returns the full name of the command. @@ -244,7 +246,7 @@ func (cmd *Command) setupDefaults(osArgs []string) { cmd.SuggestCommandFunc = suggestCommand } - if cmd.EnableShellCompletion { + if cmd.EnableShellCompletion || cmd.Root().shellCompletion { completionCommand := buildCompletionCommand() if cmd.ShellCompletionCommandName != "" { @@ -346,16 +348,19 @@ func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) { cmd.parent = v } - // handle the completion flag separately from the flagset since - // completion could be attempted after a flag, but before its value was put - // on the command line. this causes the flagset to interpret the completion - // flag name as the value of the flag before it which is undesirable - // note that we can only do this because the shell autocomplete function - // always appends the completion flag at the end of the command - enableShellCompletion, osArgs := checkShellCompleteFlag(cmd, osArgs) + if cmd.parent == nil { + // handle the completion flag separately from the flagset since + // completion could be attempted after a flag, but before its value was put + // on the command line. this causes the flagset to interpret the completion + // flag name as the value of the flag before it which is undesirable + // note that we can only do this because the shell autocomplete function + // always appends the completion flag at the end of the command + tracef("checking osArgs %v (cmd=%[2]q)", osArgs, cmd.Name) + cmd.shellCompletion, osArgs = checkShellCompleteFlag(cmd, osArgs) - tracef("setting cmd.EnableShellCompletion=%[1]v from checkShellCompleteFlag (cmd=%[2]q)", enableShellCompletion, cmd.Name) - cmd.EnableShellCompletion = enableShellCompletion + tracef("setting cmd.shellCompletion=%[1]v from checkShellCompleteFlag (cmd=%[2]q)", cmd.shellCompletion && cmd.EnableShellCompletion, cmd.Name) + cmd.shellCompletion = cmd.EnableShellCompletion && cmd.shellCompletion + } tracef("using post-checkShellCompleteFlag arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name) @@ -418,7 +423,7 @@ func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) { return nil } - if cmd.After != nil && !cmd.EnableShellCompletion { + if cmd.After != nil && !cmd.Root().shellCompletion { defer func() { if err := cmd.After(ctx, cmd); err != nil { err = cmd.handleExitCoder(ctx, err) @@ -445,7 +450,7 @@ func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) { } } - if cmd.Before != nil && !cmd.EnableShellCompletion { + if cmd.Before != nil && !cmd.Root().shellCompletion { if err := cmd.Before(ctx, cmd); err != nil { deferErr = cmd.handleExitCoder(ctx, err) return deferErr @@ -666,7 +671,7 @@ func (cmd *Command) parseFlags(args Args) (Args, error) { tracef("parsing flags iteratively tail=%[1]q (cmd=%[2]q)", args.Tail(), cmd.Name) - if err := parseIter(cmd.flagSet, cmd, args.Tail(), cmd.Root().EnableShellCompletion); err != nil { + if err := parseIter(cmd.flagSet, cmd, args.Tail(), cmd.Root().shellCompletion); err != nil { return cmd.Args(), err } diff --git a/completion_test.go b/completion_test.go index f21d027e0b..db441e95cf 100644 --- a/completion_test.go +++ b/completion_test.go @@ -61,6 +61,33 @@ func TestCompletionShell(t *testing.T) { } } +func TestCompletionSubcommand(t *testing.T) { + out := &bytes.Buffer{} + + cmd := &Command{ + EnableShellCompletion: true, + Writer: out, + Commands: []*Command{ + { + Name: "bar", + Commands: []*Command{ + { + Name: "xyz", + }, + }, + }, + }, + } + + r := require.New(t) + + r.NoError(cmd.Run(buildTestContext(t), []string{"foo", "bar", "--generate-shell-completion"})) + r.Containsf( + out.String(), "xyz", + "Expected output to contain shell name %[1]q", "xyz", + ) +} + func TestCompletionInvalidShell(t *testing.T) { cmd := &Command{ EnableShellCompletion: true, diff --git a/help.go b/help.go index 2ee9f7658c..a68c92cdb7 100644 --- a/help.go +++ b/help.go @@ -218,8 +218,16 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) { func DefaultCompleteWithFlags(cmd *Command) func(ctx context.Context, cmd *Command) { return func(_ context.Context, cmd *Command) { - if len(os.Args) > 2 { - lastArg := os.Args[len(os.Args)-2] + args := os.Args + if cmd != nil && cmd.flagSet != nil && cmd.parent != nil { + args = cmd.Args().Slice() + tracef("running default complete with flags[%v] on command %[1]q", args, cmd.Name) + } else { + tracef("running default complete with os.Args flags") + } + argsLen := len(args) + if argsLen > 2 { + lastArg := args[argsLen-2] if strings.HasPrefix(lastArg, "-") { if cmd != nil { @@ -235,11 +243,10 @@ func DefaultCompleteWithFlags(cmd *Command) func(ctx context.Context, cmd *Comma } if cmd != nil { + tracef("printing command suggestions on command %[1]q", cmd.Name) printCommandSuggestions(cmd.Commands, cmd.Root().Writer) return } - - printCommandSuggestions(cmd.Commands, cmd.Root().Writer) } } @@ -431,7 +438,7 @@ func checkVersion(cmd *Command) bool { } func checkShellCompleteFlag(c *Command, arguments []string) (bool, []string) { - if !c.EnableShellCompletion { + if (c.parent == nil && !c.EnableShellCompletion) || (c.parent != nil && !c.Root().shellCompletion) { return false, arguments } @@ -448,7 +455,7 @@ func checkShellCompleteFlag(c *Command, arguments []string) (bool, []string) { func checkCompletions(ctx context.Context, cmd *Command) bool { tracef("checking completions on command %[1]q", cmd.Name) - if !cmd.EnableShellCompletion { + if !cmd.Root().shellCompletion { return false } @@ -461,6 +468,7 @@ func checkCompletions(ctx context.Context, cmd *Command) bool { } if cmd.ShellComplete != nil { + tracef("running shell completion func for command %[1]q", cmd.Name) cmd.ShellComplete(ctx, cmd) }