Skip to content

Commit 58ec406

Browse files
authored
Merge pull request #2437 from sb123sb123/fix/2173-zsh-persistent-flag-completion
fix: complete persistent flags in shell completion
2 parents d1d8108 + 1978ede commit 58ec406

3 files changed

Lines changed: 101 additions & 14 deletions

File tree

‎completion_partial_regression_test.go‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ func TestPartialFlagCompletionAfterPositionalArgument(t *testing.T) {
3636
for _, scope := range []struct {
3737
name string
3838
path []string
39+
want string
3940
}{
40-
{"app", nil},
41-
{"sub", []string{"sub"}},
42-
{"nested", []string{"sub", "nested"}},
41+
{"app", nil, "--param-app-one\n--param-app-two\n"},
42+
{"sub", []string{"sub"}, "--param-sub-one\n--param-sub-two\n--param-app-one\n--param-app-two\n"},
43+
{"nested", []string{"sub", "nested"}, "--param-nested-one\n--param-nested-two\n--param-app-one\n--param-app-two\n--param-sub-one\n--param-sub-two\n"},
4344
} {
4445
for _, positional := range [][]string{nil, {"value"}, {"first", "second"}} {
4546
t.Run(scope.name+"/"+strings.Join(positional, "_"), func(t *testing.T) {
@@ -53,9 +54,8 @@ func TestPartialFlagCompletionAfterPositionalArgument(t *testing.T) {
5354
if err := cmd.Run(context.Background(), args); err != nil {
5455
t.Fatal(err)
5556
}
56-
want := "--param-" + scope.name + "-one\n--param-" + scope.name + "-two\n"
57-
if got := output.String(); got != want {
58-
t.Errorf("completion for %q = %q, want %q", args, got, want)
57+
if got := output.String(); got != scope.want {
58+
t.Errorf("completion for %q = %q, want %q", args, got, scope.want)
5959
}
6060
if ran {
6161
t.Error("completion executed the command action")

‎help.go‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,15 @@ func DefaultCompleteWithFlags(ctx context.Context, cmd *Command) {
272272

273273
if strings.HasPrefix(lastArg, "-") {
274274
tracef("printing flag suggestion for flag[%v] on command %[1]q", lastArg, cmd.Name)
275-
printFlagSuggestions(lastArg, cmd.Flags, cmd.Root().Writer)
275+
flags := cmd.allFlags()
276+
if !cmd.SkipFlagParsing {
277+
for _, fl := range cmd.VisiblePersistentFlags() {
278+
if !hasFlag(flags, fl) {
279+
flags = append(flags, fl)
280+
}
281+
}
282+
}
283+
printFlagSuggestions(lastArg, flags, cmd.Root().Writer)
276284
return
277285
}
278286

‎help_test.go‎

Lines changed: 86 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,18 @@ func TestHideHelp_SubcommandCannotOptBackIn(t *testing.T) {
14111411
r.ErrorContains(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "--help"}), providedButNotDefinedErrMsg)
14121412
}
14131413

1414+
type completionOnlyFlag struct {
1415+
name string
1416+
}
1417+
1418+
func (f *completionOnlyFlag) String() string { return "" }
1419+
func (f *completionOnlyFlag) Get() any { return nil }
1420+
func (f *completionOnlyFlag) PreParse() error { return nil }
1421+
func (f *completionOnlyFlag) PostParse() error { return nil }
1422+
func (f *completionOnlyFlag) Set(string, string) error { return nil }
1423+
func (f *completionOnlyFlag) Names() []string { return []string{f.name} }
1424+
func (f *completionOnlyFlag) IsSet() bool { return false }
1425+
14141426
func TestDefaultCompleteWithFlags(t *testing.T) {
14151427
origArgv := os.Args
14161428
t.Cleanup(func() { os.Args = origArgv })
@@ -1429,6 +1441,24 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
14291441
env: map[string]string{"SHELL": "bash"},
14301442
expected: "",
14311443
},
1444+
{
1445+
name: "persistent-flag-after-positional-argument",
1446+
cmd: &Command{
1447+
Name: "add",
1448+
Arguments: []Argument{
1449+
&StringArg{Name: "arg1"},
1450+
},
1451+
parent: &Command{
1452+
Name: "app",
1453+
Flags: []Flag{
1454+
&StringFlag{Name: "config", Usage: "path to config file"},
1455+
},
1456+
},
1457+
},
1458+
argv: []string{"app", "add", "value", "--conf", completionFlag},
1459+
env: map[string]string{"SHELL": "bash"},
1460+
expected: "--config:path to config file\n",
1461+
},
14321462
{
14331463
name: "typical-flag-suggestion",
14341464
cmd: &Command{
@@ -1449,7 +1479,56 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
14491479
},
14501480
argv: []string{"cmd", "--e", completionFlag},
14511481
env: map[string]string{"SHELL": "bash"},
1452-
expected: "--excitement\n",
1482+
expected: "--excitement\n--everybody-jump-on\n",
1483+
},
1484+
{
1485+
name: "shared-local-and-persistent-flag-is-suggested-once",
1486+
cmd: func() *Command {
1487+
shared := &BoolFlag{Name: "verbose"}
1488+
return &Command{
1489+
Flags: []Flag{shared},
1490+
parent: &Command{Name: "app", Flags: []Flag{shared}},
1491+
}
1492+
}(),
1493+
argv: []string{"app", "sub", "--ver", completionFlag},
1494+
env: map[string]string{"SHELL": "bash"},
1495+
expected: "--verbose\n",
1496+
},
1497+
{
1498+
name: "custom-flag-without-visible-flag-is-suggested",
1499+
cmd: &Command{
1500+
Flags: []Flag{&completionOnlyFlag{name: "custom"}},
1501+
},
1502+
argv: []string{"app", "--cus", completionFlag},
1503+
env: map[string]string{"SHELL": "bash"},
1504+
expected: "--custom\n",
1505+
},
1506+
{
1507+
name: "mutually-exclusive-flags-are-suggested",
1508+
cmd: &Command{
1509+
MutuallyExclusiveFlags: []MutuallyExclusiveFlags{{
1510+
Flags: [][]Flag{
1511+
{&BoolFlag{Name: "alpha"}},
1512+
{&BoolFlag{Name: "also"}},
1513+
},
1514+
}},
1515+
},
1516+
argv: []string{"app", "--al", completionFlag},
1517+
env: map[string]string{"SHELL": "bash"},
1518+
expected: "--alpha\n--also\n",
1519+
},
1520+
{
1521+
name: "skip-flag-parsing-does-not-suggest-parent-flags",
1522+
cmd: &Command{
1523+
SkipFlagParsing: true,
1524+
parent: &Command{
1525+
Name: "app",
1526+
Flags: []Flag{&BoolFlag{Name: "root-flag"}},
1527+
},
1528+
},
1529+
argv: []string{"app", "sub", "--r", completionFlag},
1530+
env: map[string]string{"SHELL": "bash"},
1531+
expected: "",
14531532
},
14541533
{
14551534
name: "typical-flag-suggestion-hidden-bool",
@@ -1471,7 +1550,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
14711550
},
14721551
argv: []string{"cmd", "--e", completionFlag},
14731552
env: map[string]string{"SHELL": "bash"},
1474-
expected: "",
1553+
expected: "--everybody-jump-on\n",
14751554
},
14761555
{
14771556
name: "typical-flag-suggestion-hidden-non-bool",
@@ -1493,7 +1572,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
14931572
},
14941573
argv: []string{"cmd", "--e", completionFlag},
14951574
env: map[string]string{"SHELL": "bash"},
1496-
expected: "--excitement\n",
1575+
expected: "--excitement\n--everybody-jump-on\n",
14971576
},
14981577
{
14991578
name: "typical-flag-suggestion-hidden-bool-with-inverse",
@@ -1515,7 +1594,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
15151594
},
15161595
argv: []string{"cmd", "--e", completionFlag},
15171596
env: map[string]string{"SHELL": "bash"},
1518-
expected: "--excitement\n",
1597+
expected: "--excitement\n--everybody-jump-on\n",
15191598
},
15201599
{
15211600
name: "flag-suggestion-double-dash-shows-all-flags",
@@ -1537,7 +1616,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
15371616
},
15381617
argv: []string{"cmd", "--e", "--", completionFlag},
15391618
env: map[string]string{"SHELL": "bash"},
1540-
expected: "--excitement\n--hat-shape\n",
1619+
expected: "--excitement\n--hat-shape\n--happiness\n--everybody-jump-on\n",
15411620
},
15421621
{
15431622
name: "typical-command-suggestion",
@@ -1603,7 +1682,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
16031682
},
16041683
argv: []string{"cmd", "putz", "-e", completionFlag},
16051684
env: map[string]string{"SHELL": "zsh"},
1606-
expected: "--excitement:an exciting flag\n",
1685+
expected: "--excitement:an exciting flag\n--everybody-jump-on\n",
16071686
},
16081687
{
16091688
name: "zsh-autocomplete-with-empty-flag-descriptions",
@@ -1623,7 +1702,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
16231702
},
16241703
argv: []string{"cmd", "putz", "-e", completionFlag},
16251704
env: map[string]string{"SHELL": "zsh"},
1626-
expected: "--excitement\n",
1705+
expected: "--excitement\n--everybody-jump-on\n",
16271706
},
16281707
} {
16291708
t.Run(tc.name, func(ct *testing.T) {

0 commit comments

Comments
 (0)