internal/lsp: reorganize completion tests

Our completion tests check for a lot of different behaviors. It may be
easier to develop if we have separate tests for things like deep
completion and completion snippets.

Change-Id: I7f4b0c0e52670f2a6c00247199933fd1ffa0096f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/196021
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/cmd/test/cmdtest.go b/internal/lsp/cmd/test/cmdtest.go
index 3352323..d31dfe4 100644
--- a/internal/lsp/cmd/test/cmdtest.go
+++ b/internal/lsp/cmd/test/cmdtest.go
@@ -33,7 +33,27 @@
 	}
 }
 
-func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests.CompletionSnippets, items tests.CompletionItems) {
+func (r *runner) Completion(t *testing.T, data tests.Completions, items tests.CompletionItems) {
+	//TODO: add command line completions tests when it works
+}
+
+func (r *runner) CompletionSnippets(t *testing.T, data tests.CompletionSnippets, items tests.CompletionItems) {
+	//TODO: add command line completions tests when it works
+}
+
+func (r *runner) UnimportedCompletions(t *testing.T, data tests.UnimportedCompletions, items tests.CompletionItems) {
+	//TODO: add command line completions tests when it works
+}
+
+func (r *runner) DeepCompletions(t *testing.T, data tests.DeepCompletions, items tests.CompletionItems) {
+	//TODO: add command line completions tests when it works
+}
+
+func (r *runner) FuzzyCompletions(t *testing.T, data tests.FuzzyCompletions, items tests.CompletionItems) {
+	//TODO: add command line completions tests when it works
+}
+
+func (r *runner) RankCompletions(t *testing.T, data tests.RankCompletions, items tests.CompletionItems) {
 	//TODO: add command line completions tests when it works
 }
 
diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go
index d1a98f7..c176be4 100644
--- a/internal/lsp/completion.go
+++ b/internal/lsp/completion.go
@@ -47,11 +47,11 @@
 		// When using deep completions/fuzzy matching, report results as incomplete so
 		// client fetches updated completions after every key stroke.
 		IsIncomplete: options.Completion.Deep,
-		Items:        s.toProtocolCompletionItems(candidates, rng, options),
+		Items:        toProtocolCompletionItems(candidates, rng, options),
 	}, nil
 }
 
