1
0
mirror of https://github.com/golang/go synced 2024-11-18 17:14:45 -07:00

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 <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
This commit is contained in:
Rebecca Stambler 2019-12-29 02:22:12 -05:00
parent 6316571e2c
commit 8f45075ebc
10 changed files with 176 additions and 117 deletions

View File

@ -24,7 +24,6 @@ func (s *Server) completion(ctx context.Context, params *protocol.CompletionPara
return nil, err return nil, err
} }
snapshot := view.Snapshot() snapshot := view.Snapshot()
options := view.Options()
fh, err := snapshot.GetFile(uri) fh, err := snapshot.GetFile(uri)
if err != nil { if err != nil {
return nil, err return nil, err
@ -33,8 +32,7 @@ func (s *Server) completion(ctx context.Context, params *protocol.CompletionPara
var surrounding *source.Selection var surrounding *source.Selection
switch fh.Identity().Kind { switch fh.Identity().Kind {
case source.Go: case source.Go:
options.Completion.FullDocumentation = options.HoverKind == source.FullDocumentation candidates, surrounding, err = source.Completion(ctx, snapshot, fh, params.Position)
candidates, surrounding, err = source.Completion(ctx, snapshot, fh, params.Position, options.Completion)
case source.Mod: case source.Mod:
candidates, surrounding = nil, nil 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 // When using deep completions/fuzzy matching, report results as incomplete so
// client fetches updated completions after every key stroke. // 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) 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 // Limit the number of deep completions to not overwhelm the user in cases
// with dozens of deep completion matches. // with dozens of deep completion matches.
if candidate.Depth > 0 { if candidate.Depth > 0 {
if !options.Completion.Deep { if !options.DeepCompletion {
continue continue
} }
if numDeepCompletionsSeen >= source.MaxDeepCompletions { if numDeepCompletionsSeen >= source.MaxDeepCompletions {

View File

@ -11,11 +11,10 @@ import (
) )
func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) { func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
got := r.callCompletion(t, src, source.CompletionOptions{ got := r.callCompletion(t, src, func(opts *source.Options) {
Deep: false, opts.DeepCompletion = false
FuzzyMatching: false, opts.Matcher = source.CaseInsensitive
Documentation: true, opts.Literal = strings.Contains(string(src.URI()), "literal")
Literal: strings.Contains(string(src.URI()), "literal"),
}) })
if !strings.Contains(string(src.URI()), "builtins") { if !strings.Contains(string(src.URI()), "builtins") {
got = tests.FilterBuiltins(got) 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) { 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{ list := r.callCompletion(t, src, func(opts *source.Options) {
Placeholders: placeholders, opts.Placeholders = placeholders
Deep: true, opts.DeepCompletion = true
FuzzyMatching: true, opts.Matcher = source.Fuzzy
Literal: true, opts.Literal = true
}) })
got := tests.FindItem(list, *items[expected.CompletionItem]) got := tests.FindItem(list, *items[expected.CompletionItem])
want := expected.PlainSnippet 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) { func (r *runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
got := r.callCompletion(t, src, source.CompletionOptions{ got := r.callCompletion(t, src, func(opts *source.Options) {
Unimported: true, opts.UnimportedCompletion = true
}) })
if !strings.Contains(string(src.URI()), "builtins") { if !strings.Contains(string(src.URI()), "builtins") {
got = tests.FilterBuiltins(got) 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) { func (r *runner) DeepCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
got := r.callCompletion(t, src, source.CompletionOptions{ got := r.callCompletion(t, src, func(opts *source.Options) {
Deep: true, opts.DeepCompletion = true
Documentation: true, opts.Matcher = source.CaseInsensitive
}) })
if !strings.Contains(string(src.URI()), "builtins") { if !strings.Contains(string(src.URI()), "builtins") {
got = tests.FilterBuiltins(got) 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) { func (r *runner) FuzzyCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
got := r.callCompletion(t, src, source.CompletionOptions{ got := r.callCompletion(t, src, func(opts *source.Options) {
FuzzyMatching: true, opts.DeepCompletion = true
Deep: true, opts.Matcher = source.Fuzzy
}) })
if !strings.Contains(string(src.URI()), "builtins") { if !strings.Contains(string(src.URI()), "builtins") {
got = tests.FilterBuiltins(got) 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) { func (r *runner) CaseSensitiveCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
got := r.callCompletion(t, src, source.CompletionOptions{ got := r.callCompletion(t, src, func(opts *source.Options) {
CaseSensitive: true, opts.Matcher = source.CaseSensitive
}) })
if !strings.Contains(string(src.URI()), "builtins") { if !strings.Contains(string(src.URI()), "builtins") {
got = tests.FilterBuiltins(got) 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) { func (r *runner) RankCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
got := r.callCompletion(t, src, source.CompletionOptions{ got := r.callCompletion(t, src, func(opts *source.Options) {
FuzzyMatching: true, opts.DeepCompletion = true
Deep: true, opts.Matcher = source.Fuzzy
Literal: true, opts.Literal = true
}) })
want := expected(t, test, items) want := expected(t, test, items)
if msg := tests.CheckCompletionOrder(want, got, true); msg != "" { 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 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() t.Helper()
view, err := r.server.session.ViewOf(src.URI()) 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() original := view.Options()
modified := original modified := original
modified.InsertTextFormat = protocol.SnippetTextFormat options(&modified)
modified.Completion = options
view, err = view.SetOptions(r.ctx, modified) view, err = view.SetOptions(r.ctx, modified)
if err != nil { if err != nil {
t.Error(err) t.Error(err)

View File

@ -131,9 +131,8 @@ func (ipm insensitivePrefixMatcher) Score(candidateLabel string) float32 {
type completer struct { type completer struct {
snapshot Snapshot snapshot Snapshot
pkg Package pkg Package
qf types.Qualifier
qf types.Qualifier opts *completionOptions
opts CompletionOptions
// ctx is the context associated with this completion request. // ctx is the context associated with this completion request.
ctx context.Context ctx context.Context
@ -247,10 +246,10 @@ func (p Selection) Suffix() string {
} }
func (c *completer) deepCompletionContext() (context.Context, context.CancelFunc) { 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.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) { 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()), 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()) c.matcher = fuzzy.NewMatcher(c.surrounding.Prefix())
} else if c.opts.CaseSensitive { case CaseSensitive:
c.matcher = prefixMatcher(c.surrounding.Prefix()) c.matcher = prefixMatcher(c.surrounding.Prefix())
} else { default:
c.matcher = insensitivePrefixMatcher(strings.ToLower(c.surrounding.Prefix())) 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 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 // 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. // 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") ctx, done := trace.StartSpan(ctx, "source.Completion")
defer done() defer done()
@ -439,6 +439,7 @@ func Completion(ctx context.Context, snapshot Snapshot, fh FileHandle, pos proto
return nil, nil, nil return nil, nil, nil
} }
opts := snapshot.View().Options()
c := &completer{ c := &completer{
pkg: pkg, pkg: pkg,
snapshot: snapshot, snapshot: snapshot,
@ -451,7 +452,16 @@ func Completion(ctx context.Context, snapshot Snapshot, fh FileHandle, pos proto
seen: make(map[types.Object]bool), seen: make(map[types.Object]bool),
enclosingFunc: enclosingFunction(path, rng.Start, pkg.GetTypesInfo()), enclosingFunc: enclosingFunction(path, rng.Start, pkg.GetTypesInfo()),
enclosingCompositeLiteral: enclosingCompositeLiteral(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 // default to a matcher that always matches
matcher: prefixMatcher(""), matcher: prefixMatcher(""),
methodSetCache: make(map[methodSetKey]*types.MethodSet), methodSetCache: make(map[methodSetKey]*types.MethodSet),
@ -459,7 +469,7 @@ func Completion(ctx context.Context, snapshot Snapshot, fh FileHandle, pos proto
startTime: startTime, startTime: startTime,
} }
if opts.Deep { if c.opts.deepCompletion {
// Initialize max search depth to unlimited. // Initialize max search depth to unlimited.
c.deepState.maxDepth = -1 c.deepState.maxDepth = -1
} }
@ -629,7 +639,7 @@ func (c *completer) selector(sel *ast.SelectorExpr) error {
} }
// Try unimported packages. // 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 { if err := c.unimportedMembers(id); err != nil {
return err 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() ctx, cancel := c.deepCompletionContext()
defer cancel() defer cancel()
// Suggest packages that have not been imported yet. // Suggest packages that have not been imported yet.

View File

@ -168,7 +168,7 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
snippet: snip, snippet: snip,
} }
// If the user doesn't want documentation for completion items. // If the user doesn't want documentation for completion items.
if !c.opts.Documentation { if !c.opts.documentation {
return item, nil return item, nil
} }
pos := c.snapshot.View().Session().Cache().FileSet().Position(obj.Pos()) 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 return item, nil
} }
item.Documentation = hover.Synopsis item.Documentation = hover.Synopsis
if c.opts.FullDocumentation { if c.opts.fullDocumentation {
item.Documentation = hover.FullDocumentation item.Documentation = hover.FullDocumentation
} }
return item, nil return item, nil

View File

@ -20,7 +20,7 @@ import (
// literal generates composite literal, function literal, and make() // literal generates composite literal, function literal, and make()
// completion items. // completion items.
func (c *completer) literal(literalType types.Type, imp *importInfo) { func (c *completer) literal(literalType types.Type, imp *importInfo) {
if !c.opts.Literal { if !c.opts.literal {
return return
} }
@ -213,7 +213,7 @@ func (c *completer) functionLiteral(sig *types.Signature, matchScore float64) {
// Our parameter names are guesses, so they must be placeholders // Our parameter names are guesses, so they must be placeholders
// for easy correction. If placeholders are disabled, don't // for easy correction. If placeholders are disabled, don't
// offer the completion. // offer the completion.
if !c.opts.Placeholders { if !c.opts.placeholders {
return return
} }
@ -367,7 +367,7 @@ func (c *completer) makeCall(typeName string, secondArg string, matchScore float
if secondArg != "" { if secondArg != "" {
snip.WriteText(", ") snip.WriteText(", ")
snip.WritePlaceholder(func(b *snippet.Builder) { snip.WritePlaceholder(func(b *snippet.Builder) {
if c.opts.Placeholders { if c.opts.placeholders {
b.WriteText(secondArg) b.WriteText(secondArg)
} }
}) })

View File

@ -36,7 +36,7 @@ func (c *completer) structFieldSnippet(label, detail string) *snippet.Builder {
snip.WriteText(label + ": ") snip.WriteText(label + ": ")
snip.WritePlaceholder(func(b *snippet.Builder) { snip.WritePlaceholder(func(b *snippet.Builder) {
// A placeholder snippet turns "Foo{Ba<>" into "Foo{Bar: <*int*>". // A placeholder snippet turns "Foo{Ba<>" into "Foo{Bar: <*int*>".
if c.opts.Placeholders { if c.opts.placeholders {
b.WriteText(detail) b.WriteText(detail)
} }
}) })
@ -79,7 +79,7 @@ func (c *completer) functionCallSnippet(name string, params []string) *snippet.B
snip := &snippet.Builder{} snip := &snippet.Builder{}
snip.WriteText(name + "(") snip.WriteText(name + "(")
if c.opts.Placeholders { if c.opts.placeholders {
// A placeholder snippet turns "someFun<>" into "someFunc(<*i int*>, *s string*)". // A placeholder snippet turns "someFun<>" into "someFunc(<*i int*>, *s string*)".
for i, p := range params { for i, p := range params {
if i > 0 { if i > 0 {

View File

@ -107,8 +107,8 @@ func (c *completer) shouldPrune() bool {
} }
// Check our remaining budget every 100 candidates. // Check our remaining budget every 100 candidates.
if c.opts.Budget > 0 && c.deepState.candidateCount%100 == 0 { if c.opts.budget > 0 && c.deepState.candidateCount%100 == 0 {
spent := float64(time.Since(c.startTime)) / float64(c.opts.Budget) spent := float64(time.Since(c.startTime)) / float64(c.opts.budget)
switch { switch {
case spent >= 0.90: case spent >= 0.90:

View File

@ -74,16 +74,13 @@ var (
}, },
} }
DefaultUserOptions = UserOptions{ DefaultUserOptions = UserOptions{
Env: os.Environ(), Env: os.Environ(),
HoverKind: SynopsisDocumentation, HoverKind: SynopsisDocumentation,
Completion: CompletionOptions{ LinkTarget: "pkg.go.dev",
Documentation: true, Matcher: Fuzzy,
Deep: true, DeepCompletion: true,
FuzzyMatching: true, CompletionDocumentation: true,
Literal: true, Literal: true,
Budget: 100 * time.Millisecond,
},
LinkTarget: "pkg.go.dev",
} }
DefaultHooks = Hooks{ DefaultHooks = Hooks{
ComputeEdits: myers.ComputeEdits, ComputeEdits: myers.ComputeEdits,
@ -94,7 +91,9 @@ var (
DefaultExperimentalOptions = ExperimentalOptions{ DefaultExperimentalOptions = ExperimentalOptions{
TempModfile: false, TempModfile: false,
} }
DefaultDebuggingOptions = DebuggingOptions{} DefaultDebuggingOptions = DebuggingOptions{
CompletionBudget: 100 * time.Millisecond,
}
) )
type Options struct { type Options struct {
@ -142,7 +141,37 @@ type UserOptions struct {
// LocalPrefix is used to specify goimports's -local behavior. // LocalPrefix is used to specify goimports's -local behavior.
LocalPrefix string 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 { type Hooks struct {
@ -161,26 +190,23 @@ type ExperimentalOptions struct {
type DebuggingOptions struct { type DebuggingOptions struct {
VerboseOutput bool VerboseOutput bool
}
type CompletionOptions struct { // CompletionBudget is the soft latency goal for completion requests. Most
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
// requests finish in a couple milliseconds, but in some cases deep // requests finish in a couple milliseconds, but in some cases deep
// completions can take much longer. As we use up our budget we // completions can take much longer. As we use up our budget we
// dynamically reduce the search scope to ensure we return timely // dynamically reduce the search scope to ensure we return timely
// results. Zero means unlimited. // results. Zero means unlimited.
Budget time.Duration CompletionBudget time.Duration
} }
type Matcher int
const (
Fuzzy = Matcher(iota)
CaseInsensitive
CaseSensitive
)
type HoverKind int type HoverKind int
const ( const (
@ -280,17 +306,13 @@ func (o *Options) set(name string, value interface{}) OptionResult {
o.BuildFlags = flags o.BuildFlags = flags
case "completionDocumentation": case "completionDocumentation":
result.setBool(&o.Completion.Documentation) result.setBool(&o.CompletionDocumentation)
case "usePlaceholders": case "usePlaceholders":
result.setBool(&o.Completion.Placeholders) result.setBool(&o.Placeholders)
case "deepCompletion": case "deepCompletion":
result.setBool(&o.Completion.Deep) result.setBool(&o.DeepCompletion)
case "fuzzyMatching":
result.setBool(&o.Completion.FuzzyMatching)
case "caseSensitiveCompletion":
result.setBool(&o.Completion.CaseSensitive)
case "completeUnimported": case "completeUnimported":
result.setBool(&o.Completion.Unimported) result.setBool(&o.UnimportedCompletion)
case "completionBudget": case "completionBudget":
if v, ok := result.asString(); ok { if v, ok := result.asString(); ok {
d, err := time.ParseDuration(v) 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) result.errorf("failed to parse duration %q: %v", v, err)
break 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": case "hoverKind":
hoverKind, ok := value.(string) hoverKind, ok := result.asString()
if !ok { if !ok {
result.errorf("invalid type %T for string option %q", value, name)
break break
} }
switch hoverKind { switch hoverKind {
@ -378,6 +413,14 @@ func (o *Options) set(name string, value interface{}) OptionResult {
result.State = OptionDeprecated result.State = OptionDeprecated
result.Replacement = "completeUnimported" result.Replacement = "completeUnimported"
case "fuzzyMatching":
result.State = OptionDeprecated
result.Replacement = "matcher"
case "caseSensitiveCompletion":
result.State = OptionDeprecated
result.Replacement = "matcher"
case "noIncrementalSync": case "noIncrementalSync":
result.State = OptionDeprecated result.State = OptionDeprecated

View File

@ -107,10 +107,10 @@ func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion,
for _, pos := range test.CompletionItems { for _, pos := range test.CompletionItems {
want = append(want, tests.ToProtocolCompletionItem(*items[pos])) want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
} }
prefix, list := r.callCompletion(t, src, source.CompletionOptions{ prefix, list := r.callCompletion(t, src, func(opts *source.Options) {
Documentation: true, opts.Matcher = source.Fuzzy
FuzzyMatching: true, opts.Literal = strings.Contains(string(src.URI()), "literal")
Literal: strings.Contains(string(src.URI()), "literal"), opts.DeepCompletion = false
}) })
if !strings.Contains(string(src.URI()), "builtins") { if !strings.Contains(string(src.URI()), "builtins") {
list = tests.FilterBuiltins(list) 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) { 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{ _, list := r.callCompletion(t, src, func(opts *source.Options) {
Placeholders: placeholders, opts.Placeholders = placeholders
Deep: true, opts.DeepCompletion = true
Literal: true, opts.Literal = true
}) })
got := tests.FindItem(list, *items[expected.CompletionItem]) got := tests.FindItem(list, *items[expected.CompletionItem])
want := expected.PlainSnippet 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 { for _, pos := range test.CompletionItems {
want = append(want, tests.ToProtocolCompletionItem(*items[pos])) want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
} }
_, got := r.callCompletion(t, src, source.CompletionOptions{ _, got := r.callCompletion(t, src, func(opts *source.Options) {
Unimported: true, opts.UnimportedCompletion = true
}) })
if !strings.Contains(string(src.URI()), "builtins") { if !strings.Contains(string(src.URI()), "builtins") {
got = tests.FilterBuiltins(got) 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 { for _, pos := range test.CompletionItems {
want = append(want, tests.ToProtocolCompletionItem(*items[pos])) want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
} }
prefix, list := r.callCompletion(t, src, source.CompletionOptions{ prefix, list := r.callCompletion(t, src, func(opts *source.Options) {
Deep: true, opts.DeepCompletion = true
Documentation: true, opts.Matcher = source.CaseInsensitive
}) })
if !strings.Contains(string(src.URI()), "builtins") { if !strings.Contains(string(src.URI()), "builtins") {
list = tests.FilterBuiltins(list) 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 { for _, pos := range test.CompletionItems {
want = append(want, tests.ToProtocolCompletionItem(*items[pos])) want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
} }
_, got := r.callCompletion(t, src, source.CompletionOptions{ _, got := r.callCompletion(t, src, func(opts *source.Options) {
FuzzyMatching: true, opts.DeepCompletion = true
Deep: true, opts.Matcher = source.Fuzzy
}) })
if !strings.Contains(string(src.URI()), "builtins") { if !strings.Contains(string(src.URI()), "builtins") {
got = tests.FilterBuiltins(got) 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 { for _, pos := range test.CompletionItems {
want = append(want, tests.ToProtocolCompletionItem(*items[pos])) want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
} }
_, list := r.callCompletion(t, src, source.CompletionOptions{ _, list := r.callCompletion(t, src, func(opts *source.Options) {
CaseSensitive: true, opts.Matcher = source.CaseSensitive
}) })
if !strings.Contains(string(src.URI()), "builtins") { if !strings.Contains(string(src.URI()), "builtins") {
list = tests.FilterBuiltins(list) 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 { for _, pos := range test.CompletionItems {
want = append(want, tests.ToProtocolCompletionItem(*items[pos])) want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
} }
_, got := r.callCompletion(t, src, source.CompletionOptions{ _, got := r.callCompletion(t, src, func(opts *source.Options) {
FuzzyMatching: true, opts.DeepCompletion = true
Deep: true, opts.Matcher = source.Fuzzy
Literal: true, opts.Literal = true
}) })
if msg := tests.CheckCompletionOrder(want, got, true); msg != "" { if msg := tests.CheckCompletionOrder(want, got, true); msg != "" {
t.Errorf("%s: %s", src, 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()) fh, err := r.view.Snapshot().GetFile(src.URI())
if err != nil { if err != nil {
t.Fatal(err) 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), Line: float64(src.Start().Line() - 1),
Character: float64(src.Start().Column() - 1), Character: float64(src.Start().Column() - 1),
}, options) })
if err != nil && !errors.As(err, &source.ErrIsDefinition{}) { if err != nil && !errors.As(err, &source.ErrIsDefinition{}) {
t.Fatalf("failed for %v: %v", src, err) 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. // Apply deep completion filtering.
for _, item := range list { for _, item := range list {
if item.Depth > 0 { if item.Depth > 0 {
if !options.Deep { if !modified.DeepCompletion {
continue continue
} }
if numDeepCompletionsSeen >= source.MaxDeepCompletions { if numDeepCompletionsSeen >= source.MaxDeepCompletions {

View File

@ -191,7 +191,7 @@ func DefaultOptions() source.Options {
} }
o.HoverKind = source.SynopsisDocumentation o.HoverKind = source.SynopsisDocumentation
o.InsertTextFormat = protocol.SnippetTextFormat o.InsertTextFormat = protocol.SnippetTextFormat
o.Completion.Budget = time.Minute o.CompletionBudget = time.Minute
return o return o
} }