From 8f45075ebc74db2a074a301e9d79b2d203a5e99b Mon Sep 17 00:00:00 2001 From: Rebecca Stambler Date: Sun, 29 Dec 2019 02:22:12 -0500 Subject: [PATCH] internal/lsp: merge completion options into source.Options This change flattens the completion options type into UserOptions and DebuggingOptions, which will enable us to generate documentation for these options more effectively. This results in some modifications in the tests. Additionally, the fuzzyMatching and caseSensitive boolean flags are merged into one setting, matcher, which can be used to specify the type of matcher that is used for completion. Other requests (notably workspaceSymbols) may need to use a matcher in the future. Change-Id: I185875e50351be4090c7a2b3340d40286dc9f4a0 Reviewed-on: https://go-review.googlesource.com/c/tools/+/212635 Run-TryBot: Rebecca Stambler TryBot-Result: Gobot Gobot Reviewed-by: Heschi Kreinick --- internal/lsp/completion.go | 9 +- internal/lsp/completion_test.go | 52 +++++----- internal/lsp/source/completion.go | 36 ++++--- internal/lsp/source/completion_format.go | 4 +- internal/lsp/source/completion_literal.go | 6 +- internal/lsp/source/completion_snippet.go | 4 +- internal/lsp/source/deep_completion.go | 4 +- internal/lsp/source/options.go | 115 +++++++++++++++------- internal/lsp/source/source_test.go | 61 +++++++----- internal/lsp/tests/tests.go | 2 +- 10 files changed, 176 insertions(+), 117 deletions(-) diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go index 820e039b12..7edc1844b4 100644 --- a/internal/lsp/completion.go +++ b/internal/lsp/completion.go @@ -24,7 +24,6 @@ func (s *Server) completion(ctx context.Context, params *protocol.CompletionPara return nil, err } snapshot := view.Snapshot() - options := view.Options() fh, err := snapshot.GetFile(uri) if err != nil { return nil, err @@ -33,8 +32,7 @@ func (s *Server) completion(ctx context.Context, params *protocol.CompletionPara var surrounding *source.Selection switch fh.Identity().Kind { case source.Go: - options.Completion.FullDocumentation = options.HoverKind == source.FullDocumentation - candidates, surrounding, err = source.Completion(ctx, snapshot, fh, params.Position, options.Completion) + candidates, surrounding, err = source.Completion(ctx, snapshot, fh, params.Position) case source.Mod: candidates, surrounding = nil, nil } @@ -62,7 +60,8 @@ func (s *Server) completion(ctx context.Context, params *protocol.CompletionPara // When using deep completions/fuzzy matching, report results as incomplete so // client fetches updated completions after every key stroke. - incompleteResults := options.Completion.Deep || options.Completion.FuzzyMatching + options := view.Options() + incompleteResults := options.DeepCompletion || options.Matcher == source.Fuzzy items := toProtocolCompletionItems(candidates, rng, options) @@ -94,7 +93,7 @@ func toProtocolCompletionItems(candidates []source.CompletionItem, rng protocol. // Limit the number of deep completions to not overwhelm the user in cases // with dozens of deep completion matches. if candidate.Depth > 0 { - if !options.Completion.Deep { + if !options.DeepCompletion { continue } if numDeepCompletionsSeen >= source.MaxDeepCompletions { diff --git a/internal/lsp/completion_test.go b/internal/lsp/completion_test.go index 846a54b467..a8955e62b7 100644 --- a/internal/lsp/completion_test.go +++ b/internal/lsp/completion_test.go @@ -11,11 +11,10 @@ import ( ) func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { - got := r.callCompletion(t, src, source.CompletionOptions{ - Deep: false, - FuzzyMatching: false, - Documentation: true, - Literal: strings.Contains(string(src.URI()), "literal"), + got := r.callCompletion(t, src, func(opts *source.Options) { + opts.DeepCompletion = false + opts.Matcher = source.CaseInsensitive + opts.Literal = strings.Contains(string(src.URI()), "literal") }) if !strings.Contains(string(src.URI()), "builtins") { got = tests.FilterBuiltins(got) @@ -27,11 +26,11 @@ func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, } func (r *runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, items tests.CompletionItems) { - list := r.callCompletion(t, src, source.CompletionOptions{ - Placeholders: placeholders, - Deep: true, - FuzzyMatching: true, - Literal: true, + list := r.callCompletion(t, src, func(opts *source.Options) { + opts.Placeholders = placeholders + opts.DeepCompletion = true + opts.Matcher = source.Fuzzy + opts.Literal = true }) got := tests.FindItem(list, *items[expected.CompletionItem]) want := expected.PlainSnippet @@ -44,8 +43,8 @@ func (r *runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.C } func (r *runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { - got := r.callCompletion(t, src, source.CompletionOptions{ - Unimported: true, + got := r.callCompletion(t, src, func(opts *source.Options) { + opts.UnimportedCompletion = true }) if !strings.Contains(string(src.URI()), "builtins") { got = tests.FilterBuiltins(got) @@ -57,9 +56,9 @@ func (r *runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Co } func (r *runner) DeepCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { - got := r.callCompletion(t, src, source.CompletionOptions{ - Deep: true, - Documentation: true, + got := r.callCompletion(t, src, func(opts *source.Options) { + opts.DeepCompletion = true + opts.Matcher = source.CaseInsensitive }) if !strings.Contains(string(src.URI()), "builtins") { got = tests.FilterBuiltins(got) @@ -71,9 +70,9 @@ func (r *runner) DeepCompletion(t *testing.T, src span.Span, test tests.Completi } func (r *runner) FuzzyCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { - got := r.callCompletion(t, src, source.CompletionOptions{ - FuzzyMatching: true, - Deep: true, + got := r.callCompletion(t, src, func(opts *source.Options) { + opts.DeepCompletion = true + opts.Matcher = source.Fuzzy }) if !strings.Contains(string(src.URI()), "builtins") { got = tests.FilterBuiltins(got) @@ -85,8 +84,8 @@ func (r *runner) FuzzyCompletion(t *testing.T, src span.Span, test tests.Complet } func (r *runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { - got := r.callCompletion(t, src, source.CompletionOptions{ - CaseSensitive: true, + got := r.callCompletion(t, src, func(opts *source.Options) { + opts.Matcher = source.CaseSensitive }) if !strings.Contains(string(src.URI()), "builtins") { got = tests.FilterBuiltins(got) @@ -98,10 +97,10 @@ func (r *runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests } func (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { - got := r.callCompletion(t, src, source.CompletionOptions{ - FuzzyMatching: true, - Deep: true, - Literal: true, + got := r.callCompletion(t, src, func(opts *source.Options) { + opts.DeepCompletion = true + opts.Matcher = source.Fuzzy + opts.Literal = true }) want := expected(t, test, items) if msg := tests.CheckCompletionOrder(want, got, true); msg != "" { @@ -120,7 +119,7 @@ func expected(t *testing.T, test tests.Completion, items tests.CompletionItems) return want } -func (r *runner) callCompletion(t *testing.T, src span.Span, options source.CompletionOptions) []protocol.CompletionItem { +func (r *runner) callCompletion(t *testing.T, src span.Span, options func(*source.Options)) []protocol.CompletionItem { t.Helper() view, err := r.server.session.ViewOf(src.URI()) @@ -129,8 +128,7 @@ func (r *runner) callCompletion(t *testing.T, src span.Span, options source.Comp } original := view.Options() modified := original - modified.InsertTextFormat = protocol.SnippetTextFormat - modified.Completion = options + options(&modified) view, err = view.SetOptions(r.ctx, modified) if err != nil { t.Error(err) diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go index 30f3d6ee5e..4f212a1759 100644 --- a/internal/lsp/source/completion.go +++ b/internal/lsp/source/completion.go @@ -131,9 +131,8 @@ func (ipm insensitivePrefixMatcher) Score(candidateLabel string) float32 { type completer struct { snapshot Snapshot pkg Package - - qf types.Qualifier - opts CompletionOptions + qf types.Qualifier + opts *completionOptions // ctx is the context associated with this completion request. ctx context.Context @@ -247,10 +246,10 @@ func (p Selection) Suffix() string { } func (c *completer) deepCompletionContext() (context.Context, context.CancelFunc) { - if c.opts.Budget == 0 { + if c.opts.budget == 0 { return context.WithCancel(c.ctx) } - return context.WithDeadline(c.ctx, c.startTime.Add(c.opts.Budget)) + return context.WithDeadline(c.ctx, c.startTime.Add(c.opts.budget)) } func (c *completer) setSurrounding(ident *ast.Ident) { @@ -268,11 +267,12 @@ func (c *completer) setSurrounding(ident *ast.Ident) { mappedRange: newMappedRange(c.snapshot.View().Session().Cache().FileSet(), c.mapper, ident.Pos(), ident.End()), } - if c.opts.FuzzyMatching { + switch c.opts.matcher { + case Fuzzy: c.matcher = fuzzy.NewMatcher(c.surrounding.Prefix()) - } else if c.opts.CaseSensitive { + case CaseSensitive: c.matcher = prefixMatcher(c.surrounding.Prefix()) - } else { + default: c.matcher = insensitivePrefixMatcher(strings.ToLower(c.surrounding.Prefix())) } } @@ -405,7 +405,7 @@ func (e ErrIsDefinition) Error() string { // The selection is computed based on the preceding identifier and can be used by // the client to score the quality of the completion. For instance, some clients // may tolerate imperfect matches as valid completion results, since users may make typos. -func Completion(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position, opts CompletionOptions) ([]CompletionItem, *Selection, error) { +func Completion(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) ([]CompletionItem, *Selection, error) { ctx, done := trace.StartSpan(ctx, "source.Completion") defer done() @@ -439,6 +439,7 @@ func Completion(ctx context.Context, snapshot Snapshot, fh FileHandle, pos proto return nil, nil, nil } + opts := snapshot.View().Options() c := &completer{ pkg: pkg, snapshot: snapshot, @@ -451,7 +452,16 @@ func Completion(ctx context.Context, snapshot Snapshot, fh FileHandle, pos proto seen: make(map[types.Object]bool), enclosingFunc: enclosingFunction(path, rng.Start, pkg.GetTypesInfo()), enclosingCompositeLiteral: enclosingCompositeLiteral(path, rng.Start, pkg.GetTypesInfo()), - opts: opts, + opts: &completionOptions{ + matcher: opts.Matcher, + deepCompletion: opts.DeepCompletion, + unimported: opts.UnimportedCompletion, + documentation: opts.CompletionDocumentation, + fullDocumentation: opts.HoverKind == FullDocumentation, + placeholders: opts.Placeholders, + literal: opts.Literal, + budget: opts.CompletionBudget, + }, // default to a matcher that always matches matcher: prefixMatcher(""), methodSetCache: make(map[methodSetKey]*types.MethodSet), @@ -459,7 +469,7 @@ func Completion(ctx context.Context, snapshot Snapshot, fh FileHandle, pos proto startTime: startTime, } - if opts.Deep { + if c.opts.deepCompletion { // Initialize max search depth to unlimited. c.deepState.maxDepth = -1 } @@ -629,7 +639,7 @@ func (c *completer) selector(sel *ast.SelectorExpr) error { } // Try unimported packages. - if id, ok := sel.X.(*ast.Ident); ok && c.opts.Unimported && len(c.items) < unimportedTarget { + if id, ok := sel.X.(*ast.Ident); ok && c.opts.unimported && len(c.items) < unimportedTarget { if err := c.unimportedMembers(id); err != nil { return err } @@ -862,7 +872,7 @@ func (c *completer) lexical() error { } } - if c.opts.Unimported && len(c.items) < unimportedTarget { + if c.opts.unimported && len(c.items) < unimportedTarget { ctx, cancel := c.deepCompletionContext() defer cancel() // Suggest packages that have not been imported yet. diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go index 77713dddfe..168308f6bd 100644 --- a/internal/lsp/source/completion_format.go +++ b/internal/lsp/source/completion_format.go @@ -168,7 +168,7 @@ func (c *completer) item(cand candidate) (CompletionItem, error) { snippet: snip, } // If the user doesn't want documentation for completion items. - if !c.opts.Documentation { + if !c.opts.documentation { return item, nil } pos := c.snapshot.View().Session().Cache().FileSet().Position(obj.Pos()) @@ -200,7 +200,7 @@ func (c *completer) item(cand candidate) (CompletionItem, error) { return item, nil } item.Documentation = hover.Synopsis - if c.opts.FullDocumentation { + if c.opts.fullDocumentation { item.Documentation = hover.FullDocumentation } return item, nil diff --git a/internal/lsp/source/completion_literal.go b/internal/lsp/source/completion_literal.go index 287d43d046..89ecda6387 100644 --- a/internal/lsp/source/completion_literal.go +++ b/internal/lsp/source/completion_literal.go @@ -20,7 +20,7 @@ import ( // literal generates composite literal, function literal, and make() // completion items. func (c *completer) literal(literalType types.Type, imp *importInfo) { - if !c.opts.Literal { + if !c.opts.literal { return } @@ -213,7 +213,7 @@ func (c *completer) functionLiteral(sig *types.Signature, matchScore float64) { // Our parameter names are guesses, so they must be placeholders // for easy correction. If placeholders are disabled, don't // offer the completion. - if !c.opts.Placeholders { + if !c.opts.placeholders { return } @@ -367,7 +367,7 @@ func (c *completer) makeCall(typeName string, secondArg string, matchScore float if secondArg != "" { snip.WriteText(", ") snip.WritePlaceholder(func(b *snippet.Builder) { - if c.opts.Placeholders { + if c.opts.placeholders { b.WriteText(secondArg) } }) diff --git a/internal/lsp/source/completion_snippet.go b/internal/lsp/source/completion_snippet.go index 567825693f..6aaf28dce4 100644 --- a/internal/lsp/source/completion_snippet.go +++ b/internal/lsp/source/completion_snippet.go @@ -36,7 +36,7 @@ func (c *completer) structFieldSnippet(label, detail string) *snippet.Builder { snip.WriteText(label + ": ") snip.WritePlaceholder(func(b *snippet.Builder) { // A placeholder snippet turns "Foo{Ba<>" into "Foo{Bar: <*int*>". - if c.opts.Placeholders { + if c.opts.placeholders { b.WriteText(detail) } }) @@ -79,7 +79,7 @@ func (c *completer) functionCallSnippet(name string, params []string) *snippet.B snip := &snippet.Builder{} snip.WriteText(name + "(") - if c.opts.Placeholders { + if c.opts.placeholders { // A placeholder snippet turns "someFun<>" into "someFunc(<*i int*>, *s string*)". for i, p := range params { if i > 0 { diff --git a/internal/lsp/source/deep_completion.go b/internal/lsp/source/deep_completion.go index 24643d8722..74f2bbf5a9 100644 --- a/internal/lsp/source/deep_completion.go +++ b/internal/lsp/source/deep_completion.go @@ -107,8 +107,8 @@ func (c *completer) shouldPrune() bool { } // Check our remaining budget every 100 candidates. - if c.opts.Budget > 0 && c.deepState.candidateCount%100 == 0 { - spent := float64(time.Since(c.startTime)) / float64(c.opts.Budget) + if c.opts.budget > 0 && c.deepState.candidateCount%100 == 0 { + spent := float64(time.Since(c.startTime)) / float64(c.opts.budget) switch { case spent >= 0.90: diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go index e3398211d8..a2eeb0baea 100644 --- a/internal/lsp/source/options.go +++ b/internal/lsp/source/options.go @@ -74,16 +74,13 @@ var ( }, } DefaultUserOptions = UserOptions{ - Env: os.Environ(), - HoverKind: SynopsisDocumentation, - Completion: CompletionOptions{ - Documentation: true, - Deep: true, - FuzzyMatching: true, - Literal: true, - Budget: 100 * time.Millisecond, - }, - LinkTarget: "pkg.go.dev", + Env: os.Environ(), + HoverKind: SynopsisDocumentation, + LinkTarget: "pkg.go.dev", + Matcher: Fuzzy, + DeepCompletion: true, + CompletionDocumentation: true, + Literal: true, } DefaultHooks = Hooks{ ComputeEdits: myers.ComputeEdits, @@ -94,7 +91,9 @@ var ( DefaultExperimentalOptions = ExperimentalOptions{ TempModfile: false, } - DefaultDebuggingOptions = DebuggingOptions{} + DefaultDebuggingOptions = DebuggingOptions{ + CompletionBudget: 100 * time.Millisecond, + } ) type Options struct { @@ -142,7 +141,37 @@ type UserOptions struct { // LocalPrefix is used to specify goimports's -local behavior. LocalPrefix string - Completion CompletionOptions + // Matcher specifies the type of matcher to use for completion requests. + Matcher Matcher + + // DeepCompletion allows completion to perform nested searches through + // possible candidates. + DeepCompletion bool + + // UnimportedCompletion enables completion for unimported packages. + UnimportedCompletion bool + + // CompletionDocumentation returns additional documentation with completion + // requests. + CompletionDocumentation bool + + // Placeholders adds placeholders to parameters and structs in completion + // results. + Placeholders bool + + // Literal enables completion for map, slice, and function literals. + Literal bool +} + +type completionOptions struct { + deepCompletion bool + unimported bool + documentation bool + fullDocumentation bool + placeholders bool + literal bool + matcher Matcher + budget time.Duration } type Hooks struct { @@ -161,26 +190,23 @@ type ExperimentalOptions struct { type DebuggingOptions struct { VerboseOutput bool -} -type CompletionOptions struct { - Deep bool - FuzzyMatching bool - CaseSensitive bool - Unimported bool - Documentation bool - FullDocumentation bool - Placeholders bool - Literal bool - - // Budget is the soft latency goal for completion requests. Most + // CompletionBudget is the soft latency goal for completion requests. Most // requests finish in a couple milliseconds, but in some cases deep // completions can take much longer. As we use up our budget we // dynamically reduce the search scope to ensure we return timely // results. Zero means unlimited. - Budget time.Duration + CompletionBudget time.Duration } +type Matcher int + +const ( + Fuzzy = Matcher(iota) + CaseInsensitive + CaseSensitive +) + type HoverKind int const ( @@ -280,17 +306,13 @@ func (o *Options) set(name string, value interface{}) OptionResult { o.BuildFlags = flags case "completionDocumentation": - result.setBool(&o.Completion.Documentation) + result.setBool(&o.CompletionDocumentation) case "usePlaceholders": - result.setBool(&o.Completion.Placeholders) + result.setBool(&o.Placeholders) case "deepCompletion": - result.setBool(&o.Completion.Deep) - case "fuzzyMatching": - result.setBool(&o.Completion.FuzzyMatching) - case "caseSensitiveCompletion": - result.setBool(&o.Completion.CaseSensitive) + result.setBool(&o.DeepCompletion) case "completeUnimported": - result.setBool(&o.Completion.Unimported) + result.setBool(&o.UnimportedCompletion) case "completionBudget": if v, ok := result.asString(); ok { d, err := time.ParseDuration(v) @@ -298,13 +320,26 @@ func (o *Options) set(name string, value interface{}) OptionResult { result.errorf("failed to parse duration %q: %v", v, err) break } - o.Completion.Budget = d + o.CompletionBudget = d + } + + case "matcher": + matcher, ok := result.asString() + if !ok { + break + } + switch matcher { + case "fuzzy": + o.Matcher = Fuzzy + case "caseSensitive": + o.Matcher = CaseSensitive + default: + o.Matcher = CaseInsensitive } case "hoverKind": - hoverKind, ok := value.(string) + hoverKind, ok := result.asString() if !ok { - result.errorf("invalid type %T for string option %q", value, name) break } switch hoverKind { @@ -378,6 +413,14 @@ func (o *Options) set(name string, value interface{}) OptionResult { result.State = OptionDeprecated result.Replacement = "completeUnimported" + case "fuzzyMatching": + result.State = OptionDeprecated + result.Replacement = "matcher" + + case "caseSensitiveCompletion": + result.State = OptionDeprecated + result.Replacement = "matcher" + case "noIncrementalSync": result.State = OptionDeprecated diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index defab5e333..f6d36e9320 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -107,10 +107,10 @@ func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, for _, pos := range test.CompletionItems { want = append(want, tests.ToProtocolCompletionItem(*items[pos])) } - prefix, list := r.callCompletion(t, src, source.CompletionOptions{ - Documentation: true, - FuzzyMatching: true, - Literal: strings.Contains(string(src.URI()), "literal"), + prefix, list := r.callCompletion(t, src, func(opts *source.Options) { + opts.Matcher = source.Fuzzy + opts.Literal = strings.Contains(string(src.URI()), "literal") + opts.DeepCompletion = false }) if !strings.Contains(string(src.URI()), "builtins") { list = tests.FilterBuiltins(list) @@ -128,10 +128,10 @@ func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, } func (r *runner) CompletionSnippet(t *testing.T, src span.Span, expected tests.CompletionSnippet, placeholders bool, items tests.CompletionItems) { - _, list := r.callCompletion(t, src, source.CompletionOptions{ - Placeholders: placeholders, - Deep: true, - Literal: true, + _, list := r.callCompletion(t, src, func(opts *source.Options) { + opts.Placeholders = placeholders + opts.DeepCompletion = true + opts.Literal = true }) got := tests.FindItem(list, *items[expected.CompletionItem]) want := expected.PlainSnippet @@ -148,8 +148,8 @@ func (r *runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Co for _, pos := range test.CompletionItems { want = append(want, tests.ToProtocolCompletionItem(*items[pos])) } - _, got := r.callCompletion(t, src, source.CompletionOptions{ - Unimported: true, + _, got := r.callCompletion(t, src, func(opts *source.Options) { + opts.UnimportedCompletion = true }) if !strings.Contains(string(src.URI()), "builtins") { got = tests.FilterBuiltins(got) @@ -164,9 +164,9 @@ func (r *runner) DeepCompletion(t *testing.T, src span.Span, test tests.Completi for _, pos := range test.CompletionItems { want = append(want, tests.ToProtocolCompletionItem(*items[pos])) } - prefix, list := r.callCompletion(t, src, source.CompletionOptions{ - Deep: true, - Documentation: true, + prefix, list := r.callCompletion(t, src, func(opts *source.Options) { + opts.DeepCompletion = true + opts.Matcher = source.CaseInsensitive }) if !strings.Contains(string(src.URI()), "builtins") { list = tests.FilterBuiltins(list) @@ -189,9 +189,9 @@ func (r *runner) FuzzyCompletion(t *testing.T, src span.Span, test tests.Complet for _, pos := range test.CompletionItems { want = append(want, tests.ToProtocolCompletionItem(*items[pos])) } - _, got := r.callCompletion(t, src, source.CompletionOptions{ - FuzzyMatching: true, - Deep: true, + _, got := r.callCompletion(t, src, func(opts *source.Options) { + opts.DeepCompletion = true + opts.Matcher = source.Fuzzy }) if !strings.Contains(string(src.URI()), "builtins") { got = tests.FilterBuiltins(got) @@ -206,8 +206,8 @@ func (r *runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests for _, pos := range test.CompletionItems { want = append(want, tests.ToProtocolCompletionItem(*items[pos])) } - _, list := r.callCompletion(t, src, source.CompletionOptions{ - CaseSensitive: true, + _, list := r.callCompletion(t, src, func(opts *source.Options) { + opts.Matcher = source.CaseSensitive }) if !strings.Contains(string(src.URI()), "builtins") { list = tests.FilterBuiltins(list) @@ -222,25 +222,34 @@ func (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completi for _, pos := range test.CompletionItems { want = append(want, tests.ToProtocolCompletionItem(*items[pos])) } - _, got := r.callCompletion(t, src, source.CompletionOptions{ - FuzzyMatching: true, - Deep: true, - Literal: true, + _, got := r.callCompletion(t, src, func(opts *source.Options) { + opts.DeepCompletion = true + opts.Matcher = source.Fuzzy + opts.Literal = true }) if msg := tests.CheckCompletionOrder(want, got, true); msg != "" { t.Errorf("%s: %s", src, msg) } } -func (r *runner) callCompletion(t *testing.T, src span.Span, options source.CompletionOptions) (string, []protocol.CompletionItem) { +func (r *runner) callCompletion(t *testing.T, src span.Span, options func(*source.Options)) (string, []protocol.CompletionItem) { fh, err := r.view.Snapshot().GetFile(src.URI()) if err != nil { t.Fatal(err) } - list, surrounding, err := source.Completion(r.ctx, r.view.Snapshot(), fh, protocol.Position{ + original := r.view.Options() + modified := original + options(&modified) + view, err := r.view.SetOptions(r.ctx, modified) + if err != nil { + t.Fatal(err) + } + defer r.view.SetOptions(r.ctx, original) + + list, surrounding, err := source.Completion(r.ctx, view.Snapshot(), fh, protocol.Position{ Line: float64(src.Start().Line() - 1), Character: float64(src.Start().Column() - 1), - }, options) + }) if err != nil && !errors.As(err, &source.ErrIsDefinition{}) { t.Fatalf("failed for %v: %v", src, err) } @@ -261,7 +270,7 @@ func (r *runner) callCompletion(t *testing.T, src span.Span, options source.Comp // Apply deep completion filtering. for _, item := range list { if item.Depth > 0 { - if !options.Deep { + if !modified.DeepCompletion { continue } if numDeepCompletionsSeen >= source.MaxDeepCompletions { diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index 3b9d129b7c..49608d34c5 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -191,7 +191,7 @@ func DefaultOptions() source.Options { } o.HoverKind = source.SynopsisDocumentation o.InsertTextFormat = protocol.SnippetTextFormat - o.Completion.Budget = time.Minute + o.CompletionBudget = time.Minute return o }