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>
diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go
index f894142..d4012f3 100644
--- a/internal/lsp/completion.go
+++ b/internal/lsp/completion.go
@@ -13,7 +13,7 @@
"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
sort.Slice(items, func(i, j int) bool {
return items[i].Score > items[j].Score
@@ -22,14 +22,33 @@
if snippetsSupported {
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)
+ if prefix != "" {
+ insertText = insertText[len(prefix):]
+ }
i := protocol.CompletionItem{
Label: item.Label,
- InsertText: insertText,
Detail: item.Detail,
Kind: float64(toProtocolCompletionItemKind(item.Kind)),
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 triggerSignatureHelp && signatureHelpEnabled {
@@ -72,7 +91,9 @@
case source.ConstantCompletionItem:
// The label for constants is of the format "<identifier> = <value>".
// 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:
trimmed := label[:strings.Index(label, "(")]
params := strings.Trim(label[strings.Index(label, "("):], "()")
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index a4b310f..03c862f 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -201,10 +201,28 @@
},
},
})
+ 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 {
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 != "" {
t.Errorf(diff)
}
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index 691cdab..f5bdf0d 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -169,13 +169,13 @@
return nil, err
}
pos := fromProtocolPosition(tok, params.Position)
- items, err := source.Completion(ctx, f, pos)
+ items, prefix, err := source.Completion(ctx, f, pos)
if err != nil {
return nil, err
}
return &protocol.CompletionList{
IsIncomplete: false,
- Items: toProtocolCompletionItems(items, s.snippetsSupported, s.signatureHelpEnabled),
+ Items: toProtocolCompletionItems(items, prefix, params.Position, s.snippetsSupported, s.signatureHelpEnabled),
}, nil
}
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index 258594b..5f8e3d8 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -34,17 +34,16 @@
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()
if err != nil {
- return nil, err
+ return nil, "", err
}
pkg, err := f.GetPackage()
if err != nil {
- return nil, err
+ return nil, "", err
}
- items, _, err := completions(file, pos, pkg.Fset, pkg.Types, pkg.TypesInfo)
- return items, err
+ return completions(file, pos, pkg.Fset, pkg.Types, pkg.TypesInfo)
}
const stdScore float64 = 1.0
@@ -93,9 +92,6 @@
if typ != nil && matchingTypes(typ, obj.Type()) {
weight *= 10.0
}
- if !strings.HasPrefix(obj.Name(), prefix) {
- return items
- }
item := formatCompletion(obj, pkgStringer, weight, func(v *types.Var) bool {
return isParameter(sig, v)
})
@@ -115,7 +111,8 @@
// Is this the Sel part of a selector?
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
if obj, ok := info.Defs[n]; ok {
@@ -137,10 +134,12 @@
// recv.‸(arg)
case *ast.TypeAssertExpr:
// 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:
- return selector(n, info, found)
+ items, err = selector(n, pos, info, found)
+ return items, prefix, err
default:
// fallback to lexical completions
@@ -153,7 +152,7 @@
// selector finds completions for
// the specified selector expression.
// 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?
if id, ok := sel.X.(*ast.Ident); ok {
if pkgname, ok := info.Uses[id].(*types.PkgName); ok {
@@ -164,14 +163,14 @@
for _, name := range scope.Names() {
items = found(scope.Lookup(name), stdScore, items)
}
- return items, prefix, nil
+ return items, nil
}
}
// Inv: sel is a true selector.
tv, ok := info.Types[sel.X]
if !ok {
- return nil, "", fmt.Errorf("cannot resolve %s", sel.X)
+ return nil, fmt.Errorf("cannot resolve %s", sel.X)
}
// methods of T
@@ -193,7 +192,7 @@
items = found(f, stdScore, items)
}
- return items, prefix, nil
+ return items, nil
}
// lexical finds completions in the lexical environment.
@@ -208,7 +207,7 @@
}
scopes = append(scopes, info.Scopes[n])
}
- scopes = append(scopes, pkg.Scope())
+ scopes = append(scopes, pkg.Scope(), types.Universe)
// Process scopes innermost first.
for i, scope := range scopes {