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 {