1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:04:44 -07:00

internal/lsp: sort completions according to rank

The LSP specification doesn't have a Score field, so we must provide
sortText to the protocol in order to maintain the correct order.

Change-Id: I075849f520c21a0465dfb2060c598d8bae5f876b
Reviewed-on: https://go-review.googlesource.com/c/151237
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:
Rebecca Stambler 2018-11-21 23:51:01 -05:00
parent 04b5d21e00
commit 36a8f0a386
4 changed files with 61 additions and 23 deletions

View File

@ -13,7 +13,7 @@ import (
"golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/lsp/source"
) )
func toProtocolCompletionItems(items []source.CompletionItem, snippetsSupported, signatureHelpEnabled bool) []protocol.CompletionItem { func toProtocolCompletionItems(items []source.CompletionItem, prefix string, pos protocol.Position, snippetsSupported, signatureHelpEnabled bool) []protocol.CompletionItem {
var results []protocol.CompletionItem var results []protocol.CompletionItem
sort.Slice(items, func(i, j int) bool { sort.Slice(items, func(i, j int) bool {
return items[i].Score > items[j].Score return items[i].Score > items[j].Score
@ -22,14 +22,33 @@ func toProtocolCompletionItems(items []source.CompletionItem, snippetsSupported,
if snippetsSupported { if snippetsSupported {
insertTextFormat = protocol.SnippetTextFormat insertTextFormat = protocol.SnippetTextFormat
} }
for _, item := range items { for i, item := range items {
// Matching against the label.
if !strings.HasPrefix(item.Label, prefix) {
continue
}
insertText, triggerSignatureHelp := labelToProtocolSnippets(item.Label, item.Kind, insertTextFormat, signatureHelpEnabled) insertText, triggerSignatureHelp := labelToProtocolSnippets(item.Label, item.Kind, insertTextFormat, signatureHelpEnabled)
if prefix != "" {
insertText = insertText[len(prefix):]
}
i := protocol.CompletionItem{ i := protocol.CompletionItem{
Label: item.Label, Label: item.Label,
InsertText: insertText,
Detail: item.Detail, Detail: item.Detail,
Kind: float64(toProtocolCompletionItemKind(item.Kind)), Kind: float64(toProtocolCompletionItemKind(item.Kind)),
InsertTextFormat: insertTextFormat, InsertTextFormat: insertTextFormat,
TextEdit: &protocol.TextEdit{
NewText: insertText,
Range: protocol.Range{
Start: pos,
End: pos,
},
},
// InsertText is deprecated in favor of TextEdit.
InsertText: insertText,
// This is a hack so that the client sorts completion results in the order
// according to their score. This can be removed upon the resolution of
// https://github.com/Microsoft/language-server-protocol/issues/348.
SortText: fmt.Sprintf("%05d", i),
} }
// If we are completing a function, we should trigger signature help if possible. // If we are completing a function, we should trigger signature help if possible.
if triggerSignatureHelp && signatureHelpEnabled { if triggerSignatureHelp && signatureHelpEnabled {
@ -72,7 +91,9 @@ func labelToProtocolSnippets(label string, kind source.CompletionItemKind, inser
case source.ConstantCompletionItem: case source.ConstantCompletionItem:
// The label for constants is of the format "<identifier> = <value>". // The label for constants is of the format "<identifier> = <value>".
// We should now insert the " = <value>" part of the label. // We should now insert the " = <value>" part of the label.
return label[:strings.Index(label, " =")], false if i := strings.Index(label, " ="); i >= 0 {
return label[:i], false
}
case source.FunctionCompletionItem, source.MethodCompletionItem: case source.FunctionCompletionItem, source.MethodCompletionItem:
trimmed := label[:strings.Index(label, "(")] trimmed := label[:strings.Index(label, "(")]
params := strings.Trim(label[strings.Index(label, "("):], "()") params := strings.Trim(label[strings.Index(label, "("):], "()")

View File

@ -201,10 +201,28 @@ func (c completions) test(t *testing.T, exported *packagestest.Exported, s *serv
}, },
}, },
}) })
var got []protocol.CompletionItem
for _, item := range list.Items {
// Skip all types with no details (builtin types).
if item.Detail == "" && item.Kind == float64(protocol.TypeParameterCompletion) {
continue
}
// Skip remaining builtin types.
trimmed := item.Label
if i := strings.Index(trimmed, "("); i >= 0 {
trimmed = trimmed[:i]
}
switch trimmed {
case "append", "cap", "close", "complex", "copy", "delete",
"error", "false", "imag", "iota", "len", "make", "new",
"nil", "panic", "print", "println", "real", "recover", "true":
continue
}
got = append(got, item)
}
if err != nil { if err != nil {
t.Fatalf("completion failed for %s:%v:%v: %v", filepath.Base(src.Filename), src.Line, src.Column, err) t.Fatalf("completion failed for %s:%v:%v: %v", filepath.Base(src.Filename), src.Line, src.Column, err)
} }
got := list.Items
if diff := diffC(src, want, got); diff != "" { if diff := diffC(src, want, got); diff != "" {
t.Errorf(diff) t.Errorf(diff)
} }

View File

@ -169,13 +169,13 @@ func (s *server) Completion(ctx context.Context, params *protocol.CompletionPara
return nil, err return nil, err
} }
pos := fromProtocolPosition(tok, params.Position) pos := fromProtocolPosition(tok, params.Position)
items, err := source.Completion(ctx, f, pos) items, prefix, err := source.Completion(ctx, f, pos)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &protocol.CompletionList{ return &protocol.CompletionList{
IsIncomplete: false, IsIncomplete: false,
Items: toProtocolCompletionItems(items, s.snippetsSupported, s.signatureHelpEnabled), Items: toProtocolCompletionItems(items, prefix, params.Position, s.snippetsSupported, s.signatureHelpEnabled),
}, nil }, nil
} }

View File

@ -34,17 +34,16 @@ const (
PackageCompletionItem PackageCompletionItem
) )
func Completion(ctx context.Context, f *File, pos token.Pos) ([]CompletionItem, error) { func Completion(ctx context.Context, f *File, pos token.Pos) ([]CompletionItem, string, error) {
file, err := f.GetAST() file, err := f.GetAST()
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
pkg, err := f.GetPackage() pkg, err := f.GetPackage()
if err != nil { if err != nil {
return nil, err return nil, "", err
} }
items, _, err := completions(file, pos, pkg.Fset, pkg.Types, pkg.TypesInfo) return completions(file, pos, pkg.Fset, pkg.Types, pkg.TypesInfo)
return items, err
} }
const stdScore float64 = 1.0 const stdScore float64 = 1.0
@ -93,9 +92,6 @@ func completions(file *ast.File, pos token.Pos, fset *token.FileSet, pkg *types.
if typ != nil && matchingTypes(typ, obj.Type()) { if typ != nil && matchingTypes(typ, obj.Type()) {
weight *= 10.0 weight *= 10.0
} }
if !strings.HasPrefix(obj.Name(), prefix) {
return items
}
item := formatCompletion(obj, pkgStringer, weight, func(v *types.Var) bool { item := formatCompletion(obj, pkgStringer, weight, func(v *types.Var) bool {
return isParameter(sig, v) return isParameter(sig, v)
}) })
@ -115,7 +111,8 @@ func completions(file *ast.File, pos token.Pos, fset *token.FileSet, pkg *types.
// Is this the Sel part of a selector? // Is this the Sel part of a selector?
if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == n { if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == n {
return selector(sel, info, found) items, err = selector(sel, pos, info, found)
return items, prefix, err
} }
// reject defining identifiers // reject defining identifiers
if obj, ok := info.Defs[n]; ok { if obj, ok := info.Defs[n]; ok {
@ -137,10 +134,12 @@ func completions(file *ast.File, pos token.Pos, fset *token.FileSet, pkg *types.
// recv.‸(arg) // recv.‸(arg)
case *ast.TypeAssertExpr: case *ast.TypeAssertExpr:
// Create a fake selector expression. // Create a fake selector expression.
return selector(&ast.SelectorExpr{X: n.X}, info, found) items, err = selector(&ast.SelectorExpr{X: n.X}, pos, info, found)
return items, prefix, err
case *ast.SelectorExpr: case *ast.SelectorExpr:
return selector(n, info, found) items, err = selector(n, pos, info, found)
return items, prefix, err
default: default:
// fallback to lexical completions // fallback to lexical completions
@ -153,7 +152,7 @@ func completions(file *ast.File, pos token.Pos, fset *token.FileSet, pkg *types.
// selector finds completions for // selector finds completions for
// the specified selector expression. // the specified selector expression.
// TODO(rstambler): Set the prefix filter correctly for selectors. // TODO(rstambler): Set the prefix filter correctly for selectors.
func selector(sel *ast.SelectorExpr, info *types.Info, found finder) (items []CompletionItem, prefix string, err error) { func selector(sel *ast.SelectorExpr, pos token.Pos, info *types.Info, found finder) (items []CompletionItem, err error) {
// Is sel a qualified identifier? // Is sel a qualified identifier?
if id, ok := sel.X.(*ast.Ident); ok { if id, ok := sel.X.(*ast.Ident); ok {
if pkgname, ok := info.Uses[id].(*types.PkgName); ok { if pkgname, ok := info.Uses[id].(*types.PkgName); ok {
@ -164,14 +163,14 @@ func selector(sel *ast.SelectorExpr, info *types.Info, found finder) (items []Co
for _, name := range scope.Names() { for _, name := range scope.Names() {
items = found(scope.Lookup(name), stdScore, items) items = found(scope.Lookup(name), stdScore, items)
} }
return items, prefix, nil return items, nil
} }
} }
// Inv: sel is a true selector. // Inv: sel is a true selector.
tv, ok := info.Types[sel.X] tv, ok := info.Types[sel.X]
if !ok { if !ok {
return nil, "", fmt.Errorf("cannot resolve %s", sel.X) return nil, fmt.Errorf("cannot resolve %s", sel.X)
} }
// methods of T // methods of T
@ -193,7 +192,7 @@ func selector(sel *ast.SelectorExpr, info *types.Info, found finder) (items []Co
items = found(f, stdScore, items) items = found(f, stdScore, items)
} }
return items, prefix, nil return items, nil
} }
// lexical finds completions in the lexical environment. // lexical finds completions in the lexical environment.
@ -208,7 +207,7 @@ func lexical(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Inf
} }
scopes = append(scopes, info.Scopes[n]) scopes = append(scopes, info.Scopes[n])
} }
scopes = append(scopes, pkg.Scope()) scopes = append(scopes, pkg.Scope(), types.Universe)
// Process scopes innermost first. // Process scopes innermost first.
for i, scope := range scopes { for i, scope := range scopes {