Skip to content

Commit 1f0e15c

Browse files
authored
Merge pull request #1824 from dearchap/issue_1814
Fix:(issue_1814) Fix completions for subcommands
2 parents e8abd76 + 2f3036b commit 1f0e15c

File tree

3 files changed

+59
-19
lines changed

3 files changed

+59
-19
lines changed

command.go

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ type Command struct {
139139
isInError bool
140140
// track state of defaults
141141
didSetupDefaults bool
142+
// whether in shell completion mode
143+
shellCompletion bool
142144
}
143145

144146
// FullName returns the full name of the command.
@@ -244,7 +246,7 @@ func (cmd *Command) setupDefaults(osArgs []string) {
244246
cmd.SuggestCommandFunc = suggestCommand
245247
}
246248

247-
if cmd.EnableShellCompletion {
249+
if cmd.EnableShellCompletion || cmd.Root().shellCompletion {
248250
completionCommand := buildCompletionCommand()
249251

250252
if cmd.ShellCompletionCommandName != "" {
@@ -346,16 +348,19 @@ func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) {
346348
cmd.parent = v
347349
}
348350

349-
// handle the completion flag separately from the flagset since
350-
// completion could be attempted after a flag, but before its value was put
351-
// on the command line. this causes the flagset to interpret the completion
352-
// flag name as the value of the flag before it which is undesirable
353-
// note that we can only do this because the shell autocomplete function
354-
// always appends the completion flag at the end of the command
355-
enableShellCompletion, osArgs := checkShellCompleteFlag(cmd, osArgs)
351+
if cmd.parent == nil {
352+
// handle the completion flag separately from the flagset since
353+
// completion could be attempted after a flag, but before its value was put
354+
// on the command line. this causes the flagset to interpret the completion
355+
// flag name as the value of the flag before it which is undesirable
356+
// note that we can only do this because the shell autocomplete function
357+
// always appends the completion flag at the end of the command
358+
tracef("checking osArgs %v (cmd=%[2]q)", osArgs, cmd.Name)
359+
cmd.shellCompletion, osArgs = checkShellCompleteFlag(cmd, osArgs)
356360

357-
tracef("setting cmd.EnableShellCompletion=%[1]v from checkShellCompleteFlag (cmd=%[2]q)", enableShellCompletion, cmd.Name)
358-
cmd.EnableShellCompletion = enableShellCompletion
361+
tracef("setting cmd.shellCompletion=%[1]v from checkShellCompleteFlag (cmd=%[2]q)", cmd.shellCompletion && cmd.EnableShellCompletion, cmd.Name)
362+
cmd.shellCompletion = cmd.EnableShellCompletion && cmd.shellCompletion
363+
}
359364

360365
tracef("using post-checkShellCompleteFlag arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
361366

@@ -418,7 +423,7 @@ func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) {
418423
return nil
419424
}
420425

421-
if cmd.After != nil && !cmd.EnableShellCompletion {
426+
if cmd.After != nil && !cmd.Root().shellCompletion {
422427
defer func() {
423428
if err := cmd.After(ctx, cmd); err != nil {
424429
err = cmd.handleExitCoder(ctx, err)
@@ -445,7 +450,7 @@ func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) {
445450
}
446451
}
447452

448-
if cmd.Before != nil && !cmd.EnableShellCompletion {
453+
if cmd.Before != nil && !cmd.Root().shellCompletion {
449454
if err := cmd.Before(ctx, cmd); err != nil {
450455
deferErr = cmd.handleExitCoder(ctx, err)
451456
return deferErr
@@ -666,7 +671,7 @@ func (cmd *Command) parseFlags(args Args) (Args, error) {
666671

667672
tracef("parsing flags iteratively tail=%[1]q (cmd=%[2]q)", args.Tail(), cmd.Name)
668673

669-
if err := parseIter(cmd.flagSet, cmd, args.Tail(), cmd.Root().EnableShellCompletion); err != nil {
674+
if err := parseIter(cmd.flagSet, cmd, args.Tail(), cmd.Root().shellCompletion); err != nil {
670675
return cmd.Args(), err
671676
}
672677

completion_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,33 @@ func TestCompletionShell(t *testing.T) {
6161
}
6262
}
6363

64+
func TestCompletionSubcommand(t *testing.T) {
65+
out := &bytes.Buffer{}
66+
67+
cmd := &Command{
68+
EnableShellCompletion: true,
69+
Writer: out,
70+
Commands: []*Command{
71+
{
72+
Name: "bar",
73+
Commands: []*Command{
74+
{
75+
Name: "xyz",
76+
},
77+
},
78+
},
79+
},
80+
}
81+
82+
r := require.New(t)
83+
84+
r.NoError(cmd.Run(buildTestContext(t), []string{"foo", "bar", "--generate-shell-completion"}))
85+
r.Containsf(
86+
out.String(), "xyz",
87+
"Expected output to contain shell name %[1]q", "xyz",
88+
)
89+
}
90+
6491
func TestCompletionInvalidShell(t *testing.T) {
6592
cmd := &Command{
6693
EnableShellCompletion: true,

help.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,16 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
218218

219219
func DefaultCompleteWithFlags(cmd *Command) func(ctx context.Context, cmd *Command) {
220220
return func(_ context.Context, cmd *Command) {
221-
if len(os.Args) > 2 {
222-
lastArg := os.Args[len(os.Args)-2]
221+
args := os.Args
222+
if cmd != nil && cmd.flagSet != nil && cmd.parent != nil {
223+
args = cmd.Args().Slice()
224+
tracef("running default complete with flags[%v] on command %[1]q", args, cmd.Name)
225+
} else {
226+
tracef("running default complete with os.Args flags")
227+
}
228+
argsLen := len(args)
229+
if argsLen > 2 {
230+
lastArg := args[argsLen-2]
223231

224232
if strings.HasPrefix(lastArg, "-") {
225233
if cmd != nil {
@@ -235,11 +243,10 @@ func DefaultCompleteWithFlags(cmd *Command) func(ctx context.Context, cmd *Comma
235243
}
236244

237245
if cmd != nil {
246+
tracef("printing command suggestions on command %[1]q", cmd.Name)
238247
printCommandSuggestions(cmd.Commands, cmd.Root().Writer)
239248
return
240249
}
241-
242-
printCommandSuggestions(cmd.Commands, cmd.Root().Writer)
243250
}
244251
}
245252

@@ -431,7 +438,7 @@ func checkVersion(cmd *Command) bool {
431438
}
432439

433440
func checkShellCompleteFlag(c *Command, arguments []string) (bool, []string) {
434-
if !c.EnableShellCompletion {
441+
if (c.parent == nil && !c.EnableShellCompletion) || (c.parent != nil && !c.Root().shellCompletion) {
435442
return false, arguments
436443
}
437444

@@ -448,7 +455,7 @@ func checkShellCompleteFlag(c *Command, arguments []string) (bool, []string) {
448455
func checkCompletions(ctx context.Context, cmd *Command) bool {
449456
tracef("checking completions on command %[1]q", cmd.Name)
450457

451-
if !cmd.EnableShellCompletion {
458+
if !cmd.Root().shellCompletion {
452459
return false
453460
}
454461

@@ -461,6 +468,7 @@ func checkCompletions(ctx context.Context, cmd *Command) bool {
461468
}
462469

463470
if cmd.ShellComplete != nil {
471+
tracef("running shell completion func for command %[1]q", cmd.Name)
464472
cmd.ShellComplete(ctx, cmd)
465473
}
466474

0 commit comments

Comments
 (0)