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
17 changes: 17 additions & 0 deletions command_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ func (cmd *Command) parseFlags(args Args) (Args, error) {

// stop parsing once we see a "--"
if firstArg == "--" {
// In shell completion mode, preserve "--" so that completion can detect
// when the user is completing "--" itself vs. completing after "--"
if cmd.Root().shellCompletion {
posArgs = append(posArgs, firstArg)
}
posArgs = append(posArgs, rargs[1:]...)
return &stringSliceArgs{posArgs}, nil
}
Expand Down Expand Up @@ -166,6 +171,12 @@ func (cmd *Command) parseFlags(args Args) (Args, error) {
// not a bool flag so need to get the next arg
if flagVal == "" && !valFromEqual {
if len(rargs) == 1 {
// In shell completion mode, preserve the flag so that DefaultCompleteWithFlags can use it
// as lastArg and offer suggestions for it.
if cmd.Root().shellCompletion {
posArgs = append(posArgs, rargs...)
return &stringSliceArgs{posArgs}, nil
}
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", argumentNotProvidedErrMsg, firstArg)
}
flagVal = rargs[1]
Expand All @@ -182,6 +193,12 @@ func (cmd *Command) parseFlags(args Args) (Args, error) {

// no flag lookup found and short handling is disabled
if !shortOptionHandling {
// In shell completion mode, preserve the partial flag so that DefaultCompleteWithFlags can use it
// as lastArg and offer suggestions that match the prefix.
if cmd.Root().shellCompletion {
posArgs = append(posArgs, rargs...)
return &stringSliceArgs{posArgs}, nil
}
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", providedButNotDefinedErrMsg, flagName)
}

Expand Down
4 changes: 2 additions & 2 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -599,8 +599,8 @@ func TestCommand_Run_CustomShellCompleteAcceptsMalformedFlags(t *testing.T) {
testArgs *stringSliceArgs
expectedOut string
}{
{testArgs: &stringSliceArgs{v: []string{"--undefined"}}, expectedOut: "found 0 args"},
{testArgs: &stringSliceArgs{v: []string{"--number"}}, expectedOut: "found 0 args"},
{testArgs: &stringSliceArgs{v: []string{"--undefined"}}, expectedOut: "found 1 args"},
{testArgs: &stringSliceArgs{v: []string{"--number"}}, expectedOut: "found 1 args"},
{testArgs: &stringSliceArgs{v: []string{"--number", "forty-two"}}, expectedOut: "found 0 args"},
{testArgs: &stringSliceArgs{v: []string{"--number", "42"}}, expectedOut: "found 0 args"},
{testArgs: &stringSliceArgs{v: []string{"--number", "42", "newArg"}}, expectedOut: "found 1 args"},
Expand Down
34 changes: 25 additions & 9 deletions completion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,13 @@ func TestCompletionSubcommand(t *testing.T) {
},
},
{
name: "subcommand flag no completion",
name: "subcommand double dash shows long flags",
args: []string{"foo", "bar", "--", completionFlag},
contains: "l1",
msg: "Expected output to contain shell name %[1]q",
contains: "--l1",
msg: "Expected output to contain flag %[1]q",
msgArgs: []any{
"l1",
"--l1",
},
notContains: true,
},
{
name: "sub sub command general completion",
Expand All @@ -150,14 +149,13 @@ func TestCompletionSubcommand(t *testing.T) {
},
},
{
name: "sub sub command no completion",
name: "sub sub command double dash shows flags",
args: []string{"foo", "bar", "xyz", "--", completionFlag},
contains: "-g",
contains: "--help",
msg: "Expected output to contain flag %[1]q",
msgArgs: []any{
"-g",
"--help",
},
notContains: true,
},
{
name: "sub sub command no completion extra args",
Expand All @@ -169,6 +167,24 @@ func TestCompletionSubcommand(t *testing.T) {
},
notContains: true,
},
{
name: "subcommand partial double dash flag completion",
args: []string{"foo", "bar", "--l", completionFlag},
contains: "--l1",
msg: "Expected output to contain flag %[1]q",
msgArgs: []any{
"--l1",
},
},
{
name: "sub sub command partial double dash flag completion",
args: []string{"foo", "bar", "xyz", "--he", completionFlag},
contains: "--help",
msg: "Expected output to contain flag %[1]q",
msgArgs: []any{
"--help",
},
},
}

for _, test := range tests {
Expand Down
13 changes: 5 additions & 8 deletions help.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,6 @@ func DefaultCompleteWithFlags(ctx context.Context, cmd *Command) {
lastArg = args[argsLen-1]
}

if lastArg == "--" {
tracef("No completions due to termination")
return
}

if lastArg == completionFlag {
lastArg = ""
}
Expand Down Expand Up @@ -485,10 +480,12 @@ func checkShellCompleteFlag(c *Command, arguments []string) (bool, []string) {
return false, arguments
}

// If arguments include "--", shell completion is disabled
// because after "--" only positional arguments are accepted.
// If arguments include "--" before the token being completed, shell completion
// is disabled because after "--" only positional arguments are accepted.
// https://unix.stackexchange.com/a/11382
if slices.Contains(arguments, "--") {
// Note: The token being completed is at position pos-1 (immediately before completionFlag).
// We only check arguments before that position, so completing "--" itself still works.
if pos >= 1 && slices.Contains(arguments[:pos-1], "--") {
return false, arguments[:pos]
}

Expand Down
13 changes: 11 additions & 2 deletions help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1325,7 +1325,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
expected: "",
},
{
name: "flag-suggestion-end-args",
name: "flag-suggestion-double-dash-shows-all-flags",
cmd: &Command{
Flags: []Flag{
&BoolFlag{Name: "excitement"},
Expand All @@ -1344,7 +1344,7 @@ func TestDefaultCompleteWithFlags(t *testing.T) {
},
argv: []string{"cmd", "--e", "--", completionFlag},
env: map[string]string{"SHELL": "bash"},
expected: "",
expected: "--excitement\n--hat-shape\n",
},
{
name: "typical-command-suggestion",
Expand Down Expand Up @@ -1907,6 +1907,15 @@ func Test_checkShellCompleteFlag(t *testing.T) {
wantShellCompletion: true,
wantArgs: []string{"foo"},
},
{
name: "double dash is the token being completed",
arguments: []string{"foo", "--", completionFlag},
cmd: &Command{
EnableShellCompletion: true,
},
wantShellCompletion: true,
wantArgs: []string{"foo", "--"},
},
}

for _, tt := range tests {
Expand Down