diff --git a/command.go b/command.go index eeeb9e0..de501d5 100644 --- a/command.go +++ b/command.go @@ -74,6 +74,12 @@ func (c *Command) predict(a Args) (options []string, only bool) { return } } + + // We matched so stop searching. Continuing to search can accidentally + // match a subcommand with current set of commands, see issue #46. + if subCommandFound { + break + } } // if last completed word is a global flag that we need to complete diff --git a/complete_test.go b/complete_test.go index 135c6ad..1a42da1 100644 --- a/complete_test.go +++ b/complete_test.go @@ -184,6 +184,81 @@ func TestCompleter_Complete(t *testing.T) { } } +func TestCompleter_Complete_SharedPrefix(t *testing.T) { + t.Parallel() + initTests() + + c := Command{ + Sub: Commands{ + "status": { + Flags: Flags{ + "-f3": PredictNothing, + }, + }, + "job": { + Sub: Commands{ + "status": { + Flags: Flags{ + "-f4": PredictNothing, + }, + }, + }, + }, + }, + Flags: Flags{ + "-o": PredictFiles("*.txt"), + }, + GlobalFlags: Flags{ + "-h": PredictNothing, + "-global1": PredictAnything, + }, + } + + tests := []struct { + args string + want []string + }{ + { + args: "", + want: []string{"status", "job", "-h", "-global1", "-o"}, + }, + { + args: "-", + want: []string{"-h", "-global1", "-o"}, + }, + { + args: "j", + want: []string{"job"}, + }, + { + args: "job ", + want: []string{"-h", "-global1", "status"}, + }, + { + args: "job status ", + want: []string{"-f4", "-h", "-global1"}, + }, + } + + for _, tt := range tests { + t.Run(tt.args, func(t *testing.T) { + + tt.args = "cmd " + tt.args + os.Setenv(envComplete, tt.args) + line, _ := getLine() + + got := c.Predict(newArgs(line)) + + sort.Strings(tt.want) + sort.Strings(got) + + if !equalSlices(got, tt.want) { + t.Errorf("failed '%s'\ngot = %s\nwant: %s", t.Name(), got, tt.want) + } + }) + } +} + func equalSlices(a, b []string) bool { if len(a) != len(b) { return false