-func (s *Server) toProtocolCompletionItems(candidates []source.CompletionItem, rng protocol.Range, options source.Options) []protocol.CompletionItem {
+func toProtocolCompletionItems(candidates []source.CompletionItem, rng protocol.Range, options source.Options) []protocol.CompletionItem {
 	var (
 		items                  = make([]protocol.CompletionItem, 0, len(candidates))
 		numDeepCompletionsSeen int
diff --git a/internal/lsp/completion_test.go b/internal/lsp/completion_test.go
new file mode 100644
index 0000000..be42932
--- /dev/null
+++ b/internal/lsp/completion_test.go
@@ -0,0 +1,151 @@
+package lsp
+
+import (
+	"strings"
+	"testing"
+	"time"
+
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
+	"golang.org/x/tools/internal/lsp/tests"
+	"golang.org/x/tools/internal/span"
+)
+
+func (r *runner) Completion(t *testing.T, data tests.Completions, items tests.CompletionItems) {
+	for src, test := range data {
+		got := r.callCompletion(t, src, source.CompletionOptions{
+			Deep:          false,
+			FuzzyMatching: false,
+			Documentation: true,
+		})
+		if !strings.Contains(string(src.URI()), "builtins") {
+			got = tests.FilterBuiltins(got)
+		}
+		want := expected(t, test, items)
+		if diff := tests.DiffCompletionItems(want, got); diff != "" {
+			t.Errorf("%s: %s", src, diff)
+		}
+	}
+}
+
+func (r *runner) CompletionSnippets(t *testing.T, data tests.CompletionSnippets, items tests.CompletionItems) {
+	for _, placeholders := range []bool{true, false} {
+		for src, expected := range data {
+			list := r.callCompletion(t, src, source.CompletionOptions{
+				Placeholders:  placeholders,
+				Deep:          true,
+				Budget:        5 * time.Second,
+				FuzzyMatching: true,
+			})
+			got := tests.FindItem(list, *items[expected.CompletionItem])
+			want := expected.PlainSnippet
+			if placeholders {
+				want = expected.PlaceholderSnippet
+			}
+			if diff := tests.DiffSnippets(want, got); diff != "" {
+				t.Errorf("%s: %v", src, diff)
+			}
+		}
+	}
+}
+
+func (r *runner) UnimportedCompletions(t *testing.T, data tests.UnimportedCompletions, items tests.CompletionItems) {
+	for src, test := range data {
+		got := r.callCompletion(t, src, source.CompletionOptions{
+			Unimported: true,
+		})
+		if !strings.Contains(string(src.URI()), "builtins") {
+			got = tests.FilterBuiltins(got)
+		}
+		want := expected(t, test, items)
+		if diff := tests.DiffCompletionItems(want, got); diff != "" {
+			t.Errorf("%s: %s", src, diff)
+		}
+	}
+}
+
+func (r *runner) DeepCompletions(t *testing.T, data tests.DeepCompletions, items tests.CompletionItems) {
+	for src, test := range data {
+		got := r.callCompletion(t, src, source.CompletionOptions{
+			Deep:          true,
+			Budget:        5 * time.Second,
+			Documentation: true,
+		})
+		if !strings.Contains(string(src.URI()), "builtins") {
+			got = tests.FilterBuiltins(got)
+		}
+		want := expected(t, test, items)
+		if msg := tests.DiffCompletionItems(want, got); msg != "" {
+			t.Errorf("%s: %s", src, msg)
+		}
+	}
+}
+
+func (r *runner) FuzzyCompletions(t *testing.T, data tests.FuzzyCompletions, items tests.CompletionItems) {
+	for src, test := range data {
+		got := r.callCompletion(t, src, source.CompletionOptions{
+			FuzzyMatching: true,
+			Deep:          true,
+			Budget:        5 * time.Second,
+		})
+		if !strings.Contains(string(src.URI()), "builtins") {
+			got = tests.FilterBuiltins(got)
+		}
+		want := expected(t, test, items)
+		if msg := tests.DiffCompletionItems(want, got); msg != "" {
+			t.Errorf("%s: %s", src, msg)
+		}
+	}
+}
+
+func (r *runner) RankCompletions(t *testing.T, data tests.RankCompletions, items tests.CompletionItems) {
+	for src, test := range data {
+		got := r.callCompletion(t, src, source.CompletionOptions{
+			FuzzyMatching: true,
+			Deep:          true,
+			Budget:        5 * time.Second,
+		})
+		want := expected(t, test, items)
+		if msg := tests.CheckCompletionOrder(want, got); msg != "" {
+			t.Errorf("%s: %s", src, msg)
+		}
+	}
+}
+
+func expected(t *testing.T, test tests.Completion, items tests.CompletionItems) []protocol.CompletionItem {
+	t.Helper()
+
+	var want []protocol.CompletionItem
+	for _, pos := range test.CompletionItems {
+		item := items[pos]
+		want = append(want, tests.ToProtocolCompletionItem(*item))
+	}
+	return want
+}
+func (r *runner) callCompletion(t *testing.T, src span.Span, options source.CompletionOptions) []protocol.CompletionItem {
+	t.Helper()
+
+	view := r.server.session.ViewOf(src.URI())
+	original := view.Options()
+	modified := original
+	modified.InsertTextFormat = protocol.SnippetTextFormat
+	modified.Completion = options
+	view.SetOptions(modified)
+	defer view.SetOptions(original)
+
+	list, err := r.server.Completion(r.ctx, &protocol.CompletionParams{
+		TextDocumentPositionParams: protocol.TextDocumentPositionParams{
+			TextDocument: protocol.TextDocumentIdentifier{
+				URI: protocol.NewURI(src.URI()),
+			},
+			Position: protocol.Position{
+				Line:      float64(src.Start().Line() - 1),
+				Character: float64(src.Start().Column() - 1),
+			},
+		},
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	return list.Items
+}
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index ba56315..c133bac 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -15,7 +15,6 @@
 	"sort"
 	"strings"
 	"testing"
-	"time"
 
 	"golang.org/x/tools/go/packages/packagestest"
 	"golang.org/x/tools/internal/lsp/cache"
@@ -51,18 +50,7 @@
 
 	cache := cache.New()
 	session := cache.NewSession(ctx)
-	options := session.Options()
-	options.SupportedCodeActions = map[source.FileKind]map[protocol.CodeActionKind]bool{
-		source.Go: {
-			protocol.SourceOrganizeImports: true,
-			protocol.QuickFix:              true,
-		},
-		source.Mod: {},
-		source.Sum: {},
-	}
-	options.HoverKind = source.SynopsisDocumentation
-	// Crank this up so tests don't flake.
-	options.Completion.Budget = 5 * time.Second
+	options := tests.DefaultOptions()
 	session.SetOptions(options)
 	options.Env = data.Config.Env
 	session.NewView(ctx, viewName, span.FileURI(data.Config.Dir), options)
@@ -111,218 +99,6 @@
 	}
 }
 
-func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests.CompletionSnippets, items tests.CompletionItems) {
-	for src, test := range data {
-		view := r.server.session.ViewOf(src.URI())
-		original := view.Options()
-		modified := original
-
-		// Set this as a default.
-		modified.Completion.Documentation = true
-
-		var want []source.CompletionItem
-		for _, pos := range test.CompletionItems {
-			want = append(want, *items[pos])
-		}
-
-		modified.Completion.Deep = strings.Contains(string(src.URI()), "deepcomplete")
-		modified.Completion.FuzzyMatching = strings.Contains(string(src.URI()), "fuzzymatch")
-		modified.Completion.Unimported = strings.Contains(string(src.URI()), "unimported")
-		view.SetOptions(modified)
-
-		list := r.runCompletion(t, src)
-
-		wantBuiltins := strings.Contains(string(src.URI()), "builtins")
-		var got []protocol.CompletionItem
-		for _, item := range list.Items {
-			if !wantBuiltins && isBuiltin(item) {
-				continue
-			}
-			got = append(got, item)
-		}
-
-		switch test.Type {
-		case tests.CompletionFull:
-			if diff := diffCompletionItems(want, got); diff != "" {
-				t.Errorf("%s: %s", src, diff)
-			}
-		case tests.CompletionPartial:
-			if msg := checkCompletionOrder(want, got); msg != "" {
-				t.Errorf("%s: %s", src, msg)
-			}
-		}
-		view.SetOptions(original)
-	}
-
-	for _, usePlaceholders := range []bool{true, false} {
-
-		for src, want := range snippets {
-			view := r.server.session.ViewOf(src.URI())
-			original := view.Options()
-			modified := original
-
-			modified.InsertTextFormat = protocol.SnippetTextFormat
-			modified.Completion.Deep = strings.Contains(string(src.URI()), "deepcomplete")
-			modified.Completion.FuzzyMatching = strings.Contains(string(src.URI()), "fuzzymatch")
-			modified.Completion.Unimported = strings.Contains(string(src.URI()), "unimported")
-			modified.Completion.Placeholders = usePlaceholders
-			view.SetOptions(modified)
-
-			list := r.runCompletion(t, src)
-
-			wantItem := items[want.CompletionItem]
-			var got *protocol.CompletionItem
-			for _, item := range list.Items {
-				if item.Label == wantItem.Label {
-					got = &item
-					break
-				}
-			}
-			var expected string
-			if usePlaceholders {
-				expected = want.PlaceholderSnippet
-			} else {
-				expected = want.PlainSnippet
-			}
-
-			if expected == "" {
-				if got != nil {
-					t.Fatalf("%s:%d: expected no snippet but got %q", src.URI(), src.Start().Line(), got.TextEdit.NewText)
-				}
-			} else {
-				if got == nil {
-					t.Fatalf("%s:%d: couldn't find completion matching %q", src.URI(), src.Start().Line(), wantItem.Label)
-				}
-
-				if expected != got.TextEdit.NewText {
-					t.Errorf("%s: expected snippet %q, got %q", src, expected, got.TextEdit.NewText)
-				}
-			}
-			view.SetOptions(original)
-		}
-	}
-}
-
-func (r *runner) runCompletion(t *testing.T, src span.Span) *protocol.CompletionList {
-	t.Helper()
-	list, err := r.server.Completion(r.ctx, &protocol.CompletionParams{
-		TextDocumentPositionParams: protocol.TextDocumentPositionParams{
-			TextDocument: protocol.TextDocumentIdentifier{
-				URI: protocol.NewURI(src.URI()),
-			},
-			Position: protocol.Position{
-				Line:      float64(src.Start().Line() - 1),
-				Character: float64(src.Start().Column() - 1),
-			},
-		},
-	})
-	if err != nil {
-		t.Fatal(err)
-	}
-	return list
-}
-
-func isBuiltin(item protocol.CompletionItem) bool {
-	// If a type has no detail, it is a builtin type.
-	if item.Detail == "" && item.Kind == protocol.TypeParameterCompletion {
-		return true
-	}
-	// Remaining builtin constants, variables, interfaces, and functions.
-	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":
-		return true
-	}
-	return false
-}
-
-// diffCompletionItems prints the diff between expected and actual completion
-// test results.
-func diffCompletionItems(want []source.CompletionItem, got []protocol.CompletionItem) string {
-	if len(got) != len(want) {
-		return summarizeCompletionItems(-1, want, got, "different lengths got %v want %v", len(got), len(want))
-	}
-	for i, w := range want {
-		g := got[i]
-		if w.Label != g.Label {
-			return summarizeCompletionItems(i, want, got, "incorrect Label got %v want %v", g.Label, w.Label)
-		}
-		if w.Detail != g.Detail {
-			return summarizeCompletionItems(i, want, got, "incorrect Detail got %v want %v", g.Detail, w.Detail)
-		}
-		if w.Documentation != "" && !strings.HasPrefix(w.Documentation, "@") {
-			if w.Documentation != g.Documentation {
-				return summarizeCompletionItems(i, want, got, "incorrect Documentation got %v want %v", g.Documentation, w.Documentation)
-			}
-		}
-		if wkind := toProtocolCompletionItemKind(w.Kind); wkind != g.Kind {
-			return summarizeCompletionItems(i, want, got, "incorrect Kind got %v want %v", g.Kind, wkind)
-		}
-	}
-	return ""
-}
-
-func checkCompletionOrder(want []source.CompletionItem, got []protocol.CompletionItem) string {
-	var (
-		matchedIdxs []int
-		lastGotIdx  int
-		inOrder     = true
-	)
-	for _, w := range want {
-		var found bool
-		for i, g := range got {
-			if w.Label == g.Label && w.Detail == g.Detail && toProtocolCompletionItemKind(w.Kind) == g.Kind {
-				matchedIdxs = append(matchedIdxs, i)
-				found = true
-				if i < lastGotIdx {
-					inOrder = false
-				}
-				lastGotIdx = i
-				break
-			}
-		}
-		if !found {
-			return summarizeCompletionItems(-1, []source.CompletionItem{w}, got, "didn't find expected completion")
-		}
-	}
-
-	sort.Ints(matchedIdxs)
-	matched := make([]protocol.CompletionItem, 0, len(matchedIdxs))
-	for _, idx := range matchedIdxs {
-		matched = append(matched, got[idx])
-	}
-
-	if !inOrder {
-		return summarizeCompletionItems(-1, want, matched, "completions out of order")
-	}
-
-	return ""
-}
-
-func summarizeCompletionItems(i int, want []source.CompletionItem, got []protocol.CompletionItem, reason string, args ...interface{}) string {
-	msg := &bytes.Buffer{}
-	fmt.Fprint(msg, "completion failed")
-	if i >= 0 {
-		fmt.Fprintf(msg, " at %d", i)
-	}
-	fmt.Fprint(msg, " because of ")
-	fmt.Fprintf(msg, reason, args...)
-	fmt.Fprint(msg, ":\nexpected:\n")
-	for _, d := range want {
-		fmt.Fprintf(msg, "  %v\n", d)
-	}
-	fmt.Fprintf(msg, "got:\n")
-	for _, d := range got {
-		fmt.Fprintf(msg, "  %v\n", d)
-	}
-	return msg.String()
-}
-
 func (r *runner) FoldingRange(t *testing.T, data tests.FoldingRanges) {
 	for _, spn := range data {
 		uri := spn.URI()
@@ -909,7 +685,7 @@
 	return ""
 }
 
-func summarizeSymbols(t *testing.T, i int, want []protocol.DocumentSymbol, got []protocol.DocumentSymbol, reason string, args ...interface{}) string {
+func summarizeSymbols(t *testing.T, i int, want, got []protocol.DocumentSymbol, reason string, args ...interface{}) string {
 	msg := &bytes.Buffer{}
 	fmt.Fprint(msg, "document symbols failed")
 	if i >= 0 {
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 7e9af00..66ef61e 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -49,7 +49,7 @@
 
 	cache := cache.New()
 	session := cache.NewSession(ctx)
-	options := session.Options()
+	options := tests.DefaultOptions()
 	options.Env = data.Config.Env
 	r := &runner{
 		view: session.NewView(ctx, "source_test", span.FileURI(data.Config.Dir), options),
@@ -86,245 +86,194 @@
 	}
 }
 
-func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests.CompletionSnippets, items tests.CompletionItems) {
-	ctx := r.ctx
+func (r *runner) Completion(t *testing.T, data tests.Completions, items tests.CompletionItems) {
 	for src, test := range data {
-		var want []source.CompletionItem
+		var want []protocol.CompletionItem
 		for _, pos := range test.CompletionItems {
-			want = append(want, *items[pos])
+			want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
 		}
-		f, err := r.view.GetFile(ctx, src.URI())
-		if err != nil {
-			t.Fatalf("failed for %v: %v", src, err)
-		}
-		deepComplete := strings.Contains(string(src.URI()), "deepcomplete")
-		fuzzyMatch := strings.Contains(string(src.URI()), "fuzzymatch")
-		unimported := strings.Contains(string(src.URI()), "unimported")
-		list, surrounding, err := source.Completion(ctx, r.view, f.(source.GoFile), protocol.Position{
-			Line:      float64(src.Start().Line() - 1),
-			Character: float64(src.Start().Column() - 1),
-		}, source.CompletionOptions{
+		prefix, list := r.callCompletion(t, src, source.CompletionOptions{
 			Documentation: true,
-			Deep:          deepComplete,
-			FuzzyMatching: fuzzyMatch,
-			Unimported:    unimported,
-			// Crank this up so tests don't flake.
-			Budget: 5 * time.Second,
+			FuzzyMatching: true,
 		})
-		if err != nil {
-			t.Fatalf("failed for %v: %v", src, err)
+		if !strings.Contains(string(src.URI()), "builtins") {
+			list = tests.FilterBuiltins(list)
 		}
-		var (
-			prefix       string
-			fuzzyMatcher *fuzzy.Matcher
-		)
-		if surrounding != nil {
-			prefix = strings.ToLower(surrounding.Prefix())
-			if deepComplete && prefix != "" {
-				fuzzyMatcher = fuzzy.NewMatcher(surrounding.Prefix(), fuzzy.Symbol)
-			}
-		}
-		wantBuiltins := strings.Contains(string(src.URI()), "builtins")
-		var got []source.CompletionItem
+		var got []protocol.CompletionItem
 		for _, item := range list {
-			if !wantBuiltins && isBuiltin(item) {
+			if !strings.HasPrefix(strings.ToLower(item.Label), prefix) {
 				continue
 			}
-
-			// If deep completion is enabled, we need to use the fuzzy matcher to match
-			// the code's behavior.
-			if deepComplete {
-				if fuzzyMatcher != nil && fuzzyMatcher.Score(item.Label) < 0 {
-					continue
-				}
-			} else {
-				// We let the client do fuzzy matching, so we return all possible candidates.
-				// To simplify testing, filter results with prefixes that don't match exactly.
-				if !strings.HasPrefix(strings.ToLower(item.Label), prefix) {
-					continue
-				}
-			}
 			got = append(got, item)
 		}
-		switch test.Type {
-		case tests.CompletionFull:
-			if diff := diffCompletionItems(want, got); diff != "" {
+		if diff := tests.DiffCompletionItems(want, got); diff != "" {
+			t.Errorf("%s: %s", src, diff)
+		}
+	}
+}
+
+func (r *runner) CompletionSnippets(t *testing.T, data tests.CompletionSnippets, items tests.CompletionItems) {
+	for _, placeholders := range []bool{true, false} {
+		for src, expected := range data {
+			_, list := r.callCompletion(t, src, source.CompletionOptions{
+				Placeholders: placeholders,
+				Deep:         true,
+				Budget:       5 * time.Second,
+			})
+			got := tests.FindItem(list, *items[expected.CompletionItem])
+			want := expected.PlainSnippet
+			if placeholders {
+				want = expected.PlaceholderSnippet
+			}
+			if diff := tests.DiffSnippets(want, got); diff != "" {
 				t.Errorf("%s: %s", src, diff)
 			}
-		case tests.CompletionPartial:
-			if msg := checkCompletionOrder(want, got); msg != "" {
-				t.Errorf("%s: %s", src, msg)
-			}
-		}
-	}
-	for _, usePlaceholders := range []bool{true, false} {
-		for src, want := range snippets {
-			f, err := r.view.GetFile(ctx, src.URI())
-			if err != nil {
-				t.Fatalf("failed for %v: %v", src, err)
-			}
-
-			list, _, err := source.Completion(ctx, r.view, f.(source.GoFile), protocol.Position{
-				Line:      float64(src.Start().Line() - 1),
-				Character: float64(src.Start().Column() - 1),
-			}, source.CompletionOptions{
-				Documentation: true,
-				Deep:          strings.Contains(string(src.URI()), "deepcomplete"),
-				FuzzyMatching: strings.Contains(string(src.URI()), "fuzzymatch"),
-				Placeholders:  usePlaceholders,
-				// Crank this up so tests don't flake.
-				Budget: 5 * time.Second,
-			})
-			if err != nil {
-				t.Fatalf("failed for %v: %v", src, err)
-			}
-			wantItem := items[want.CompletionItem]
-			var got *source.CompletionItem
-			for _, item := range list {
-				if item.Label == wantItem.Label {
-					got = &item
-					break
-				}
-			}
-			expected := want.PlainSnippet
-			if usePlaceholders {
-				expected = want.PlaceholderSnippet
-			}
-			if expected == "" {
-				if got != nil {
-					t.Fatalf("%s:%d: expected no matching snippet", src.URI(), src.Start().Line())
-				}
-			} else {
-				if got == nil {
-					t.Fatalf("%s:%d: couldn't find completion matching %q", src.URI(), src.Start().Line(), wantItem.Label)
-				}
-				actual := got.Snippet()
-				if expected != actual {
-					t.Errorf("%s: expected placeholder snippet %q, got %q", src, expected, actual)
-				}
-			}
 		}
 	}
 }
 
-func isBuiltin(item source.CompletionItem) bool {
-	// If a type has no detail, it is a builtin type.
-	if item.Detail == "" && item.Kind == source.TypeCompletionItem {
-		return true
+func (r *runner) UnimportedCompletions(t *testing.T, data tests.UnimportedCompletions, items tests.CompletionItems) {
+	for src, test := range data {
+		var want []protocol.CompletionItem
+		for _, pos := range test.CompletionItems {
+			want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
+		}
+		_, got := r.callCompletion(t, src, source.CompletionOptions{
+			Unimported: true,
+		})
+		if !strings.Contains(string(src.URI()), "builtins") {
+			got = tests.FilterBuiltins(got)
+		}
+		if diff := tests.DiffCompletionItems(want, got); diff != "" {
+			t.Errorf("%s: %s", src, diff)
+		}
 	}
-	// Remaining builtin constants, variables, interfaces, and functions.
-	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":
-		return true
-	}
-	return false
 }
 
-// diffCompletionItems prints the diff between expected and actual completion
-// test results.
-func diffCompletionItems(want []source.CompletionItem, got []source.CompletionItem) string {
-	sort.SliceStable(got, func(i, j int) bool {
-		return got[i].Score > got[j].Score
-	})
-
-	// duplicate the lsp/completion logic to limit deep candidates to keep expected
-	// list short
-	var idx, seenDeepCompletions int
-	for _, item := range got {
-		if item.Depth > 0 {
-			if seenDeepCompletions >= 3 {
+func (r *runner) DeepCompletions(t *testing.T, data tests.DeepCompletions, items tests.CompletionItems) {
+	for src, test := range data {
+		var want []protocol.CompletionItem
+		for _, pos := range test.CompletionItems {
+			want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
+		}
+		prefix, list := r.callCompletion(t, src, source.CompletionOptions{
+			Deep:          true,
+			Budget:        5 * time.Second,
+			Documentation: true,
+		})
+		if !strings.Contains(string(src.URI()), "builtins") {
+			list = tests.FilterBuiltins(list)
+		}
+		fuzzyMatcher := fuzzy.NewMatcher(prefix, fuzzy.Symbol)
+		var got []protocol.CompletionItem
+		for _, item := range list {
+			if fuzzyMatcher.Score(item.Label) < 0 {
 				continue
 			}
-			seenDeepCompletions++
+			got = append(got, item)
 		}
-		got[idx] = item
-		idx++
-	}
-	got = got[:idx]
-
-	if len(got) != len(want) {
-		return summarizeCompletionItems(-1, want, got, "different lengths got %v want %v", len(got), len(want))
-	}
-	for i, w := range want {
-		g := got[i]
-		if w.Label != g.Label {
-			return summarizeCompletionItems(i, want, got, "incorrect Label got %v want %v", g.Label, w.Label)
-		}
-		if w.Detail != g.Detail {
-			return summarizeCompletionItems(i, want, got, "incorrect Detail got %v want %v", g.Detail, w.Detail)
-		}
-		if w.Documentation != "" && !strings.HasPrefix(w.Documentation, "@") {
-			if w.Documentation != g.Documentation {
-				return summarizeCompletionItems(i, want, got, "incorrect Documentation got %v want %v", g.Documentation, w.Documentation)
-			}
-		}
-		if w.Kind != g.Kind {
-			return summarizeCompletionItems(i, want, got, "incorrect Kind got %v want %v", g.Kind, w.Kind)
+		if msg := tests.DiffCompletionItems(want, got); msg != "" {
+			t.Errorf("%s: %s", src, msg)
 		}
 	}
-	return ""
 }
 
-func checkCompletionOrder(want []source.CompletionItem, got []source.CompletionItem) string {
-	var (
-		matchedIdxs []int
-		lastGotIdx  int
-		inOrder     = true
-	)
-	for _, w := range want {
-		var found bool
-		for i, g := range got {
-			if w.Label == g.Label && w.Detail == g.Detail && w.Kind == g.Kind {
-				matchedIdxs = append(matchedIdxs, i)
-				found = true
-				if i < lastGotIdx {
-					inOrder = false
-				}
-				lastGotIdx = i
-				break
+func (r *runner) FuzzyCompletions(t *testing.T, data tests.FuzzyCompletions, items tests.CompletionItems) {
+	for src, test := range data {
+		var want []protocol.CompletionItem
+		for _, pos := range test.CompletionItems {
+			want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
+		}
+		prefix, list := r.callCompletion(t, src, source.CompletionOptions{
+			FuzzyMatching: true,
+			Deep:          true,
+			Budget:        5 * time.Second,
+		})
+		if !strings.Contains(string(src.URI()), "builtins") {
+			list = tests.FilterBuiltins(list)
+		}
+		var fuzzyMatcher *fuzzy.Matcher
+		if prefix != "" {
+			fuzzyMatcher = fuzzy.NewMatcher(prefix, fuzzy.Symbol)
+		}
+		var got []protocol.CompletionItem
+		for _, item := range list {
+			if fuzzyMatcher != nil && fuzzyMatcher.Score(item.Label) < 0 {
+				continue
 			}
+			got = append(got, item)
 		}
-		if !found {
-			return summarizeCompletionItems(-1, []source.CompletionItem{w}, got, "didn't find expected completion")
+		if msg := tests.DiffCompletionItems(want, got); msg != "" {
+			t.Errorf("%s: %s", src, msg)
 		}
 	}
-
-	sort.Ints(matchedIdxs)
-	matched := make([]source.CompletionItem, 0, len(matchedIdxs))
-	for _, idx := range matchedIdxs {
-		matched = append(matched, got[idx])
-	}
-
-	if !inOrder {
-		return summarizeCompletionItems(-1, want, matched, "completions out of order")
-	}
-
-	return ""
 }
 
-func summarizeCompletionItems(i int, want []source.CompletionItem, got []source.CompletionItem, reason string, args ...interface{}) string {
-	msg := &bytes.Buffer{}
-	fmt.Fprint(msg, "completion failed")
-	if i >= 0 {
-		fmt.Fprintf(msg, " at %d", i)
+func (r *runner) RankCompletions(t *testing.T, data tests.RankCompletions, items tests.CompletionItems) {
+	for src, test := range data {
+		var want []protocol.CompletionItem
+		for _, pos := range test.CompletionItems {
+			want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
+		}
+		prefix, list := r.callCompletion(t, src, source.CompletionOptions{
+			FuzzyMatching: true,
+			Deep:          true,
+			Budget:        5 * time.Second,
+		})
+		if !strings.Contains(string(src.URI()), "builtins") {
+			list = tests.FilterBuiltins(list)
+		}
+		fuzzyMatcher := fuzzy.NewMatcher(prefix, fuzzy.Symbol)
+		var got []protocol.CompletionItem
+		for _, item := range list {
+			if fuzzyMatcher.Score(item.Label) < 0 {
+				continue
+			}
+			got = append(got, item)
+		}
+		if msg := tests.CheckCompletionOrder(want, got); msg != "" {
+			t.Errorf("%s: %s", src, msg)
+		}
 	}
-	fmt.Fprint(msg, " because of ")
-	fmt.Fprintf(msg, reason, args...)
-	fmt.Fprint(msg, ":\nexpected:\n")
-	for _, d := range want {
-		fmt.Fprintf(msg, "  %v\n", d)
+}
+
+func (r *runner) callCompletion(t *testing.T, src span.Span, options source.CompletionOptions) (string, []protocol.CompletionItem) {
+	f, err := r.view.GetFile(r.ctx, src.URI())
+	if err != nil {
+		t.Fatal(err)
 	}
-	fmt.Fprintf(msg, "got:\n")
-	for _, d := range got {
-		fmt.Fprintf(msg, "  %v\n", d)
+	list, surrounding, err := source.Completion(r.ctx, r.view, f.(source.GoFile), protocol.Position{
+		Line:      float64(src.Start().Line() - 1),
+		Character: float64(src.Start().Column() - 1),
+	}, options)
+	if err != nil {
+		t.Fatalf("failed for %v: %v", src, err)
 	}
-	return msg.String()
+	var prefix string
+	if surrounding != nil {
+		prefix = strings.ToLower(surrounding.Prefix())
+	}
+	// TODO(rstambler): In testing this out, I noticed that scores are equal,
+	// even when they shouldn't be. This needs more investigation.
+	sort.SliceStable(list, func(i, j int) bool {
+		return list[i].Score > list[j].Score
+	})
+	var numDeepCompletionsSeen int
+	var items []source.CompletionItem
+	// Apply deep completion filtering.
+	for _, item := range list {
+		if item.Depth > 0 {
+			if !options.Deep {
+				continue
+			}
+			if numDeepCompletionsSeen >= source.MaxDeepCompletions {
+				continue
+			}
+			numDeepCompletionsSeen++
+		}
+		items = append(items, item)
+	}
+	return prefix, tests.ToProtocolCompletionItems(items)
 }
 
 func (r *runner) FoldingRange(t *testing.T, data tests.FoldingRanges) {
diff --git a/internal/lsp/testdata/deepcomplete/deep_complete.go b/internal/lsp/testdata/deep/deep.go
similarity index 82%
rename from internal/lsp/testdata/deepcomplete/deep_complete.go
rename to internal/lsp/testdata/deep/deep.go
index 61b8cd1..f6bcbd7 100644
--- a/internal/lsp/testdata/deepcomplete/deep_complete.go
+++ b/internal/lsp/testdata/deep/deep.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package deepcomplete
+package deep
 
 import "context"
 
@@ -18,7 +18,7 @@
 func _() {
 	var a deepA   //@item(deepAVar, "a", "deepA", "var")
 	a.b           //@item(deepABField, "a.b", "deepB", "field")
-	wantsDeepB(a) //@complete(")", deepABField, deepAVar)
+	wantsDeepB(a) //@deep(")", deepABField, deepAVar)
 
 	deepA{a} //@snippet("}", deepABField, "a.b", "a.b")
 }
@@ -29,7 +29,7 @@
 	context.Background() //@item(ctxBackground, "context.Background", "func() context.Context", "func", "Background returns a non-nil, empty Context.")
 	context.TODO()       //@item(ctxTODO, "context.TODO", "func() context.Context", "func", "TODO returns a non-nil, empty Context.")
 
-	wantsContext(c) //@completePartial(")", ctxBackground, ctxTODO)
+	wantsContext(c) //@rank(")", ctxBackground, ctxTODO)
 }
 
 func _() {
@@ -39,7 +39,7 @@
 	}
 	var circle deepCircle   //@item(deepCircle, "circle", "deepCircle", "var")
 	circle.deepCircle       //@item(deepCircleField, "circle.deepCircle", "*deepCircle", "field", "deepCircle is circular.")
-	var _ deepCircle = circ //@complete(" //", deepCircle, deepCircleField)
+	var _ deepCircle = circ //@deep(" //", deepCircle, deepCircleField)
 }
 
 func _() {
@@ -57,7 +57,7 @@
 	var a deepEmbedA //@item(deepEmbedA, "a", "deepEmbedA", "var")
 	a.deepEmbedB     //@item(deepEmbedB, "a.deepEmbedB", "deepEmbedB", "field")
 	a.deepEmbedC     //@item(deepEmbedC, "a.deepEmbedC", "deepEmbedC", "field")
-	wantsC(a)        //@complete(")", deepEmbedC, deepEmbedA, deepEmbedB)
+	wantsC(a)        //@deep(")", deepEmbedC, deepEmbedA, deepEmbedB)
 }
 
 func _() {
@@ -67,7 +67,7 @@
 	}
 
 	nested{
-		a: 123, //@complete(" //", deepNestedField)
+		a: 123, //@deep(" //", deepNestedField)
 	}
 }
 
@@ -86,5 +86,5 @@
 
 	// "a.d" should be ranked above the deeper "a.b.c"
 	var i int
-	i = a //@complete(" //", deepAD, deepABC, deepA, deepAB)
+	i = a //@deep(" //", deepAD, deepABC, deepA, deepAB)
 }
diff --git a/internal/lsp/testdata/deepcomplete/fuzzymatch/deep_fuzzy.go b/internal/lsp/testdata/fuzzy/fuzzy.go
similarity index 70%
rename from internal/lsp/testdata/deepcomplete/fuzzymatch/deep_fuzzy.go
rename to internal/lsp/testdata/fuzzy/fuzzy.go
index 747f966..73268f5 100644
--- a/internal/lsp/testdata/deepcomplete/fuzzymatch/deep_fuzzy.go
+++ b/internal/lsp/testdata/fuzzy/fuzzy.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package fuzzymatch
+package fuzzy
 
 func _() {
 	var a struct {
@@ -13,13 +13,13 @@
 	a.fabar  //@item(fuzzFabarField, "a.fabar", "int", "field")
 	a.fooBar //@item(fuzzFooBarField, "a.fooBar", "string", "field")
 
-	afa //@complete(" //", fuzzFabarField, fuzzFooBarField)
-	afb //@complete(" //", fuzzFooBarField, fuzzFabarField)
+	afa //@fuzzy(" //", fuzzFabarField, fuzzFooBarField)
+	afb //@fuzzy(" //", fuzzFooBarField, fuzzFabarField)
 
-	fab //@complete(" //", fuzzFabarField)
+	fab //@fuzzy(" //", fuzzFabarField)
 
 	var myString string
-	myString = af //@complete(" //", fuzzFooBarField, fuzzFabarField)
+	myString = af //@fuzzy(" //", fuzzFooBarField, fuzzFabarField)
 
 	var b struct {
 		c struct {
@@ -40,9 +40,9 @@
 	b.c.d.e.abc //@item(fuzzABCstring, "b.c.d.e.abc", "string", "field")
 
 	// in depth order by default
-	abc //@complete(" //", fuzzABCInt, fuzzABCbool, fuzzABCfloat)
+	abc //@fuzzy(" //", fuzzABCInt, fuzzABCbool, fuzzABCfloat)
 
 	// deep candidate that matches expected type should still ranked first
 	var s string
-	s = abc //@complete(" //", fuzzABCstring, fuzzABCInt, fuzzABCbool)
+	s = abc //@fuzzy(" //", fuzzABCstring, fuzzABCInt, fuzzABCbool)
 }
diff --git a/internal/lsp/testdata/snippets/literal_snippets.go b/internal/lsp/testdata/snippets/literal_snippets.go
index f2c9472..92fe6a3 100644
--- a/internal/lsp/testdata/snippets/literal_snippets.go
+++ b/internal/lsp/testdata/snippets/literal_snippets.go
@@ -103,7 +103,7 @@
 }
 
 func _() {
-	"func(...) {}" //@item(litFunc, "func(...) {}", "", "var")
+	_ = "func(...) {}" //@item(litFunc, "func(...) {}", "", "var")
 
 	sort.Slice(nil, f) //@snippet(")", litFunc, "func(i, j int) bool {$0\\}", "func(i, j int) bool {$0\\}")
 
diff --git a/internal/lsp/testdata/unimported/mkunimported.go b/internal/lsp/testdata/unimported/mkunimported.go
index d4210b0..fc1751c 100644
--- a/internal/lsp/testdata/unimported/mkunimported.go
+++ b/internal/lsp/testdata/unimported/mkunimported.go
@@ -91,7 +91,7 @@
 		marker := strings.ReplaceAll(path, "/", "slash")
 		markers = append(markers, marker)
 	}
-	outf("	//@complete(\"\", %s)\n", strings.Join(markers, ", "))
+	outf("	//@unimported(\"\", %s)\n", strings.Join(markers, ", "))
 	outf("}\n")
 	outf("// Create markers for unimported std lib packages. Only for use by this test.\n")
 
diff --git a/internal/lsp/testdata/unimported/unimported.go b/internal/lsp/testdata/unimported/unimported.go
index 5166d85..6529943 100644
--- a/internal/lsp/testdata/unimported/unimported.go
+++ b/internal/lsp/testdata/unimported/unimported.go
@@ -3,7 +3,7 @@
 package unimported
 
 func _() {
-	//@complete("", archiveslashtar, archiveslashzip, bufio, bytes, compressslashbzip2, compressslashflate, compressslashgzip, compressslashlzw, compressslashzlib, containerslashheap, containerslashlist, containerslashring, context, crypto, cryptoslashaes, cryptoslashcipher, cryptoslashdes, cryptoslashdsa, cryptoslashecdsa, cryptoslashed25519, cryptoslashelliptic, cryptoslashhmac, cryptoslashmd5, cryptoslashrand, cryptoslashrc4, cryptoslashrsa, cryptoslashsha1, cryptoslashsha256, cryptoslashsha512, cryptoslashsubtle, cryptoslashtls, cryptoslashx509, cryptoslashx509slashpkix, databaseslashsql, databaseslashsqlslashdriver, debugslashdwarf, debugslashelf, debugslashgosym, debugslashmacho, debugslashpe, debugslashplan9obj, encoding, encodingslashascii85, encodingslashasn1, encodingslashbase32, encodingslashbase64, encodingslashbinary, encodingslashcsv, encodingslashgob, encodingslashhex, encodingslashjson, encodingslashpem, encodingslashxml, errors, expvar, flag, fmt, goslashast, goslashbuild, goslashconstant, goslashdoc, goslashformat, goslashimporter, goslashparser, goslashprinter, goslashscanner, goslashtoken, goslashtypes, hash, hashslashadler32, hashslashcrc32, hashslashcrc64, hashslashfnv, html, htmlslashtemplate, image, imageslashcolor, imageslashcolorslashpalette, imageslashdraw, imageslashgif, imageslashjpeg, imageslashpng, indexslashsuffixarray, io, ioslashioutil, log, logslashsyslog, math, mathslashbig, mathslashbits, mathslashcmplx, mathslashrand, mime, mimeslashmultipart, mimeslashquotedprintable, net, netslashhttp, netslashhttpslashcgi, netslashhttpslashcookiejar, netslashhttpslashfcgi, netslashhttpslashhttptest, netslashhttpslashhttptrace, netslashhttpslashhttputil, netslashhttpslashpprof, netslashmail, netslashrpc, netslashrpcslashjsonrpc, netslashsmtp, netslashtextproto, netslashurl, os, osslashexec, osslashsignal, osslashuser, path, pathslashfilepath, plugin, reflect, regexp, regexpslashsyntax, runtime, runtimeslashdebug, runtimeslashpprof, runtimeslashtrace, sort, strconv, strings, sync, syncslashatomic, syscall, syscallslashjs, testing, testingslashiotest, testingslashquick, textslashscanner, textslashtabwriter, textslashtemplate, textslashtemplateslashparse, time, unicode, unicodeslashutf16, unicodeslashutf8, unsafe)
+	//@unimported("", archiveslashtar, archiveslashzip, bufio, bytes, compressslashbzip2, compressslashflate, compressslashgzip, compressslashlzw, compressslashzlib, containerslashheap, containerslashlist, containerslashring, context, crypto, cryptoslashaes, cryptoslashcipher, cryptoslashdes, cryptoslashdsa, cryptoslashecdsa, cryptoslashed25519, cryptoslashelliptic, cryptoslashhmac, cryptoslashmd5, cryptoslashrand, cryptoslashrc4, cryptoslashrsa, cryptoslashsha1, cryptoslashsha256, cryptoslashsha512, cryptoslashsubtle, cryptoslashtls, cryptoslashx509, cryptoslashx509slashpkix, databaseslashsql, databaseslashsqlslashdriver, debugslashdwarf, debugslashelf, debugslashgosym, debugslashmacho, debugslashpe, debugslashplan9obj, encoding, encodingslashascii85, encodingslashasn1, encodingslashbase32, encodingslashbase64, encodingslashbinary, encodingslashcsv, encodingslashgob, encodingslashhex, encodingslashjson, encodingslashpem, encodingslashxml, errors, expvar, flag, fmt, goslashast, goslashbuild, goslashconstant, goslashdoc, goslashformat, goslashimporter, goslashparser, goslashprinter, goslashscanner, goslashtoken, goslashtypes, hash, hashslashadler32, hashslashcrc32, hashslashcrc64, hashslashfnv, html, htmlslashtemplate, image, imageslashcolor, imageslashcolorslashpalette, imageslashdraw, imageslashgif, imageslashjpeg, imageslashpng, indexslashsuffixarray, io, ioslashioutil, log, logslashsyslog, math, mathslashbig, mathslashbits, mathslashcmplx, mathslashrand, mime, mimeslashmultipart, mimeslashquotedprintable, net, netslashhttp, netslashhttpslashcgi, netslashhttpslashcookiejar, netslashhttpslashfcgi, netslashhttpslashhttptest, netslashhttpslashhttptrace, netslashhttpslashhttputil, netslashhttpslashpprof, netslashmail, netslashrpc, netslashrpcslashjsonrpc, netslashsmtp, netslashtextproto, netslashurl, os, osslashexec, osslashsignal, osslashuser, path, pathslashfilepath, plugin, reflect, regexp, regexpslashsyntax, runtime, runtimeslashdebug, runtimeslashpprof, runtimeslashtrace, sort, strconv, strings, sync, syncslashatomic, syscall, syscallslashjs, testing, testingslashiotest, testingslashquick, textslashscanner, textslashtabwriter, textslashtemplate, textslashtemplateslashparse, time, unicode, unicodeslashutf16, unicodeslashutf8, unsafe)
 }
 
 // Create markers for unimported std lib packages. Only for use by this test.
diff --git a/internal/lsp/tests/completion.go b/internal/lsp/tests/completion.go
new file mode 100644
index 0000000..d96b44d
--- /dev/null
+++ b/internal/lsp/tests/completion.go
@@ -0,0 +1,193 @@
+package tests
+
+import (
+	"bytes"
+	"fmt"
+	"sort"
+	"strings"
+
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
+)
+
+func ToProtocolCompletionItems(items []source.CompletionItem) []protocol.CompletionItem {
+	var result []protocol.CompletionItem
+	for _, item := range items {
+		result = append(result, ToProtocolCompletionItem(item))
+	}
+	return result
+}
+
+func ToProtocolCompletionItem(item source.CompletionItem) protocol.CompletionItem {
+	return protocol.CompletionItem{
+		Label:         item.Label,
+		Kind:          toProtocolCompletionItemKind(item.Kind),
+		Detail:        item.Detail,
+		Documentation: item.Documentation,
+		InsertText:    item.InsertText,
+		TextEdit: &protocol.TextEdit{
+			NewText: item.Snippet(),
+		},
+	}
+}
+
+func toProtocolCompletionItemKind(kind source.CompletionItemKind) protocol.CompletionItemKind {
+	switch kind {
+	case source.InterfaceCompletionItem:
+		return protocol.InterfaceCompletion
+	case source.StructCompletionItem:
+		return protocol.StructCompletion
+	case source.TypeCompletionItem:
+		return protocol.TypeParameterCompletion // ??
+	case source.ConstantCompletionItem:
+		return protocol.ConstantCompletion
+	case source.FieldCompletionItem:
+		return protocol.FieldCompletion
+	case source.ParameterCompletionItem, source.VariableCompletionItem:
+		return protocol.VariableCompletion
+	case source.FunctionCompletionItem:
+		return protocol.FunctionCompletion
+	case source.MethodCompletionItem:
+		return protocol.MethodCompletion
+	case source.PackageCompletionItem:
+		return protocol.ModuleCompletion // ??
+	default:
+		return protocol.TextCompletion
+	}
+}
+
+func FilterBuiltins(items []protocol.CompletionItem) []protocol.CompletionItem {
+	var got []protocol.CompletionItem
+	for _, item := range items {
+		if isBuiltin(item.Label, item.Detail, item.Kind) {
+			continue
+		}
+		got = append(got, item)
+	}
+	return got
+}
+
+func isBuiltin(label, detail string, kind protocol.CompletionItemKind) bool {
+	if detail == "" && kind == protocol.TypeParameterCompletion {
+		return true
+	}
+	// Remaining builtin constants, variables, interfaces, and functions.
+	trimmed := 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":
+		return true
+	}
+	return false
+}
+
+func CheckCompletionOrder(want, got []protocol.CompletionItem) string {
+	var (
+		matchedIdxs []int
+		lastGotIdx  int
+		inOrder     = true
+	)
+	for _, w := range want {
+		var found bool
+		for i, g := range got {
+			if w.Label == g.Label && w.Detail == g.Detail && w.Kind == g.Kind {
+				matchedIdxs = append(matchedIdxs, i)
+				found = true
+				if i < lastGotIdx {
+					inOrder = false
+				}
+				lastGotIdx = i
+				break
+			}
+		}
+		if !found {
+			return summarizeCompletionItems(-1, []protocol.CompletionItem{w}, got, "didn't find expected completion")
+		}
+	}
+
+	sort.Ints(matchedIdxs)
+	matched := make([]protocol.CompletionItem, 0, len(matchedIdxs))
+	for _, idx := range matchedIdxs {
+		matched = append(matched, got[idx])
+	}
+
+	if !inOrder {
+		return summarizeCompletionItems(-1, want, matched, "completions out of order")
+	}
+
+	return ""
+}
+
+func DiffSnippets(want string, got *protocol.CompletionItem) string {
+	if want == "" {
+		if got != nil {
+			return fmt.Sprintf("expected no snippet but got %s", got.TextEdit.NewText)
+		}
+	} else {
+		if got == nil {
+			return fmt.Sprintf("couldn't find completion matching %q", want)
+		}
+		if want != got.TextEdit.NewText {
+			return fmt.Sprintf("expected snippet %q, got %q", want, got.TextEdit.NewText)
+		}
+	}
+	return ""
+}
+
+func FindItem(list []protocol.CompletionItem, want source.CompletionItem) *protocol.CompletionItem {
+	for _, item := range list {
+		if item.Label == want.Label {
+			return &item
+		}
+	}
+	return nil
+}
+
+// DiffCompletionItems prints the diff between expected and actual completion
+// test results.
+func DiffCompletionItems(want, got []protocol.CompletionItem) string {
+	if len(got) != len(want) {
+		return summarizeCompletionItems(-1, want, got, "different lengths got %v want %v", len(got), len(want))
+	}
+	for i, w := range want {
+		g := got[i]
+		if w.Label != g.Label {
+			return summarizeCompletionItems(i, want, got, "incorrect Label got %v want %v", g.Label, w.Label)
+		}
+		if w.Detail != g.Detail {
+			return summarizeCompletionItems(i, want, got, "incorrect Detail got %v want %v", g.Detail, w.Detail)
+		}
+		if w.Documentation != "" && !strings.HasPrefix(w.Documentation, "@") {
+			if w.Documentation != g.Documentation {
+				return summarizeCompletionItems(i, want, got, "incorrect Documentation got %v want %v", g.Documentation, w.Documentation)
+			}
+		}
+		if w.Kind != g.Kind {
+			return summarizeCompletionItems(i, want, got, "incorrect Kind got %v want %v", g.Kind, w.Kind)
+		}
+	}
+	return ""
+}
+
+func summarizeCompletionItems(i int, want, got []protocol.CompletionItem, reason string, args ...interface{}) string {
+	msg := &bytes.Buffer{}
+	fmt.Fprint(msg, "completion failed")
+	if i >= 0 {
+		fmt.Fprintf(msg, " at %d", i)
+	}
+	fmt.Fprint(msg, " because of ")
+	fmt.Fprintf(msg, reason, args...)
+	fmt.Fprint(msg, ":\nexpected:\n")
+	for _, d := range want {
+		fmt.Fprintf(msg, "  %v\n", d)
+	}
+	fmt.Fprintf(msg, "got:\n")
+	for _, d := range got {
+		fmt.Fprintf(msg, "  %v\n", d)
+	}
+	return msg.String()
+}
diff --git a/internal/lsp/tests/diagnostics.go b/internal/lsp/tests/diagnostics.go
new file mode 100644
index 0000000..b4bef05
--- /dev/null
+++ b/internal/lsp/tests/diagnostics.go
@@ -0,0 +1,91 @@
+package tests
+
+import (
+	"bytes"
+	"fmt"
+	"sort"
+	"strings"
+
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
+	"golang.org/x/tools/internal/span"
+)
+
+// DiffDiagnostics prints the diff between expected and actual diagnostics test
+// results.
+func DiffDiagnostics(uri span.URI, want, got []source.Diagnostic) string {
+	sortDiagnostics(want)
+	sortDiagnostics(got)
+
+	if len(got) != len(want) {
+		return summarizeDiagnostics(-1, want, got, "different lengths got %v want %v", len(got), len(want))
+	}
+	for i, w := range want {
+		g := got[i]
+		if w.Message != g.Message {
+			return summarizeDiagnostics(i, want, got, "incorrect Message got %v want %v", g.Message, w.Message)
+		}
+		if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
+			return summarizeDiagnostics(i, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
+		}
+		// Special case for diagnostics on parse errors.
+		if strings.Contains(string(uri), "noparse") {
+			if protocol.ComparePosition(g.Range.Start, g.Range.End) != 0 || protocol.ComparePosition(w.Range.Start, g.Range.End) != 0 {
+				return summarizeDiagnostics(i, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.Start)
+			}
+		} else if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the diagnostic returns a zero-length range.
+			if protocol.ComparePosition(w.Range.End, g.Range.End) != 0 {
+				return summarizeDiagnostics(i, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
+			}
+		}
+		if w.Severity != g.Severity {
+			return summarizeDiagnostics(i, want, got, "incorrect Severity got %v want %v", g.Severity, w.Severity)
+		}
+		if w.Source != g.Source {
+			return summarizeDiagnostics(i, want, got, "incorrect Source got %v want %v", g.Source, w.Source)
+		}
+	}
+	return ""
+}
+
+func sortDiagnostics(d []source.Diagnostic) {
+	sort.Slice(d, func(i int, j int) bool {
+		return compareDiagnostic(d[i], d[j]) < 0
+	})
+}
+
+func compareDiagnostic(a, b source.Diagnostic) int {
+	if r := span.CompareURI(a.URI, b.URI); r != 0 {
+		return r
+	}
+	if r := protocol.CompareRange(a.Range, b.Range); r != 0 {
+		return r
+	}
+	if a.Message < b.Message {
+		return -1
+	}
+	if a.Message == b.Message {
+		return 0
+	} else {
+		return 1
+	}
+}
+
+func summarizeDiagnostics(i int, want []source.Diagnostic, got []source.Diagnostic, reason string, args ...interface{}) string {
+	msg := &bytes.Buffer{}
+	fmt.Fprint(msg, "diagnostics failed")
+	if i >= 0 {
+		fmt.Fprintf(msg, " at %d", i)
+	}
+	fmt.Fprint(msg, " because of ")
+	fmt.Fprintf(msg, reason, args...)
+	fmt.Fprint(msg, ":\nexpected:\n")
+	for _, d := range want {
+		fmt.Fprintf(msg, "  %s:%v: %s\n", d.URI, d.Range, d.Message)
+	}
+	fmt.Fprintf(msg, "got:\n")
+	for _, d := range got {
+		fmt.Fprintf(msg, "  %s:%v: %s\n", d.URI, d.Range, d.Message)
+	}
+	return msg.String()
+}
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index 85e54fe..87cd807 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -2,13 +2,12 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+// Package tests exports functionality to be used across a variety of gopls tests.
 package tests
 
 import (
-	"bytes"
 	"context"
 	"flag"
-	"fmt"
 	"go/ast"
 	"go/token"
 	"io/ioutil"
@@ -30,22 +29,26 @@
 // We hardcode the expected number of test cases to ensure that all tests
 // are being executed. If a test is added, this number must be changed.
 const (
-	ExpectedCompletionsCount       = 165
-	ExpectedCompletionSnippetCount = 35
-	ExpectedDiagnosticsCount       = 21
-	ExpectedFormatCount            = 6
-	ExpectedImportCount            = 2
-	ExpectedSuggestedFixCount      = 1
-	ExpectedDefinitionsCount       = 39
-	ExpectedTypeDefinitionsCount   = 2
-	ExpectedFoldingRangesCount     = 2
-	ExpectedHighlightsCount        = 2
-	ExpectedReferencesCount        = 6
-	ExpectedRenamesCount           = 20
-	ExpectedPrepareRenamesCount    = 8
-	ExpectedSymbolsCount           = 1
-	ExpectedSignaturesCount        = 21
-	ExpectedLinksCount             = 4
+	ExpectedCompletionsCount           = 152
+	ExpectedCompletionSnippetCount     = 35
+	ExpectedUnimportedCompletionsCount = 1
+	ExpectedDeepCompletionsCount       = 5
+	ExpectedFuzzyCompletionsCount      = 6
+	ExpectedRankedCompletionsCount     = 1
+	ExpectedDiagnosticsCount           = 21
+	ExpectedFormatCount                = 6
+	ExpectedImportCount                = 2
+	ExpectedSuggestedFixCount          = 1
+	ExpectedDefinitionsCount           = 39
+	ExpectedTypeDefinitionsCount       = 2
+	ExpectedFoldingRangesCount         = 2
+	ExpectedHighlightsCount            = 2
+	ExpectedReferencesCount            = 6
+	ExpectedRenamesCount               = 20
+	ExpectedPrepareRenamesCount        = 8
+	ExpectedSymbolsCount               = 1
+	ExpectedSignaturesCount            = 21
+	ExpectedLinksCount                 = 4
 )
 
 const (
@@ -61,6 +64,10 @@
 type CompletionItems map[token.Pos]*source.CompletionItem
 type Completions map[span.Span]Completion
 type CompletionSnippets map[span.Span]CompletionSnippet
+type UnimportedCompletions map[span.Span]Completion
+type DeepCompletions map[span.Span]Completion
+type FuzzyCompletions map[span.Span]Completion
+type RankCompletions map[span.Span]Completion
 type FoldingRanges []span.Span
 type Formats []span.Span
 type Imports []span.Span
@@ -76,25 +83,29 @@
 type Links map[span.URI][]Link
 
 type Data struct {
-	Config             packages.Config
-	Exported           *packagestest.Exported
-	Diagnostics        Diagnostics
-	CompletionItems    CompletionItems
-	Completions        Completions
-	CompletionSnippets CompletionSnippets
-	FoldingRanges      FoldingRanges
-	Formats            Formats
-	Imports            Imports
-	SuggestedFixes     SuggestedFixes
-	Definitions        Definitions
-	Highlights         Highlights
-	References         References
-	Renames            Renames
-	PrepareRenames     PrepareRenames
-	Symbols            Symbols
-	symbolsChildren    SymbolsChildren
-	Signatures         Signatures
-	Links              Links
+	Config                packages.Config
+	Exported              *packagestest.Exported
+	Diagnostics           Diagnostics
+	CompletionItems       CompletionItems
+	Completions           Completions
+	CompletionSnippets    CompletionSnippets
+	UnimportedCompletions UnimportedCompletions
+	DeepCompletions       DeepCompletions
+	FuzzyCompletions      FuzzyCompletions
+	RankCompletions       RankCompletions
+	FoldingRanges         FoldingRanges
+	Formats               Formats
+	Imports               Imports
+	SuggestedFixes        SuggestedFixes
+	Definitions           Definitions
+	Highlights            Highlights
+	References            References
+	Renames               Renames
+	PrepareRenames        PrepareRenames
+	Symbols               Symbols
+	symbolsChildren       SymbolsChildren
+	Signatures            Signatures
+	Links                 Links
 
 	t         testing.TB
 	fragments map[string]string
@@ -107,7 +118,12 @@
 
 type Tests interface {
 	Diagnostics(*testing.T, Diagnostics)
-	Completion(*testing.T, Completions, CompletionSnippets, CompletionItems)
+	Completion(*testing.T, Completions, CompletionItems)
+	CompletionSnippets(*testing.T, CompletionSnippets, CompletionItems)
+	UnimportedCompletions(*testing.T, UnimportedCompletions, CompletionItems)
+	DeepCompletions(*testing.T, DeepCompletions, CompletionItems)
+	FuzzyCompletions(*testing.T, FuzzyCompletions, CompletionItems)
+	RankCompletions(*testing.T, RankCompletions, CompletionItems)
 	FoldingRange(*testing.T, FoldingRanges)
 	Format(*testing.T, Formats)
 	Import(*testing.T, Imports)
@@ -132,16 +148,24 @@
 type CompletionTestType int
 
 const (
-	// Full means candidates in test must match full list of candidates.
-	CompletionFull CompletionTestType = iota
+	// Default runs the standard completion tests.
+	CompletionDefault = CompletionTestType(iota)
 
-	// Partial means candidates in test must be valid and in the right relative order.
-	CompletionPartial
+	// Unimported tests the autocompletion of unimported packages.
+	CompletionUnimported
+
+	// Deep tests deep completion.
+	CompletionDeep
+
+	// Fuzzy tests deep completion and fuzzy matching.
+	CompletionFuzzy
+
+	// CompletionRank candidates in test must be valid and in the right relative order.
+	CompletionRank
 )
 
 type Completion struct {
 	CompletionItems []token.Pos
-	Type            CompletionTestType
 }
 
 type CompletionSnippet struct {
@@ -166,23 +190,42 @@
 	return context.Background()
 }
 
+func DefaultOptions() source.Options {
+	o := source.DefaultOptions
+	o.SupportedCodeActions = map[source.FileKind]map[protocol.CodeActionKind]bool{
+		source.Go: {
+			protocol.SourceOrganizeImports: true,
+			protocol.QuickFix:              true,
+		},
+		source.Mod: {},
+		source.Sum: {},
+	}
+	o.HoverKind = source.SynopsisDocumentation
+	o.InsertTextFormat = protocol.SnippetTextFormat
+	return o
+}
+
 func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
 	t.Helper()
 
 	data := &Data{
-		Diagnostics:        make(Diagnostics),
-		CompletionItems:    make(CompletionItems),
-		Completions:        make(Completions),
-		CompletionSnippets: make(CompletionSnippets),
-		Definitions:        make(Definitions),
-		Highlights:         make(Highlights),
-		References:         make(References),
-		Renames:            make(Renames),
-		PrepareRenames:     make(PrepareRenames),
-		Symbols:            make(Symbols),
-		symbolsChildren:    make(SymbolsChildren),
-		Signatures:         make(Signatures),
-		Links:              make(Links),
+		Diagnostics:           make(Diagnostics),
+		CompletionItems:       make(CompletionItems),
+		Completions:           make(Completions),
+		CompletionSnippets:    make(CompletionSnippets),
+		UnimportedCompletions: make(UnimportedCompletions),
+		DeepCompletions:       make(DeepCompletions),
+		FuzzyCompletions:      make(FuzzyCompletions),
+		RankCompletions:       make(RankCompletions),
+		Definitions:           make(Definitions),
+		Highlights:            make(Highlights),
+		References:            make(References),
+		Renames:               make(Renames),
+		PrepareRenames:        make(PrepareRenames),
+		Symbols:               make(Symbols),
+		symbolsChildren:       make(SymbolsChildren),
+		Signatures:            make(Signatures),
+		Links:                 make(Links),
 
 		t:         t,
 		dir:       dir,
@@ -226,7 +269,7 @@
 		},
 	}
 	data.Exported = packagestest.Export(t, exporter, modules)
-	for fragment, _ := range files {
+	for fragment := range files {
 		filename := data.Exported.File(testModule, fragment)
 		data.fragments[filename] = fragment
 	}
@@ -252,25 +295,30 @@
 
 	// Collect any data that needs to be used by subsequent tests.
 	if err := data.Exported.Expect(map[string]interface{}{
-		"diag":            data.collectDiagnostics,
-		"item":            data.collectCompletionItems,
-		"complete":        data.collectCompletions(CompletionFull),
-		"completePartial": data.collectCompletions(CompletionPartial),
-		"fold":            data.collectFoldingRanges,
-		"format":          data.collectFormats,
-		"import":          data.collectImports,
-		"godef":           data.collectDefinitions,
-		"typdef":          data.collectTypeDefinitions,
-		"hover":           data.collectHoverDefinitions,
-		"highlight":       data.collectHighlights,
-		"refs":            data.collectReferences,
-		"rename":          data.collectRenames,
-		"prepare":         data.collectPrepareRenames,
-		"symbol":          data.collectSymbols,
-		"signature":       data.collectSignatures,
-		"snippet":         data.collectCompletionSnippets,
-		"link":            data.collectLinks,
-		"suggestedfix":    data.collectSuggestedFixes,
+		"diag":       data.collectDiagnostics,
+		"item":       data.collectCompletionItems,
+		"complete":   data.collectCompletions(CompletionDefault),
+		"unimported": data.collectCompletions(CompletionUnimported),
+		"deep":       data.collectCompletions(CompletionDeep),
+		"fuzzy":      data.collectCompletions(CompletionFuzzy),
+		"rank":       data.collectCompletions(CompletionRank),
+		"snippet":    data.collectCompletionSnippets,
+		"fold":       data.collectFoldingRanges,
+		"format":     data.collectFormats,
+		"import":     data.collectImports,
+		"godef":      data.collectDefinitions,
+		"typdef":     data.collectTypeDefinitions,
+		"hover":      data.collectHoverDefinitions,
+		"highlight":  data.collectHighlights,
+		"refs":       data.collectReferences,
+		"rename":     data.collectRenames,
+		"prepare":    data.collectPrepareRenames,
+		"symbol":     data.collectSymbols,
+		"signature":  data.collectSignatures,
+
+		// LSP-only features.
+		"link":         data.collectLinks,
+		"suggestedfix": data.collectSuggestedFixes,
 	}); err != nil {
 		t.Fatal(err)
 	}
@@ -292,15 +340,56 @@
 
 func Run(t *testing.T, tests Tests, data *Data) {
 	t.Helper()
+
 	t.Run("Completion", func(t *testing.T) {
 		t.Helper()
 		if len(data.Completions) != ExpectedCompletionsCount {
 			t.Errorf("got %v completions expected %v", len(data.Completions), ExpectedCompletionsCount)
 		}
+		tests.Completion(t, data.Completions, data.CompletionItems)
+	})
+
+	t.Run("CompletionSnippets", func(t *testing.T) {
+		t.Helper()
 		if len(data.CompletionSnippets) != ExpectedCompletionSnippetCount {
 			t.Errorf("got %v snippets expected %v", len(data.CompletionSnippets), ExpectedCompletionSnippetCount)
 		}
-		tests.Completion(t, data.Completions, data.CompletionSnippets, data.CompletionItems)
+		if len(data.CompletionSnippets) != ExpectedCompletionSnippetCount {
+			t.Errorf("got %v snippets expected %v", len(data.CompletionSnippets), ExpectedCompletionSnippetCount)
+		}
+		tests.CompletionSnippets(t, data.CompletionSnippets, data.CompletionItems)
+	})
+
+	t.Run("UnimportedCompletion", func(t *testing.T) {
+		t.Helper()
+		if len(data.UnimportedCompletions) != ExpectedUnimportedCompletionsCount {
+			t.Errorf("got %v unimported completions expected %v", len(data.UnimportedCompletions), ExpectedUnimportedCompletionsCount)
+		}
+		tests.UnimportedCompletions(t, data.UnimportedCompletions, data.CompletionItems)
+	})
+
+	t.Run("DeepCompletion", func(t *testing.T) {
+		t.Helper()
+		if len(data.DeepCompletions) != ExpectedDeepCompletionsCount {
+			t.Errorf("got %v deep completions expected %v", len(data.DeepCompletions), ExpectedDeepCompletionsCount)
+		}
+		tests.DeepCompletions(t, data.DeepCompletions, data.CompletionItems)
+	})
+
+	t.Run("FuzzyCompletion", func(t *testing.T) {
+		t.Helper()
+		if len(data.FuzzyCompletions) != ExpectedFuzzyCompletionsCount {
+			t.Errorf("got %v fuzzy completions expected %v", len(data.FuzzyCompletions), ExpectedFuzzyCompletionsCount)
+		}
+		tests.FuzzyCompletions(t, data.FuzzyCompletions, data.CompletionItems)
+	})
+
+	t.Run("RankCompletions", func(t *testing.T) {
+		t.Helper()
+		if len(data.RankCompletions) != ExpectedRankedCompletionsCount {
+			t.Errorf("got %v fuzzy completions expected %v", len(data.RankCompletions), ExpectedRankedCompletionsCount)
+		}
+		tests.RankCompletions(t, data.RankCompletions, data.CompletionItems)
 	})
 
 	t.Run("Diagnostics", func(t *testing.T) {
@@ -528,90 +617,32 @@
 	data.Diagnostics[spn.URI()] = append(data.Diagnostics[spn.URI()], want)
 }
 
-// diffDiagnostics prints the diff between expected and actual diagnostics test
-// results.
-func DiffDiagnostics(uri span.URI, want, got []source.Diagnostic) string {
-	sortDiagnostics(want)
-	sortDiagnostics(got)
-
-	if len(got) != len(want) {
-		return summarizeDiagnostics(-1, want, got, "different lengths got %v want %v", len(got), len(want))
-	}
-	for i, w := range want {
-		g := got[i]
-		if w.Message != g.Message {
-			return summarizeDiagnostics(i, want, got, "incorrect Message got %v want %v", g.Message, w.Message)
-		}
-		if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
-			return summarizeDiagnostics(i, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
-		}
-		// Special case for diagnostics on parse errors.
-		if strings.Contains(string(uri), "noparse") {
-			if protocol.ComparePosition(g.Range.Start, g.Range.End) != 0 || protocol.ComparePosition(w.Range.Start, g.Range.End) != 0 {
-				return summarizeDiagnostics(i, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.Start)
-			}
-		} else if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the diagnostic returns a zero-length range.
-			if protocol.ComparePosition(w.Range.End, g.Range.End) != 0 {
-				return summarizeDiagnostics(i, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
-			}
-		}
-		if w.Severity != g.Severity {
-			return summarizeDiagnostics(i, want, got, "incorrect Severity got %v want %v", g.Severity, w.Severity)
-		}
-		if w.Source != g.Source {
-			return summarizeDiagnostics(i, want, got, "incorrect Source got %v want %v", g.Source, w.Source)
-		}
-	}
-	return ""
-}
-
-func sortDiagnostics(d []source.Diagnostic) {
-	sort.Slice(d, func(i int, j int) bool {
-		return compareDiagnostic(d[i], d[j]) < 0
-	})
-}
-
-func compareDiagnostic(a, b source.Diagnostic) int {
-	if r := span.CompareURI(a.URI, b.URI); r != 0 {
-		return r
-	}
-	if r := protocol.CompareRange(a.Range, b.Range); r != 0 {
-		return r
-	}
-	if a.Message < b.Message {
-		return -1
-	}
-	if a.Message == b.Message {
-		return 0
-	} else {
-		return 1
-	}
-}
-
-func summarizeDiagnostics(i int, want []source.Diagnostic, got []source.Diagnostic, reason string, args ...interface{}) string {
-	msg := &bytes.Buffer{}
-	fmt.Fprint(msg, "diagnostics failed")
-	if i >= 0 {
-		fmt.Fprintf(msg, " at %d", i)
-	}
-	fmt.Fprint(msg, " because of ")
-	fmt.Fprintf(msg, reason, args...)
-	fmt.Fprint(msg, ":\nexpected:\n")
-	for _, d := range want {
-		fmt.Fprintf(msg, "  %s:%v: %s\n", d.URI, d.Range, d.Message)
-	}
-	fmt.Fprintf(msg, "got:\n")
-	for _, d := range got {
-		fmt.Fprintf(msg, "  %s:%v: %s\n", d.URI, d.Range, d.Message)
-	}
-	return msg.String()
-}
-
 func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []token.Pos) {
-	return func(src span.Span, expected []token.Pos) {
-		data.Completions[src] = Completion{
+	result := func(m map[span.Span]Completion, src span.Span, expected []token.Pos) {
+		m[src] = Completion{
 			CompletionItems: expected,
-			Type:            typ,
+		}
+	}
+	switch typ {
+	case CompletionDeep:
+		return func(src span.Span, expected []token.Pos) {
+			result(data.DeepCompletions, src, expected)
+		}
+	case CompletionUnimported:
+		return func(src span.Span, expected []token.Pos) {
+			result(data.UnimportedCompletions, src, expected)
+		}
+	case CompletionFuzzy:
+		return func(src span.Span, expected []token.Pos) {
+			result(data.FuzzyCompletions, src, expected)
+		}
+	case CompletionRank:
+		return func(src span.Span, expected []token.Pos) {
+			result(data.RankCompletions, src, expected)
+		}
+	default:
+		return func(src span.Span, expected []token.Pos) {
+			result(data.Completions, src, expected)
 		}
 	}
 }