fix: complete persistent flags in shell completion - #2437
Conversation
Juneezee
left a comment
There was a problem hiding this comment.
Thanks for working on this! The direction is right, but this change has a few issues:
Lines 275 to 277 in 109de8b
1. Duplicate suggestions. A flag shared between a parent and the subcommand is now suggested twice:
verbose := &cli.BoolFlag{Name: "verbose"}
cmd := &cli.Command{
Name: "app",
EnableShellCompletion: true,
Flags: []cli.Flag{verbose},
Commands: []*cli.Command{{Name: "add", Flags: []cli.Flag{verbose}}},
}$ app add --ver --generate-shell-completion
--verbose
--verbose
2. Custom flags are dropped. cmd.VisibleFlags() only keeps flags that implement VisibleFlag, so a custom flag that only implements Flag is no longer suggested (it was before):
Lines 218 to 226 in d1d8108
printFlagSuggestions already filters hidden flags, so cmd.allFlags() is enough here:
Lines 215 to 221 in d1d8108
3. SkipFlagParsing. Parent flags are now suggested on a subcommand with SkipFlagParsing: true, e.g. with a root flag --rootf, app sub --r suggests --rootf. But flags are not parsed there, so app sub --rootf does not set --rootf, and it ends up in cmd.Args() instead:
Lines 162 to 167 in d1d8108
4. Order. Please list the command's own flags before the parent flags, to match the help output (OPTIONS, then GLOBAL OPTIONS):
Lines 84 to 88 in d1d8108
Something like this fixes 1-4, using the existing hasFlag helper:
flags := cmd.allFlags()
if !cmd.SkipFlagParsing {
for _, fl := range cmd.VisiblePersistentFlags() {
if !hasFlag(flags, fl) {
flags = append(flags, fl)
}
}
}
printFlagSuggestions(lastArg, flags, cmd.Root().Writer)5. Tests. Adding Local: true to the existing fixtures keeps their old expected output, which hides the behavior change:
Lines 1439 to 1444 in 109de8b
Please revert these and update the expected output instead. Test cases for 1-4 would be great too.
Minor: could the new test go into TestDefaultCompleteWithFlags instead of a new file?
109de8b to
5068c5c
Compare
- Add a TestDefaultCompleteWithFlags case for flags in MutuallyExclusiveFlags, which are now suggested via allFlags(). - Use an explicit expected string per scope in TestPartialFlagCompletionAfterPositionalArgument instead of building it from the command path. Assisted-by: claude:claude-opus-5-5 Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
Juneezee
left a comment
There was a problem hiding this comment.
Thanks for the update! All the review comments are addressed. I pushed a small commit that adds a test for MutuallyExclusiveFlags suggestions and simplifies the expected output in TestPartialFlagCompletionAfterPositionalArgument. LGTM.
Summary
Fixes #2173.
DefaultCompleteWithFlagsnow suggests visible persistent flags from ancestor commands after a subcommand, while preserving command-local flags first. It avoids duplicate suggestions for shared flags, retains custom flags that implementFlagwithoutVisibleFlag, and omits inherited flags whenSkipFlagParsingis enabled.Regression cases cover persistent flags after positional arguments, local-before-parent ordering, duplicate flags, custom flags, and skipped parsing.
Validation
makepassed, including generation,go vet, race-enabled test suites (99.4% coverage for the main package), binary-size checks, and READMEgfmrunexamples.git diff --checkpassed.AI assistance disclosure
This pull request was developed with assistance from OpenAI Codex.
Release Notes