Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions completion_partial_regression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ func TestPartialFlagCompletionAfterPositionalArgument(t *testing.T) {
for _, scope := range []struct {
name string
path []string
want string
}{
{"app", nil},
{"sub", []string{"sub"}},
{"nested", []string{"sub", "nested"}},
{"app", nil, "--param-app-one\n--param-app-two\n"},
{"sub", []string{"sub"}, "--param-sub-one\n--param-sub-two\n--param-app-one\n--param-app-two\n"},
{"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"},
} {
for _, positional := range [][]string{nil, {"value"}, {"first", "second"}} {
t.Run(scope.name+"/"+strings.Join(positional, "_"), func(t *testing.T) {
Expand All @@ -53,9 +54,8 @@ func TestPartialFlagCompletionAfterPositionalArgument(t *testing.T) {
if err := cmd.Run(context.Background(), args); err != nil {
t.Fatal(err)
}
want := "--param-" + scope.name + "-one\n--param-" + scope.name + "-two\n"
if got := output.String(); got != want {
t.Errorf("completion for %q = %q, want %q", args, got, want)
if got := output.String(); got != scope.want {
t.Errorf("completion for %q = %q, want %q", args, got, scope.want)
}
if ran {
t.Error("completion executed the command action")
Expand Down
10 changes: 9 additions & 1 deletion help.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,15 @@ func DefaultCompleteWithFlags(ctx context.Context, cmd *Command) {

if strings.HasPrefix(lastArg, "-") {
tracef("printing flag suggestion for flag[%v] on command %[1]q", lastArg, cmd.Name)
printFlagSuggestions(lastArg, cmd.Flags, cmd.Root().Writer)
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)
return
}

Expand Down
93 changes: 86 additions & 7 deletions help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1411,6 +1411,18 @@ func TestHideHelp_SubcommandCannotOptBackIn(t *testing.T) {
r.ErrorContains(cmd.Run(buildTestContext(t), []string{"cli.test", "nully", "--help"}), providedButNotDefinedErrMsg)
}

type completionOnlyFlag struct {
name string
}

func (f *completionOnlyFlag) String() string { return "" }
func (f *completionOnlyFlag) Get() any { return nil }
func (f *completionOnlyFlag) PreParse() error { return nil }
func (f *completionOnlyFlag) PostParse() error { return nil }
func (f *completionOnlyFlag) Set(string, string) error { return nil }
func (f *completionOnlyFlag) Names() []string { return []string{f.name} }
func (f *completionOnlyFlag) IsSet() bool { return false }

func TestDefaultCompleteWithFlags(t *testing.T) {
origArgv := os.Args
t.Cleanup(func() { os.Args = origArgv })
Expand All @@ -1429,6 +1441,24 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
env: map[string]string{"SHELL": "bash"},
expected: "",
},
{
name: "persistent-flag-after-positional-argument",
cmd: &Command{
Name: "add",
Arguments: []Argument{
&StringArg{Name: "arg1"},
},
parent: &Command{
Name: "app",
Flags: []Flag{
&StringFlag{Name: "config", Usage: "path to config file"},
},
},
},
argv: []string{"app", "add", "value", "--conf", completionFlag},
env: map[string]string{"SHELL": "bash"},
expected: "--config:path to config file\n",
},
{
name: "typical-flag-suggestion",
cmd: &Command{
Expand All @@ -1449,7 +1479,56 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
},
argv: []string{"cmd", "--e", completionFlag},
env: map[string]string{"SHELL": "bash"},
expected: "--excitement\n",
expected: "--excitement\n--everybody-jump-on\n",
},
{
name: "shared-local-and-persistent-flag-is-suggested-once",
cmd: func() *Command {
shared := &BoolFlag{Name: "verbose"}
return &Command{
Flags: []Flag{shared},
parent: &Command{Name: "app", Flags: []Flag{shared}},
}
}(),
argv: []string{"app", "sub", "--ver", completionFlag},
env: map[string]string{"SHELL": "bash"},
expected: "--verbose\n",
},
{
name: "custom-flag-without-visible-flag-is-suggested",
cmd: &Command{
Flags: []Flag{&completionOnlyFlag{name: "custom"}},
},
argv: []string{"app", "--cus", completionFlag},
env: map[string]string{"SHELL": "bash"},
expected: "--custom\n",
},
{
name: "mutually-exclusive-flags-are-suggested",
cmd: &Command{
MutuallyExclusiveFlags: []MutuallyExclusiveFlags{{
Flags: [][]Flag{
{&BoolFlag{Name: "alpha"}},
{&BoolFlag{Name: "also"}},
},
}},
},
argv: []string{"app", "--al", completionFlag},
env: map[string]string{"SHELL": "bash"},
expected: "--alpha\n--also\n",
},
{
name: "skip-flag-parsing-does-not-suggest-parent-flags",
cmd: &Command{
SkipFlagParsing: true,
parent: &Command{
Name: "app",
Flags: []Flag{&BoolFlag{Name: "root-flag"}},
},
},
argv: []string{"app", "sub", "--r", completionFlag},
env: map[string]string{"SHELL": "bash"},
expected: "",
},
{
name: "typical-flag-suggestion-hidden-bool",
Expand All @@ -1471,7 +1550,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
},
argv: []string{"cmd", "--e", completionFlag},
env: map[string]string{"SHELL": "bash"},
expected: "",
expected: "--everybody-jump-on\n",
},
{
name: "typical-flag-suggestion-hidden-non-bool",
Expand All @@ -1493,7 +1572,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
},
argv: []string{"cmd", "--e", completionFlag},
env: map[string]string{"SHELL": "bash"},
expected: "--excitement\n",
expected: "--excitement\n--everybody-jump-on\n",
},
{
name: "typical-flag-suggestion-hidden-bool-with-inverse",
Expand All @@ -1515,7 +1594,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
},
argv: []string{"cmd", "--e", completionFlag},
env: map[string]string{"SHELL": "bash"},
expected: "--excitement\n",
expected: "--excitement\n--everybody-jump-on\n",
},
{
name: "flag-suggestion-double-dash-shows-all-flags",
Expand All @@ -1537,7 +1616,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
},
argv: []string{"cmd", "--e", "--", completionFlag},
env: map[string]string{"SHELL": "bash"},
expected: "--excitement\n--hat-shape\n",
expected: "--excitement\n--hat-shape\n--happiness\n--everybody-jump-on\n",
},
{
name: "typical-command-suggestion",
Expand Down Expand Up @@ -1603,7 +1682,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
},
argv: []string{"cmd", "putz", "-e", completionFlag},
env: map[string]string{"SHELL": "zsh"},
expected: "--excitement:an exciting flag\n",
expected: "--excitement:an exciting flag\n--everybody-jump-on\n",
},
{
name: "zsh-autocomplete-with-empty-flag-descriptions",
Expand All @@ -1623,7 +1702,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
},
argv: []string{"cmd", "putz", "-e", completionFlag},
env: map[string]string{"SHELL": "zsh"},
expected: "--excitement\n",
expected: "--excitement\n--everybody-jump-on\n",
},
} {
t.Run(tc.name, func(ct *testing.T) {
Expand Down