internal/lsp: change some comments and variable names in completion code
Also, return diagnostics instead of errors from source.Diagnostics (to avoid stuck diagnostics). Change-Id: I56b067273982fd086ed74185e50eda5b72b5fba1 Reviewed-on: https://go-review.googlesource.com/c/tools/+/174400 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
parent
9d4d845e86
commit
b9fed7929f
|
@ -126,6 +126,7 @@ func (v *View) FileSet() *token.FileSet {
|
||||||
func (v *View) SetContent(ctx context.Context, uri span.URI, content []byte) error {
|
func (v *View) SetContent(ctx context.Context, uri span.URI, content []byte) error {
|
||||||
v.mu.Lock()
|
v.mu.Lock()
|
||||||
defer v.mu.Unlock()
|
defer v.mu.Unlock()
|
||||||
|
|
||||||
// Cancel all still-running previous requests, since they would be
|
// Cancel all still-running previous requests, since they would be
|
||||||
// operating on stale data.
|
// operating on stale data.
|
||||||
v.cancel()
|
v.cancel()
|
||||||
|
|
|
@ -51,25 +51,24 @@ func toProtocolCompletionItems(candidates []source.CompletionItem, prefix string
|
||||||
if !strings.HasPrefix(candidate.Label, prefix) {
|
if !strings.HasPrefix(candidate.Label, prefix) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
insertText := candidate.InsertText
|
||||||
insertText := candidate.Insert
|
|
||||||
if insertTextFormat == protocol.SnippetTextFormat {
|
if insertTextFormat == protocol.SnippetTextFormat {
|
||||||
if usePlaceholders && candidate.PlaceholderSnippet != nil {
|
if usePlaceholders && candidate.PlaceholderSnippet != nil {
|
||||||
insertText = candidate.PlaceholderSnippet.String()
|
insertText = candidate.PlaceholderSnippet.String()
|
||||||
} else if candidate.PlainSnippet != nil {
|
} else if candidate.Snippet != nil {
|
||||||
insertText = candidate.PlainSnippet.String()
|
insertText = candidate.Snippet.String()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If the user has already typed some part of the completion candidate,
|
||||||
|
// don't insert that portion of the text.
|
||||||
if strings.HasPrefix(insertText, prefix) {
|
if strings.HasPrefix(insertText, prefix) {
|
||||||
insertText = insertText[len(prefix):]
|
insertText = insertText[len(prefix):]
|
||||||
}
|
}
|
||||||
|
// Don't filter on text that might have snippets in it.
|
||||||
filterText := candidate.Insert
|
filterText := candidate.InsertText
|
||||||
if strings.HasPrefix(filterText, prefix) {
|
if strings.HasPrefix(filterText, prefix) {
|
||||||
filterText = filterText[len(prefix):]
|
filterText = filterText[len(prefix):]
|
||||||
}
|
}
|
||||||
|
|
||||||
item := protocol.CompletionItem{
|
item := protocol.CompletionItem{
|
||||||
Label: candidate.Label,
|
Label: candidate.Label,
|
||||||
Detail: candidate.Detail,
|
Detail: candidate.Detail,
|
||||||
|
|
|
@ -19,27 +19,43 @@ type CompletionItem struct {
|
||||||
// Label is the primary text the user sees for this completion item.
|
// Label is the primary text the user sees for this completion item.
|
||||||
Label string
|
Label string
|
||||||
|
|
||||||
// Detail is supplemental information to present to the user. This
|
// Detail is supplemental information to present to the user.
|
||||||
// often contains the Go type of the completion item.
|
// This often contains the type or return type of the completion item.
|
||||||
Detail string
|
Detail string
|
||||||
|
|
||||||
// Insert is the text to insert if this item is selected. Any already-typed
|
// InsertText is the text to insert if this item is selected.
|
||||||
// prefix has not been trimmed. Insert does not contain snippets.
|
// Any of the prefix that has already been typed is not trimmed.
|
||||||
Insert string
|
// The insert text does not contain snippets.
|
||||||
|
InsertText string
|
||||||
|
|
||||||
Kind CompletionItemKind
|
Kind CompletionItemKind
|
||||||
|
|
||||||
// Score is the internal relevance score. Higher is more relevant.
|
// Score is the internal relevance score.
|
||||||
|
// A higher score indicates that this completion item is more relevant.
|
||||||
Score float64
|
Score float64
|
||||||
|
|
||||||
// PlainSnippet is the LSP snippet to be inserted if not nil and snippets are
|
// Snippet is the LSP snippet for the completion item, without placeholders.
|
||||||
// enabled and placeholders are not desired. This can contain tabs stops, but
|
// The LSP specification contains details about LSP snippets.
|
||||||
// should not contain placeholder text.
|
// For example, a snippet for a function with the following signature:
|
||||||
PlainSnippet *snippet.Builder
|
//
|
||||||
|
// func foo(a, b, c int)
|
||||||
|
//
|
||||||
|
// would be:
|
||||||
|
//
|
||||||
|
// foo(${1:})
|
||||||
|
//
|
||||||
|
Snippet *snippet.Builder
|
||||||
|
|
||||||
// PlaceholderSnippet is the LSP snippet to be inserted if not nil and
|
// PlaceholderSnippet is the LSP snippet for the completion ite, containing
|
||||||
// snippets are enabled and placeholders are desired. This can contain
|
// placeholders. The LSP specification contains details about LSP snippets.
|
||||||
// placeholder text.
|
// For example, a placeholder snippet for a function with the following signature:
|
||||||
|
//
|
||||||
|
// func foo(a, b, c int)
|
||||||
|
//
|
||||||
|
// would be:
|
||||||
|
//
|
||||||
|
// foo(${1:a int}, ${2: b int}, ${3: c int})
|
||||||
|
//
|
||||||
PlaceholderSnippet *snippet.Builder
|
PlaceholderSnippet *snippet.Builder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +160,7 @@ func (c *completer) found(obj types.Object, weight float64) {
|
||||||
func Completion(ctx context.Context, f File, pos token.Pos) ([]CompletionItem, string, error) {
|
func Completion(ctx context.Context, f File, pos token.Pos) ([]CompletionItem, string, error) {
|
||||||
file := f.GetAST(ctx)
|
file := f.GetAST(ctx)
|
||||||
pkg := f.GetPackage(ctx)
|
pkg := f.GetPackage(ctx)
|
||||||
if pkg.IsIllTyped() {
|
if pkg == nil || pkg.IsIllTyped() {
|
||||||
return nil, "", fmt.Errorf("package for %s is ill typed", f.URI())
|
return nil, "", fmt.Errorf("package for %s is ill typed", f.URI())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +181,7 @@ func Completion(ctx context.Context, f File, pos token.Pos) ([]CompletionItem, s
|
||||||
return nil, "", nil
|
return nil, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cl, kv, clField := enclosingCompositeLiteral(path, pos)
|
lit, kv, inCompositeLiteralField := enclosingCompositeLiteral(path, pos)
|
||||||
c := &completer{
|
c := &completer{
|
||||||
types: pkg.GetTypes(),
|
types: pkg.GetTypes(),
|
||||||
info: pkg.GetTypesInfo(),
|
info: pkg.GetTypesInfo(),
|
||||||
|
@ -177,9 +193,9 @@ func Completion(ctx context.Context, f File, pos token.Pos) ([]CompletionItem, s
|
||||||
expectedType: expectedType(path, pos, pkg.GetTypesInfo()),
|
expectedType: expectedType(path, pos, pkg.GetTypesInfo()),
|
||||||
enclosingFunction: enclosingFunction(path, pos, pkg.GetTypesInfo()),
|
enclosingFunction: enclosingFunction(path, pos, pkg.GetTypesInfo()),
|
||||||
preferTypeNames: preferTypeNames(path, pos),
|
preferTypeNames: preferTypeNames(path, pos),
|
||||||
enclosingCompositeLiteral: cl,
|
enclosingCompositeLiteral: lit,
|
||||||
enclosingKeyValue: kv,
|
enclosingKeyValue: kv,
|
||||||
inCompositeLiteralField: clField,
|
inCompositeLiteralField: inCompositeLiteralField,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Composite literals are handled entirely separately.
|
// Composite literals are handled entirely separately.
|
||||||
|
@ -466,12 +482,12 @@ func enclosingFunction(path []ast.Node, pos token.Pos, info *types.Info) *types.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *completer) expectedCompositeLiteralType(cl *ast.CompositeLit, kv *ast.KeyValueExpr) types.Type {
|
func (c *completer) expectedCompositeLiteralType(lit *ast.CompositeLit, kv *ast.KeyValueExpr) types.Type {
|
||||||
clType, ok := c.info.Types[cl]
|
litType, ok := c.info.Types[lit]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
switch t := clType.Type.Underlying().(type) {
|
switch t := litType.Type.Underlying().(type) {
|
||||||
case *types.Slice:
|
case *types.Slice:
|
||||||
return t.Elem()
|
return t.Elem()
|
||||||
case *types.Array:
|
case *types.Array:
|
||||||
|
@ -501,14 +517,14 @@ func (c *completer) expectedCompositeLiteralType(cl *ast.CompositeLit, kv *ast.K
|
||||||
// We are in a struct literal, but not a specific key-value pair.
|
// We are in a struct literal, but not a specific key-value pair.
|
||||||
// If the struct literal doesn't have explicit field names,
|
// If the struct literal doesn't have explicit field names,
|
||||||
// we may still be able to suggest an expected type.
|
// we may still be able to suggest an expected type.
|
||||||
for _, el := range cl.Elts {
|
for _, el := range lit.Elts {
|
||||||
if _, ok := el.(*ast.KeyValueExpr); ok {
|
if _, ok := el.(*ast.KeyValueExpr); ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The order of the literal fields must match the order in the struct definition.
|
// The order of the literal fields must match the order in the struct definition.
|
||||||
// Find the element that the position belongs to and suggest that field's type.
|
// Find the element that the position belongs to and suggest that field's type.
|
||||||
if i := indexExprAtPos(c.pos, cl.Elts); i < t.NumFields() {
|
if i := indexExprAtPos(c.pos, lit.Elts); i < t.NumFields() {
|
||||||
return t.Field(i).Type()
|
return t.Field(i).Type()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,8 +35,8 @@ func (c *completer) item(obj types.Object, score float64) CompletionItem {
|
||||||
detail = ""
|
detail = ""
|
||||||
} else {
|
} else {
|
||||||
val := o.Val().ExactString()
|
val := o.Val().ExactString()
|
||||||
if !strings.Contains(val, "\\n") { // skip any multiline constants
|
if !strings.ContainsRune(val, '\n') { // skip any multiline constants
|
||||||
label += " = " + o.Val().ExactString()
|
label += " = " + val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
kind = ConstantCompletionItem
|
kind = ConstantCompletionItem
|
||||||
|
@ -46,24 +46,25 @@ func (c *completer) item(obj types.Object, score float64) CompletionItem {
|
||||||
}
|
}
|
||||||
if o.IsField() {
|
if o.IsField() {
|
||||||
kind = FieldCompletionItem
|
kind = FieldCompletionItem
|
||||||
plainSnippet, placeholderSnippet = c.structFieldSnippet(label, detail)
|
plainSnippet, placeholderSnippet = c.structFieldSnippets(label, detail)
|
||||||
} else if c.isParameter(o) {
|
} else if c.isParameter(o) {
|
||||||
kind = ParameterCompletionItem
|
kind = ParameterCompletionItem
|
||||||
} else {
|
} else {
|
||||||
kind = VariableCompletionItem
|
kind = VariableCompletionItem
|
||||||
}
|
}
|
||||||
case *types.Func:
|
case *types.Func:
|
||||||
if sig, ok := o.Type().(*types.Signature); ok {
|
sig, ok := o.Type().(*types.Signature)
|
||||||
params := formatEachParam(sig, c.qf)
|
if !ok {
|
||||||
label += formatParamParts(params)
|
break
|
||||||
detail = strings.Trim(types.TypeString(sig.Results(), c.qf), "()")
|
|
||||||
kind = FunctionCompletionItem
|
|
||||||
if sig.Recv() != nil {
|
|
||||||
kind = MethodCompletionItem
|
|
||||||
}
|
|
||||||
|
|
||||||
plainSnippet, placeholderSnippet = c.funcCallSnippet(obj.Name(), params)
|
|
||||||
}
|
}
|
||||||
|
params := formatEachParam(sig, c.qf)
|
||||||
|
label += formatParamParts(params)
|
||||||
|
detail = strings.Trim(types.TypeString(sig.Results(), c.qf), "()")
|
||||||
|
kind = FunctionCompletionItem
|
||||||
|
if sig.Recv() != nil {
|
||||||
|
kind = MethodCompletionItem
|
||||||
|
}
|
||||||
|
plainSnippet, placeholderSnippet = c.functionCallSnippets(obj.Name(), params)
|
||||||
case *types.Builtin:
|
case *types.Builtin:
|
||||||
item, ok := builtinDetails[obj.Name()]
|
item, ok := builtinDetails[obj.Name()]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -82,11 +83,11 @@ func (c *completer) item(obj types.Object, score float64) CompletionItem {
|
||||||
|
|
||||||
return CompletionItem{
|
return CompletionItem{
|
||||||
Label: label,
|
Label: label,
|
||||||
Insert: insert,
|
InsertText: insert,
|
||||||
Detail: detail,
|
Detail: detail,
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Score: score,
|
Score: score,
|
||||||
PlainSnippet: plainSnippet,
|
Snippet: plainSnippet,
|
||||||
PlaceholderSnippet: placeholderSnippet,
|
PlaceholderSnippet: placeholderSnippet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,69 +5,66 @@
|
||||||
package source
|
package source
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
|
|
||||||
"golang.org/x/tools/internal/lsp/snippet"
|
"golang.org/x/tools/internal/lsp/snippet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// structField calculates the plain and placeholder snippets for struct literal
|
// structFieldSnippets calculates the plain and placeholder snippets for struct literal field names.
|
||||||
// field names as in "Foo{Ba<>".
|
func (c *completer) structFieldSnippets(label, detail string) (*snippet.Builder, *snippet.Builder) {
|
||||||
func (c *completer) structFieldSnippet(label, detail string) (*snippet.Builder, *snippet.Builder) {
|
|
||||||
if !c.inCompositeLiteralField {
|
if !c.inCompositeLiteralField {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cl := c.enclosingCompositeLiteral
|
lit := c.enclosingCompositeLiteral
|
||||||
kv := c.enclosingKeyValue
|
kv := c.enclosingKeyValue
|
||||||
|
|
||||||
// If we aren't in a composite literal or are already in a key/value
|
// If we aren't in a composite literal or are already in a key-value expression,
|
||||||
// expression, we don't want a snippet.
|
// we don't want a snippet.
|
||||||
if cl == nil || kv != nil {
|
if lit == nil || kv != nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
// First, confirm that we are actually completing a struct field.
|
||||||
if len(cl.Elts) > 0 {
|
if len(lit.Elts) > 0 {
|
||||||
i := indexExprAtPos(c.pos, cl.Elts)
|
i := indexExprAtPos(c.pos, lit.Elts)
|
||||||
if i >= len(cl.Elts) {
|
if i >= len(lit.Elts) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
// If the expression is not an identifer, it is not a struct field name.
|
||||||
// If our expression is not an identifer, we know it isn't a
|
if _, ok := lit.Elts[i].(*ast.Ident); !ok {
|
||||||
// struct field name.
|
|
||||||
if _, ok := cl.Elts[i].(*ast.Ident); !ok {
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// It is a multi-line literal if pos is not on the same line as the literal's
|
plain, placeholder := &snippet.Builder{}, &snippet.Builder{}
|
||||||
// opening brace.
|
label = fmt.Sprintf("%s: ", label)
|
||||||
multiLine := c.fset.Position(c.pos).Line != c.fset.Position(cl.Lbrace).Line
|
|
||||||
|
|
||||||
// Plain snippet will turn "Foo{Ba<>" into "Foo{Bar: <>"
|
// A plain snippet turns "Foo{Ba<>" into "Foo{Bar: <>".
|
||||||
plain := &snippet.Builder{}
|
plain.WriteText(label)
|
||||||
plain.WriteText(label + ": ")
|
|
||||||
plain.WritePlaceholder(nil)
|
plain.WritePlaceholder(nil)
|
||||||
if multiLine {
|
|
||||||
plain.WriteText(",")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Placeholder snippet will turn "Foo{Ba<>" into "Foo{Bar: *int*"
|
// A placeholder snippet turns "Foo{Ba<>" into "Foo{Bar: <*int*>".
|
||||||
placeholder := &snippet.Builder{}
|
placeholder.WriteText(label)
|
||||||
placeholder.WriteText(label + ": ")
|
|
||||||
placeholder.WritePlaceholder(func(b *snippet.Builder) {
|
placeholder.WritePlaceholder(func(b *snippet.Builder) {
|
||||||
b.WriteText(detail)
|
b.WriteText(detail)
|
||||||
})
|
})
|
||||||
if multiLine {
|
|
||||||
|
// If the cursor position is on a different line from the literal's opening brace,
|
||||||
|
// we are in a multiline literal.
|
||||||
|
if c.fset.Position(c.pos).Line != c.fset.Position(lit.Lbrace).Line {
|
||||||
|
plain.WriteText(",")
|
||||||
placeholder.WriteText(",")
|
placeholder.WriteText(",")
|
||||||
}
|
}
|
||||||
|
|
||||||
return plain, placeholder
|
return plain, placeholder
|
||||||
}
|
}
|
||||||
|
|
||||||
// funcCall calculates the plain and placeholder snippets for function calls.
|
// functionCallSnippets calculates the plain and placeholder snippets for function calls.
|
||||||
func (c *completer) funcCallSnippet(funcName string, params []string) (*snippet.Builder, *snippet.Builder) {
|
func (c *completer) functionCallSnippets(name string, params []string) (*snippet.Builder, *snippet.Builder) {
|
||||||
for i := 1; i <= 2 && i < len(c.path); i++ {
|
for i := 1; i <= 2 && i < len(c.path); i++ {
|
||||||
call, ok := c.path[i].(*ast.CallExpr)
|
call, ok := c.path[i].(*ast.CallExpr)
|
||||||
|
|
||||||
// If we are the left side (i.e. "Fun") part of a call expression,
|
// 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.
|
// we don't want a snippet since there are already parens present.
|
||||||
if ok && call.Fun == c.path[i-1] {
|
if ok && call.Fun == c.path[i-1] {
|
||||||
|
@ -75,17 +72,18 @@ func (c *completer) funcCallSnippet(funcName string, params []string) (*snippet.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plain snippet turns "someFun<>" into "someFunc(<>)"
|
plain, placeholder := &snippet.Builder{}, &snippet.Builder{}
|
||||||
plain := &snippet.Builder{}
|
label := fmt.Sprintf("%s(", name)
|
||||||
plain.WriteText(funcName + "(")
|
|
||||||
|
// A plain snippet turns "someFun<>" into "someFunc(<>)".
|
||||||
|
plain.WriteText(label)
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
plain.WritePlaceholder(nil)
|
plain.WritePlaceholder(nil)
|
||||||
}
|
}
|
||||||
plain.WriteText(")")
|
plain.WriteText(")")
|
||||||
|
|
||||||
// Placeholder snippet turns "someFun<>" into "someFunc(*i int*, s string)"
|
// A placeholder snippet turns "someFun<>" into "someFunc(<*i int*>, *s string*)".
|
||||||
placeholder := &snippet.Builder{}
|
placeholder.WriteText(label)
|
||||||
placeholder.WriteText(funcName + "(")
|
|
||||||
for i, p := range params {
|
for i, p := range params {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
placeholder.WriteText(", ")
|
placeholder.WriteText(", ")
|
||||||
|
|
|
@ -98,12 +98,8 @@ func Diagnostics(ctx context.Context, v View, uri span.URI) (map[span.URI][]Diag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(diags) > 0 {
|
if len(diags) > 0 {
|
||||||
v.Logger().Debugf(ctx, "found parse or type-checking errors for %s, returning", uri)
|
|
||||||
return reports, nil
|
return reports, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Logger().Debugf(ctx, "running `go vet` analyses for %s", uri)
|
|
||||||
|
|
||||||
// Type checking and parsing succeeded. Run analyses.
|
// Type checking and parsing succeeded. Run analyses.
|
||||||
if err := runAnalyses(ctx, v, pkg, func(a *analysis.Analyzer, diag analysis.Diagnostic) error {
|
if err := runAnalyses(ctx, v, pkg, func(a *analysis.Analyzer, diag analysis.Diagnostic) error {
|
||||||
r := span.NewRange(v.FileSet(), diag.Pos, 0)
|
r := span.NewRange(v.FileSet(), diag.Pos, 0)
|
||||||
|
@ -124,11 +120,9 @@ func Diagnostics(ctx context.Context, v View, uri span.URI) (map[span.URI][]Diag
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return singleDiagnostic(uri, "unable to run analyses for %s: %v", uri, err), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Logger().Debugf(ctx, "completed reporting `go vet` analyses for %s", uri)
|
|
||||||
|
|
||||||
return reports, nil
|
return reports, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,8 +202,6 @@ func runAnalyses(ctx context.Context, v View, pkg Package, report func(a *analys
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Logger().Debugf(ctx, "analyses have completed for %s", pkg.GetTypes().Path())
|
|
||||||
|
|
||||||
// Report diagnostics and errors from root analyzers.
|
// Report diagnostics and errors from root analyzers.
|
||||||
for _, r := range roots {
|
for _, r := range roots {
|
||||||
for _, diag := range r.diagnostics {
|
for _, diag := range r.diagnostics {
|
||||||
|
|
Loading…
Reference in New Issue