1
0
mirror of https://github.com/golang/go synced 2024-10-01 07:38:32 -06:00
go/internal/lsp/source/completion_snippet.go
Muir Manders 4298585011 internal/lsp: provide deep completion candidates
Deep completion refers to searching through an object's fields and
methods for more completion candidates. For example:

func wantsInt(int) { }
var s struct { i int }
wantsInt(<>)

Will now give a candidate for "s.i" since its type matches the
expected type.

We limit to three deep completion results. In some cases there are
many useless deep completion matches. Showing too many options defeats
the purpose of "smart" completions. We also lower a completion item's
score according to its depth so that we favor shallower options. For
now we do not continue searching past function calls to limit our
search scope. In other words, we are not able to suggest results with
any chained fields/methods after the first method call.

Deep completions are behind the "useDeepCompletions" LSP config flag
for now.

Change-Id: I1b888c82e5c4b882f9718177ce07811e2bccbf22
GitHub-Last-Rev: 26522363730036e0b382a7bcd10aa1ed825f6866
GitHub-Pull-Request: golang/tools#100
Reviewed-on: https://go-review.googlesource.com/c/tools/+/177622
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2019-06-27 18:58:03 +00:00

99 lines
2.8 KiB
Go

// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"fmt"
"go/ast"
"golang.org/x/tools/internal/lsp/snippet"
)
// structFieldSnippets calculates the plain and placeholder snippets for struct literal field names.
func (c *completer) structFieldSnippets(label, detail string) (*snippet.Builder, *snippet.Builder) {
if !c.wantStructFieldCompletions() {
return nil, nil
}
// If we are in a deep completion then we can't be completing a field
// name (e.g. "Foo{f<>}" completing to "Foo{f.Bar}" should not generate
// a snippet).
if c.inDeepCompletion() {
return nil, nil
}
clInfo := c.enclosingCompositeLiteral
// If we are already in a key-value expression, we don't want a snippet.
if clInfo.kv != nil {
return nil, nil
}
plain, placeholder := &snippet.Builder{}, &snippet.Builder{}
label = fmt.Sprintf("%s: ", label)
// A plain snippet turns "Foo{Ba<>" into "Foo{Bar: <>".
plain.WriteText(label)
plain.WritePlaceholder(nil)
// A placeholder snippet turns "Foo{Ba<>" into "Foo{Bar: <*int*>".
placeholder.WriteText(label)
placeholder.WritePlaceholder(func(b *snippet.Builder) {
b.WriteText(detail)
})
// If the cursor position is on a different line from the literal's opening brace,
// we are in a multiline literal.
if c.view.Session().Cache().FileSet().Position(c.pos).Line != c.view.Session().Cache().FileSet().Position(clInfo.cl.Lbrace).Line {
plain.WriteText(",")
placeholder.WriteText(",")
}
return plain, placeholder
}
// functionCallSnippets calculates the plain and placeholder snippets for function calls.
func (c *completer) functionCallSnippets(name string, params []string) (*snippet.Builder, *snippet.Builder) {
// If we are the left side (i.e. "Fun") part of a call expression,
// we don't want a snippet since there are already parens present.
if len(c.path) > 1 {
switch n := c.path[1].(type) {
case *ast.CallExpr:
if n.Fun == c.path[0] {
return nil, nil
}
case *ast.SelectorExpr:
if len(c.path) > 2 {
if call, ok := c.path[2].(*ast.CallExpr); ok && call.Fun == c.path[1] {
return nil, nil
}
}
}
}
plain, placeholder := &snippet.Builder{}, &snippet.Builder{}
label := fmt.Sprintf("%s(", name)
// A plain snippet turns "someFun<>" into "someFunc(<>)".
plain.WriteText(label)
if len(params) > 0 {
plain.WritePlaceholder(nil)
}
plain.WriteText(")")
// A placeholder snippet turns "someFun<>" into "someFunc(<*i int*>, *s string*)".
placeholder.WriteText(label)
for i, p := range params {
if i > 0 {
placeholder.WriteText(", ")
}
placeholder.WritePlaceholder(func(b *snippet.Builder) {
b.WriteText(p)
})
}
placeholder.WriteText(")")
return plain, placeholder
}