[gopls-release-branch.0.3] all: merge master into gopls-release-branch.0.3

In preparation for gopls/v0.3.3.

Change-Id: Ie25b15b914c7f11c685b068ef1b0b8786e299c92
diff --git a/cmd/benchcmp/benchcmp.go b/cmd/benchcmp/benchcmp.go
index 32f3a1c..ed53d71 100644
--- a/cmd/benchcmp/benchcmp.go
+++ b/cmd/benchcmp/benchcmp.go
@@ -32,6 +32,7 @@
 `
 
 func main() {
+	fmt.Fprintf(os.Stderr, "benchcmp is deprecated in favor of benchstat: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat\n")
 	flag.Usage = func() {
 		fmt.Fprintf(os.Stderr, "usage: %s old.txt new.txt\n\n", os.Args[0])
 		flag.PrintDefaults()
diff --git a/cmd/compilebench/main.go b/cmd/compilebench/main.go
index f66cf87..df74357 100644
--- a/cmd/compilebench/main.go
+++ b/cmd/compilebench/main.go
@@ -246,7 +246,7 @@
 	var pkg Pkg
 	out, err := exec.Command(*flagGoCmd, "list", "-json", dir).Output()
 	if err != nil {
-		return nil, fmt.Errorf("go list -json %s: %v\n", dir, err)
+		return nil, fmt.Errorf("go list -json %s: %v", dir, err)
 	}
 	if err := json.Unmarshal(out, &pkg); err != nil {
 		return nil, fmt.Errorf("go list -json %s: unmarshal: %v", dir, err)
diff --git a/cmd/digraph/digraph.go b/cmd/digraph/digraph.go
index ff2399f..88eb05b 100644
--- a/cmd/digraph/digraph.go
+++ b/cmd/digraph/digraph.go
@@ -417,7 +417,7 @@
 
 	case "succs", "preds":
 		if len(args) == 0 {
-			return fmt.Errorf("usage: digraph %s <node> ...", cmd)
+			return fmt.Errorf("usage: digraph %s <node> ... ", cmd)
 		}
 		g := g
 		if cmd == "preds" {
@@ -435,7 +435,7 @@
 
 	case "forward", "reverse":
 		if len(args) == 0 {
-			return fmt.Errorf("usage: digraph %s <node> ...", cmd)
+			return fmt.Errorf("usage: digraph %s <node> ... ", cmd)
 		}
 		roots := make(nodeset)
 		for _, root := range args {
diff --git a/cmd/fiximports/main.go b/cmd/fiximports/main.go
index b87a7d5..ece4adc 100644
--- a/cmd/fiximports/main.go
+++ b/cmd/fiximports/main.go
@@ -126,7 +126,7 @@
 	flag.Parse()
 
 	if len(flag.Args()) == 0 {
-		fmt.Fprintf(stderr, usage)
+		fmt.Fprint(stderr, usage)
 		os.Exit(1)
 	}
 	if !fiximports(flag.Args()...) {
diff --git a/cmd/getgo/main.go b/cmd/getgo/main.go
index a92ae48..792ea05 100644
--- a/cmd/getgo/main.go
+++ b/cmd/getgo/main.go
@@ -27,7 +27,7 @@
 	version = "devel"
 )
 
-var exitCleanly error = errors.New("exit cleanly sentinel value")
+var errExitCleanly error = errors.New("exit cleanly sentinel value")
 
 func main() {
 	flag.Parse()
@@ -41,7 +41,7 @@
 
 	runStep := func(s step) {
 		err := s(ctx)
-		if err == exitCleanly {
+		if err == errExitCleanly {
 			os.Exit(0)
 		}
 		if err != nil {
diff --git a/cmd/getgo/steps.go b/cmd/getgo/steps.go
index 41c57d2..e505f5a 100644
--- a/cmd/getgo/steps.go
+++ b/cmd/getgo/steps.go
@@ -25,7 +25,7 @@
 	}
 	if strings.ToLower(answer) != "y" {
 		fmt.Println("Exiting install.")
-		return exitCleanly
+		return errExitCleanly
 	}
 
 	return nil
@@ -65,7 +65,7 @@
 	if strings.ToLower(answer) != "y" {
 		// TODO: handle passing a version
 		fmt.Println("Aborting install.")
-		return exitCleanly
+		return errExitCleanly
 	}
 
 	return nil
@@ -79,7 +79,7 @@
 
 	if strings.ToLower(answer) != "y" {
 		fmt.Println("Aborting install.")
-		return exitCleanly
+		return errExitCleanly
 	}
 
 	fmt.Printf("Downloading Go version %s to %s\n", *goVersion, installPath)
@@ -105,7 +105,7 @@
 
 	if strings.ToLower(answer) != "y" {
 		fmt.Println("Exiting and not setting up GOPATH.")
-		return exitCleanly
+		return errExitCleanly
 	}
 
 	fmt.Println("Setting up GOPATH")
diff --git a/cmd/godex/gc.go b/cmd/godex/gc.go
index 95eba65..eaf0230 100644
--- a/cmd/godex/gc.go
+++ b/cmd/godex/gc.go
@@ -6,8 +6,11 @@
 
 package main
 
-import "go/importer"
+import (
+	"go/importer"
+	"go/token"
+)
 
 func init() {
-	register("gc", importer.For("gc", nil))
+	register("gc", importer.ForCompiler(token.NewFileSet(), "gc", nil))
 }
diff --git a/cmd/godex/gccgo.go b/cmd/godex/gccgo.go
index 7644998..a539f98 100644
--- a/cmd/godex/gccgo.go
+++ b/cmd/godex/gccgo.go
@@ -8,11 +8,12 @@
 
 import (
 	"go/importer"
+	"go/token"
 	"go/types"
 )
 
 func init() {
-	register("gccgo", importer.For("gccgo", nil))
+	register("gccgo", importer.ForCompiler(token.NewFileSet(), "gccgo", nil))
 }
 
 // Print the extra gccgo compiler data for this package, if it exists.
diff --git a/cmd/godex/godex.go b/cmd/godex/godex.go
index a222ed63..e1d7e2f 100644
--- a/cmd/godex/godex.go
+++ b/cmd/godex/godex.go
@@ -23,9 +23,9 @@
 
 // lists of registered sources and corresponding importers
 var (
-	sources      []string
-	importers    []types.Importer
-	importFailed = errors.New("import failed")
+	sources         []string
+	importers       []types.Importer
+	errImportFailed = errors.New("import failed")
 )
 
 func usage() {
@@ -154,7 +154,7 @@
 	defer func() {
 		if recover() != nil {
 			pkg = nil
-			err = importFailed
+			err = errImportFailed
 		}
 	}()
 	return p.imp.Import(path)
diff --git a/cmd/godex/writetype.go b/cmd/godex/writetype.go
index dd17f90..5cbe1b1 100644
--- a/cmd/godex/writetype.go
+++ b/cmd/godex/writetype.go
@@ -133,7 +133,7 @@
 				p.print("\n")
 			}
 			for i, n := 0, t.NumEmbeddeds(); i < n; i++ {
-				typ := t.Embedded(i)
+				typ := t.EmbeddedType(i)
 				p.writeTypeInternal(this, typ, visited)
 				p.print("\n")
 			}
diff --git a/cmd/gotype/gotype.go b/cmd/gotype/gotype.go
index ed88e63..dbb2626 100644
--- a/cmd/gotype/gotype.go
+++ b/cmd/gotype/gotype.go
@@ -284,7 +284,7 @@
 			}
 			report(err)
 		},
-		Importer: importer.For(*compiler, nil),
+		Importer: importer.ForCompiler(fset, *compiler, nil),
 		Sizes:    SizesFor(build.Default.Compiler, build.Default.GOARCH),
 	}
 
diff --git a/container/intsets/sparse_test.go b/container/intsets/sparse_test.go
index e3ef9d3..7481a06 100644
--- a/container/intsets/sparse_test.go
+++ b/container/intsets/sparse_test.go
@@ -587,7 +587,7 @@
 			t.Errorf("shallow copy: recover() = %q, want %q", got, want)
 		}
 	}()
-	y.String() // panics
+	_ = y.String() // panics
 	t.Error("didn't panic as expected")
 }
 
diff --git a/go/analysis/doc.go b/go/analysis/doc.go
index 8fa4a85..ea56b72 100644
--- a/go/analysis/doc.go
+++ b/go/analysis/doc.go
@@ -1,6 +1,6 @@
 /*
 
-The analysis package defines the interface between a modular static
+Package analysis defines the interface between a modular static
 analysis and an analysis driver program.
 
 
diff --git a/go/analysis/passes/ifaceassert/cmd/ifaceassert/main.go b/go/analysis/passes/ifaceassert/cmd/ifaceassert/main.go
new file mode 100644
index 0000000..42250f9
--- /dev/null
+++ b/go/analysis/passes/ifaceassert/cmd/ifaceassert/main.go
@@ -0,0 +1,13 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The ifaceassert command runs the ifaceassert analyzer.
+package main
+
+import (
+	"golang.org/x/tools/go/analysis/passes/ifaceassert"
+	"golang.org/x/tools/go/analysis/singlechecker"
+)
+
+func main() { singlechecker.Main(ifaceassert.Analyzer) }
diff --git a/go/analysis/passes/ifaceassert/ifaceassert.go b/go/analysis/passes/ifaceassert/ifaceassert.go
new file mode 100644
index 0000000..c5a71a7
--- /dev/null
+++ b/go/analysis/passes/ifaceassert/ifaceassert.go
@@ -0,0 +1,101 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package ifaceassert defines an Analyzer that flags
+// impossible interface-interface type assertions.
+package ifaceassert
+
+import (
+	"go/ast"
+	"go/types"
+
+	"golang.org/x/tools/go/analysis"
+	"golang.org/x/tools/go/analysis/passes/inspect"
+	"golang.org/x/tools/go/ast/inspector"
+)
+
+const Doc = `detect impossible interface-to-interface type assertions
+
+This checker flags type assertions v.(T) and corresponding type-switch cases
+in which the static type V of v is an interface that cannot possibly implement
+the target interface T. This occurs when V and T contain methods with the same
+name but different signatures. Example:
+
+	var v interface {
+		Read()
+	}
+	_ = v.(io.Reader)
+
+The Read method in v has a different signature than the Read method in
+io.Reader, so this assertion cannot succeed.
+`
+
+var Analyzer = &analysis.Analyzer{
+	Name:     "ifaceassert",
+	Doc:      Doc,
+	Requires: []*analysis.Analyzer{inspect.Analyzer},
+	Run:      run,
+}
+
+// assertableTo checks whether interface v can be asserted into t. It returns
+// nil on success, or the first conflicting method on failure.
+func assertableTo(v, t types.Type) *types.Func {
+	// ensure that v and t are interfaces
+	V, _ := v.Underlying().(*types.Interface)
+	T, _ := t.Underlying().(*types.Interface)
+	if V == nil || T == nil {
+		return nil
+	}
+	if f, wrongType := types.MissingMethod(V, T, false); wrongType {
+		return f
+	}
+	return nil
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+	nodeFilter := []ast.Node{
+		(*ast.TypeAssertExpr)(nil),
+		(*ast.TypeSwitchStmt)(nil),
+	}
+	inspect.Preorder(nodeFilter, func(n ast.Node) {
+		var (
+			assert  *ast.TypeAssertExpr // v.(T) expression
+			targets []ast.Expr          // interfaces T in v.(T)
+		)
+		switch n := n.(type) {
+		case *ast.TypeAssertExpr:
+			// take care of v.(type) in *ast.TypeSwitchStmt
+			if n.Type == nil {
+				return
+			}
+			assert = n
+			targets = append(targets, n.Type)
+		case *ast.TypeSwitchStmt:
+			// retrieve type assertion from type switch's 'assign' field
+			switch t := n.Assign.(type) {
+			case *ast.ExprStmt:
+				assert = t.X.(*ast.TypeAssertExpr)
+			case *ast.AssignStmt:
+				assert = t.Rhs[0].(*ast.TypeAssertExpr)
+			}
+			// gather target types from case clauses
+			for _, c := range n.Body.List {
+				targets = append(targets, c.(*ast.CaseClause).List...)
+			}
+		}
+		V := pass.TypesInfo.TypeOf(assert.X)
+		for _, target := range targets {
+			T := pass.TypesInfo.TypeOf(target)
+			if f := assertableTo(V, T); f != nil {
+				pass.Reportf(
+					target.Pos(),
+					"impossible type assertion: no type can implement both %v and %v (conflicting types for %v method)",
+					V, T, f.Name(),
+				)
+			}
+		}
+	})
+	return nil, nil
+}
diff --git a/go/analysis/passes/ifaceassert/ifaceassert_test.go b/go/analysis/passes/ifaceassert/ifaceassert_test.go
new file mode 100644
index 0000000..4607338
--- /dev/null
+++ b/go/analysis/passes/ifaceassert/ifaceassert_test.go
@@ -0,0 +1,17 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ifaceassert_test
+
+import (
+	"testing"
+
+	"golang.org/x/tools/go/analysis/analysistest"
+	"golang.org/x/tools/go/analysis/passes/ifaceassert"
+)
+
+func Test(t *testing.T) {
+	testdata := analysistest.TestData()
+	analysistest.Run(t, testdata, ifaceassert.Analyzer, "a")
+}
diff --git a/go/analysis/passes/ifaceassert/testdata/src/a/a.go b/go/analysis/passes/ifaceassert/testdata/src/a/a.go
new file mode 100644
index 0000000..ca9beb0
--- /dev/null
+++ b/go/analysis/passes/ifaceassert/testdata/src/a/a.go
@@ -0,0 +1,40 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file contains tests for the ifaceassert checker.
+
+package a
+
+import "io"
+
+func InterfaceAssertionTest() {
+	var (
+		a io.ReadWriteSeeker
+		b interface {
+			Read()
+			Write()
+		}
+	)
+	_ = a.(io.Reader)
+	_ = a.(io.ReadWriter)
+	_ = b.(io.Reader)  // want `^impossible type assertion: no type can implement both interface{Read\(\); Write\(\)} and io.Reader \(conflicting types for Read method\)$`
+	_ = b.(interface { // want `^impossible type assertion: no type can implement both interface{Read\(\); Write\(\)} and interface{Read\(p \[\]byte\) \(n int, err error\)} \(conflicting types for Read method\)$`
+		Read(p []byte) (n int, err error)
+	})
+
+	switch a.(type) {
+	case io.ReadWriter:
+	case interface { // want `^impossible type assertion: no type can implement both io.ReadWriteSeeker and interface{Write\(\)} \(conflicting types for Write method\)$`
+		Write()
+	}:
+	default:
+	}
+
+	switch b := b.(type) {
+	case io.ReadWriter, interface{ Read() }: // want `^impossible type assertion: no type can implement both interface{Read\(\); Write\(\)} and io.ReadWriter \(conflicting types for Read method\)$`
+	case io.Writer: // want `^impossible type assertion: no type can implement both interface{Read\(\); Write\(\)} and io.Writer \(conflicting types for Write method\)$`
+	default:
+		_ = b
+	}
+}
diff --git a/go/analysis/passes/stringintconv/cmd/stringintconv/main.go b/go/analysis/passes/stringintconv/cmd/stringintconv/main.go
new file mode 100644
index 0000000..118b957
--- /dev/null
+++ b/go/analysis/passes/stringintconv/cmd/stringintconv/main.go
@@ -0,0 +1,13 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The stringintconv command runs the stringintconv analyzer.
+package main
+
+import (
+	"golang.org/x/tools/go/analysis/passes/stringintconv"
+	"golang.org/x/tools/go/analysis/singlechecker"
+)
+
+func main() { singlechecker.Main(stringintconv.Analyzer) }
diff --git a/go/analysis/passes/stringintconv/string.go b/go/analysis/passes/stringintconv/string.go
new file mode 100644
index 0000000..ac2cd84
--- /dev/null
+++ b/go/analysis/passes/stringintconv/string.go
@@ -0,0 +1,126 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package stringintconv defines an Analyzer that flags type conversions
+// from integers to strings.
+package stringintconv
+
+import (
+	"fmt"
+	"go/ast"
+	"go/types"
+
+	"golang.org/x/tools/go/analysis"
+	"golang.org/x/tools/go/analysis/passes/inspect"
+	"golang.org/x/tools/go/ast/inspector"
+)
+
+const Doc = `check for string(int) conversions
+
+This checker flags conversions of the form string(x) where x is an integer
+(but not byte or rune) type. Such conversions are discouraged because they
+return the UTF-8 representation of the Unicode code point x, and not a decimal
+string representation of x as one might expect. Furthermore, if x denotes an
+invalid code point, the conversion cannot be statically rejected.
+
+For conversions that intend on using the code point, consider replacing them
+with string(rune(x)). Otherwise, strconv.Itoa and its equivalents return the
+string representation of the value in the desired base.
+`
+
+var Analyzer = &analysis.Analyzer{
+	Name:     "stringintconv",
+	Doc:      Doc,
+	Requires: []*analysis.Analyzer{inspect.Analyzer},
+	Run:      run,
+}
+
+func typeName(typ types.Type) string {
+	if v, _ := typ.(interface{ Name() string }); v != nil {
+		return v.Name()
+	}
+	if v, _ := typ.(interface{ Obj() *types.TypeName }); v != nil {
+		return v.Obj().Name()
+	}
+	return ""
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+	nodeFilter := []ast.Node{
+		(*ast.CallExpr)(nil),
+	}
+	inspect.Preorder(nodeFilter, func(n ast.Node) {
+		call := n.(*ast.CallExpr)
+
+		// Retrieve target type name.
+		var tname *types.TypeName
+		switch fun := call.Fun.(type) {
+		case *ast.Ident:
+			tname, _ = pass.TypesInfo.Uses[fun].(*types.TypeName)
+		case *ast.SelectorExpr:
+			tname, _ = pass.TypesInfo.Uses[fun.Sel].(*types.TypeName)
+		}
+		if tname == nil {
+			return
+		}
+		target := tname.Name()
+
+		// Check that target type T in T(v) has an underlying type of string.
+		T, _ := tname.Type().Underlying().(*types.Basic)
+		if T == nil || T.Kind() != types.String {
+			return
+		}
+		if s := T.Name(); target != s {
+			target += " (" + s + ")"
+		}
+
+		// Check that type V of v has an underlying integral type that is not byte or rune.
+		if len(call.Args) != 1 {
+			return
+		}
+		v := call.Args[0]
+		vtyp := pass.TypesInfo.TypeOf(v)
+		V, _ := vtyp.Underlying().(*types.Basic)
+		if V == nil || V.Info()&types.IsInteger == 0 {
+			return
+		}
+		switch V.Kind() {
+		case types.Byte, types.Rune, types.UntypedRune:
+			return
+		}
+
+		// Retrieve source type name.
+		source := typeName(vtyp)
+		if source == "" {
+			return
+		}
+		if s := V.Name(); source != s {
+			source += " (" + s + ")"
+		}
+		diag := analysis.Diagnostic{
+			Pos:     n.Pos(),
+			Message: fmt.Sprintf("conversion from %s to %s yields a string of one rune", source, target),
+			SuggestedFixes: []analysis.SuggestedFix{
+				{
+					Message: "Did you mean to convert a rune to a string?",
+					TextEdits: []analysis.TextEdit{
+						{
+							Pos:     v.Pos(),
+							End:     v.Pos(),
+							NewText: []byte("rune("),
+						},
+						{
+							Pos:     v.End(),
+							End:     v.End(),
+							NewText: []byte(")"),
+						},
+					},
+				},
+			},
+		}
+		pass.Report(diag)
+	})
+	return nil, nil
+}
diff --git a/go/analysis/passes/stringintconv/string_test.go b/go/analysis/passes/stringintconv/string_test.go
new file mode 100644
index 0000000..ed06332
--- /dev/null
+++ b/go/analysis/passes/stringintconv/string_test.go
@@ -0,0 +1,17 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package stringintconv_test
+
+import (
+	"testing"
+
+	"golang.org/x/tools/go/analysis/analysistest"
+	"golang.org/x/tools/go/analysis/passes/stringintconv"
+)
+
+func Test(t *testing.T) {
+	testdata := analysistest.TestData()
+	analysistest.Run(t, testdata, stringintconv.Analyzer, "a")
+}
diff --git a/go/analysis/passes/stringintconv/testdata/src/a/a.go b/go/analysis/passes/stringintconv/testdata/src/a/a.go
new file mode 100644
index 0000000..72ceb97
--- /dev/null
+++ b/go/analysis/passes/stringintconv/testdata/src/a/a.go
@@ -0,0 +1,36 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file contains tests for the stringintconv checker.
+
+package a
+
+type A string
+
+type B = string
+
+type C int
+
+type D = uintptr
+
+func StringTest() {
+	var (
+		i int
+		j rune
+		k byte
+		l C
+		m D
+		n = []int{0, 1, 2}
+		o struct{ x int }
+	)
+	const p = 0
+	_ = string(i) // want `^conversion from int to string yields a string of one rune$`
+	_ = string(j)
+	_ = string(k)
+	_ = string(p)    // want `^conversion from untyped int to string yields a string of one rune$`
+	_ = A(l)         // want `^conversion from C \(int\) to A \(string\) yields a string of one rune$`
+	_ = B(m)         // want `^conversion from uintptr to B \(string\) yields a string of one rune$`
+	_ = string(n[1]) // want `^conversion from int to string yields a string of one rune$`
+	_ = string(o.x)  // want `^conversion from int to string yields a string of one rune$`
+}
diff --git a/go/cfg/cfg.go b/go/cfg/cfg.go
index b075034..3ebc65f 100644
--- a/go/cfg/cfg.go
+++ b/go/cfg/cfg.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.
 
-// This package constructs a simple control-flow graph (CFG) of the
+// Package cfg constructs a simple control-flow graph (CFG) of the
 // statements and expressions within a single function.
 //
 // Use cfg.New to construct the CFG for a function body.
diff --git a/go/expect/expect.go b/go/expect/expect.go
index ac9975c..672c3a3 100644
--- a/go/expect/expect.go
+++ b/go/expect/expect.go
@@ -56,6 +56,7 @@
 	"bytes"
 	"fmt"
 	"go/token"
+	"path/filepath"
 	"regexp"
 )
 
@@ -89,12 +90,20 @@
 		return token.NoPos, token.NoPos, fmt.Errorf("invalid file: %v", err)
 	}
 	position := f.Position(end)
-	startOffset := f.Offset(lineStart(f, position.Line))
+	startOffset := f.Offset(f.LineStart(position.Line))
 	endOffset := f.Offset(end)
 	line := content[startOffset:endOffset]
 	matchStart, matchEnd := -1, -1
 	switch pattern := pattern.(type) {
 	case string:
+		// If the file is a go.mod and we are matching // indirect, then we
+		// need to look for it on the line after the current line.
+		// TODO(golang/go#36894): have a more intuitive approach for // indirect
+		if filepath.Ext(f.Name()) == ".mod" && pattern == "// indirect" {
+			startOffset = f.Offset(f.LineStart(position.Line + 1))
+			endOffset = f.Offset(lineEnd(f, position.Line+1))
+			line = content[startOffset:endOffset]
+		}
 		bytePattern := []byte(pattern)
 		matchStart = bytes.Index(line, bytePattern)
 		if matchStart >= 0 {
@@ -118,32 +127,9 @@
 	return f.Pos(startOffset + matchStart), f.Pos(startOffset + matchEnd), nil
 }
 
-// this functionality was borrowed from the analysisutil package
-func lineStart(f *token.File, line int) token.Pos {
-	// Use binary search to find the start offset of this line.
-	//
-	// TODO(adonovan): eventually replace this function with the
-	// simpler and more efficient (*go/token.File).LineStart, added
-	// in go1.12.
-
-	min := 0        // inclusive
-	max := f.Size() // exclusive
-	for {
-		offset := (min + max) / 2
-		pos := f.Pos(offset)
-		posn := f.Position(pos)
-		if posn.Line == line {
-			return pos - (token.Pos(posn.Column) - 1)
-		}
-
-		if min+1 >= max {
-			return token.NoPos
-		}
-
-		if posn.Line < line {
-			min = offset
-		} else {
-			max = offset
-		}
+func lineEnd(f *token.File, line int) token.Pos {
+	if line >= f.LineCount() {
+		return token.Pos(f.Base() + f.Size())
 	}
+	return f.LineStart(line + 1)
 }
diff --git a/go/expect/expect_test.go b/go/expect/expect_test.go
index c00a0d7..bd6e437 100644
--- a/go/expect/expect_test.go
+++ b/go/expect/expect_test.go
@@ -14,102 +14,127 @@
 )
 
 func TestMarker(t *testing.T) {
-	const filename = "testdata/test.go"
-	content, err := ioutil.ReadFile(filename)
-	if err != nil {
-		t.Fatal(err)
-	}
+	for _, tt := range []struct {
+		filename      string
+		expectNotes   int
+		expectMarkers map[string]string
+		expectChecks  map[string][]interface{}
+	}{
+		{
+			filename:    "testdata/test.go",
+			expectNotes: 13,
+			expectMarkers: map[string]string{
+				"αSimpleMarker": "α",
+				"OffsetMarker":  "β",
+				"RegexMarker":   "γ",
+				"εMultiple":     "ε",
+				"ζMarkers":      "ζ",
+				"ηBlockMarker":  "η",
+				"Declared":      "η",
+				"Comment":       "ι",
+				"LineComment":   "someFunc",
+				"NonIdentifier": "+",
+				"StringMarker":  "\"hello\"",
+			},
+			expectChecks: map[string][]interface{}{
+				"αSimpleMarker": nil,
+				"StringAndInt":  []interface{}{"Number %d", int64(12)},
+				"Bool":          []interface{}{true},
+			},
+		},
+		{
+			filename:    "testdata/go.mod",
+			expectNotes: 3,
+			expectMarkers: map[string]string{
+				"αMarker":        "αfake1α",
+				"IndirectMarker": "// indirect",
+				"βMarker":        "require golang.org/modfile v0.0.0",
+			},
+		},
+	} {
+		t.Run(tt.filename, func(t *testing.T) {
+			content, err := ioutil.ReadFile(tt.filename)
+			if err != nil {
+				t.Fatal(err)
+			}
+			readFile := func(string) ([]byte, error) { return content, nil }
 
-	const expectNotes = 13
-	expectMarkers := map[string]string{
-		"αSimpleMarker": "α",
-		"OffsetMarker":  "β",
-		"RegexMarker":   "γ",
-		"εMultiple":     "ε",
-		"ζMarkers":      "ζ",
-		"ηBlockMarker":  "η",
-		"Declared":      "η",
-		"Comment":       "ι",
-		"LineComment":   "someFunc",
-		"NonIdentifier": "+",
-		"StringMarker":  "\"hello\"",
-	}
-	expectChecks := map[string][]interface{}{
-		"αSimpleMarker": nil,
-		"StringAndInt":  []interface{}{"Number %d", int64(12)},
-		"Bool":          []interface{}{true},
-	}
-
-	readFile := func(string) ([]byte, error) { return content, nil }
-	markers := make(map[string]token.Pos)
-	for name, tok := range expectMarkers {
-		offset := bytes.Index(content, []byte(tok))
-		markers[name] = token.Pos(offset + 1)
-		end := bytes.Index(content[offset:], []byte(tok))
-		if end > 0 {
-			markers[name+"@"] = token.Pos(offset + end + 2)
-		}
-	}
-
-	fset := token.NewFileSet()
-	notes, err := expect.Parse(fset, filename, nil)
-	if err != nil {
-		t.Fatalf("Failed to extract notes: %v", err)
-	}
-	if len(notes) != expectNotes {
-		t.Errorf("Expected %v notes, got %v", expectNotes, len(notes))
-	}
-	for _, n := range notes {
-		switch {
-		case n.Args == nil:
-			// A //@foo note associates the name foo with the position of the
-			// first match of "foo" on the current line.
-			checkMarker(t, fset, readFile, markers, n.Pos, n.Name, n.Name)
-		case n.Name == "mark":
-			// A //@mark(name, "pattern") note associates the specified name
-			// with the position on the first match of pattern on the current line.
-			if len(n.Args) != 2 {
-				t.Errorf("%v: expected 2 args to mark, got %v", fset.Position(n.Pos), len(n.Args))
-				continue
-			}
-			ident, ok := n.Args[0].(expect.Identifier)
-			if !ok {
-				t.Errorf("%v: identifier, got %T", fset.Position(n.Pos), n.Args[0])
-				continue
-			}
-			checkMarker(t, fset, readFile, markers, n.Pos, string(ident), n.Args[1])
-
-		case n.Name == "check":
-			// A //@check(args, ...) note specifies some hypothetical action to
-			// be taken by the test driver and its expected outcome.
-			// In this test, the action is to compare the arguments
-			// against expectChecks.
-			if len(n.Args) < 1 {
-				t.Errorf("%v: expected 1 args to check, got %v", fset.Position(n.Pos), len(n.Args))
-				continue
-			}
-			ident, ok := n.Args[0].(expect.Identifier)
-			if !ok {
-				t.Errorf("%v: identifier, got %T", fset.Position(n.Pos), n.Args[0])
-				continue
-			}
-			args, ok := expectChecks[string(ident)]
-			if !ok {
-				t.Errorf("%v: unexpected check %v", fset.Position(n.Pos), ident)
-				continue
-			}
-			if len(n.Args) != len(args)+1 {
-				t.Errorf("%v: expected %v args to check, got %v", fset.Position(n.Pos), len(args)+1, len(n.Args))
-				continue
-			}
-			for i, got := range n.Args[1:] {
-				if args[i] != got {
-					t.Errorf("%v: arg %d expected %v, got %v", fset.Position(n.Pos), i, args[i], got)
+			markers := make(map[string]token.Pos)
+			for name, tok := range tt.expectMarkers {
+				offset := bytes.Index(content, []byte(tok))
+				// Handle special case where we look for // indirect and we
+				// need to search the next line.
+				if tok == "// indirect" {
+					offset = bytes.Index(content, []byte(" "+tok)) + 1
+				}
+				markers[name] = token.Pos(offset + 1)
+				end := bytes.Index(content[offset:], []byte(tok))
+				if end > 0 {
+					markers[name+"@"] = token.Pos(offset + end + 2)
 				}
 			}
-		default:
-			t.Errorf("Unexpected note %v at %v", n.Name, fset.Position(n.Pos))
-		}
+
+			fset := token.NewFileSet()
+			notes, err := expect.Parse(fset, tt.filename, content)
+			if err != nil {
+				t.Fatalf("Failed to extract notes: %v", err)
+			}
+			if len(notes) != tt.expectNotes {
+				t.Errorf("Expected %v notes, got %v", tt.expectNotes, len(notes))
+			}
+			for _, n := range notes {
+				switch {
+				case n.Args == nil:
+					// A //@foo note associates the name foo with the position of the
+					// first match of "foo" on the current line.
+					checkMarker(t, fset, readFile, markers, n.Pos, n.Name, n.Name)
+				case n.Name == "mark":
+					// A //@mark(name, "pattern") note associates the specified name
+					// with the position on the first match of pattern on the current line.
+					if len(n.Args) != 2 {
+						t.Errorf("%v: expected 2 args to mark, got %v", fset.Position(n.Pos), len(n.Args))
+						continue
+					}
+					ident, ok := n.Args[0].(expect.Identifier)
+					if !ok {
+						t.Errorf("%v: identifier, got %T", fset.Position(n.Pos), n.Args[0])
+						continue
+					}
+					checkMarker(t, fset, readFile, markers, n.Pos, string(ident), n.Args[1])
+
+				case n.Name == "check":
+					// A //@check(args, ...) note specifies some hypothetical action to
+					// be taken by the test driver and its expected outcome.
+					// In this test, the action is to compare the arguments
+					// against expectChecks.
+					if len(n.Args) < 1 {
+						t.Errorf("%v: expected 1 args to check, got %v", fset.Position(n.Pos), len(n.Args))
+						continue
+					}
+					ident, ok := n.Args[0].(expect.Identifier)
+					if !ok {
+						t.Errorf("%v: identifier, got %T", fset.Position(n.Pos), n.Args[0])
+						continue
+					}
+					args, ok := tt.expectChecks[string(ident)]
+					if !ok {
+						t.Errorf("%v: unexpected check %v", fset.Position(n.Pos), ident)
+						continue
+					}
+					if len(n.Args) != len(args)+1 {
+						t.Errorf("%v: expected %v args to check, got %v", fset.Position(n.Pos), len(args)+1, len(n.Args))
+						continue
+					}
+					for i, got := range n.Args[1:] {
+						if args[i] != got {
+							t.Errorf("%v: arg %d expected %v, got %v", fset.Position(n.Pos), i, args[i], got)
+						}
+					}
+				default:
+					t.Errorf("Unexpected note %v at %v", n.Name, fset.Position(n.Pos))
+				}
+			}
+		})
 	}
 }
 
diff --git a/go/expect/extract.go b/go/expect/extract.go
index 249369f..4862b76 100644
--- a/go/expect/extract.go
+++ b/go/expect/extract.go
@@ -9,15 +9,17 @@
 	"go/ast"
 	"go/parser"
 	"go/token"
+	"path/filepath"
 	"regexp"
 	"strconv"
 	"strings"
 	"text/scanner"
+
+	"golang.org/x/mod/modfile"
 )
 
-const (
-	commentStart = "@"
-)
+const commentStart = "@"
+const commentStartLen = len(commentStart)
 
 // Identifier is the type for an identifier in an Note argument list.
 type Identifier string
@@ -34,52 +36,72 @@
 	if content != nil {
 		src = content
 	}
-	// TODO: We should write this in terms of the scanner.
-	// there are ways you can break the parser such that it will not add all the
-	// comments to the ast, which may result in files where the tests are silently
-	// not run.
-	file, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
-	if file == nil {
-		return nil, err
+	switch filepath.Ext(filename) {
+	case ".go":
+		// TODO: We should write this in terms of the scanner.
+		// there are ways you can break the parser such that it will not add all the
+		// comments to the ast, which may result in files where the tests are silently
+		// not run.
+		file, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
+		if file == nil {
+			return nil, err
+		}
+		return ExtractGo(fset, file)
+	case ".mod":
+		file, err := modfile.Parse(filename, content, nil)
+		if err != nil {
+			return nil, err
+		}
+		f := fset.AddFile(filename, -1, len(content))
+		f.SetLinesForContent(content)
+		notes, err := extractMod(fset, file)
+		if err != nil {
+			return nil, err
+		}
+		// Since modfile.Parse does not return an *ast, we need to add the offset
+		// within the file's contents to the file's base relative to the fileset.
+		for _, note := range notes {
+			note.Pos += token.Pos(f.Base())
+		}
+		return notes, nil
 	}
-	return Extract(fset, file)
+	return nil, nil
 }
 
-// Extract collects all the notes present in an AST.
+// extractMod collects all the notes present in a go.mod file.
 // Each comment whose text starts with @ is parsed as a comma-separated
 // sequence of notes.
 // See the package documentation for details about the syntax of those
 // notes.
-func Extract(fset *token.FileSet, file *ast.File) ([]*Note, error) {
+// Only allow notes to appear with the following format: "//@mark()" or // @mark()
+func extractMod(fset *token.FileSet, file *modfile.File) ([]*Note, error) {
 	var notes []*Note
-	for _, g := range file.Comments {
-		for _, c := range g.List {
-			text := c.Text
-			if strings.HasPrefix(text, "/*") {
-				text = strings.TrimSuffix(text, "*/")
-			}
-			text = text[2:] // remove "//" or "/*" prefix
-
-			// Allow notes to appear within comments.
-			// For example:
-			// "// //@mark()" is valid.
-			// "// @mark()" is not valid.
-			// "// /*@mark()*/" is not valid.
-			var adjust int
-			if i := strings.Index(text, commentStart); i > 2 {
-				// Get the text before the commentStart.
-				pre := text[i-2 : i]
-				if pre != "//" {
-					continue
-				}
-				text = text[i:]
-				adjust = i
-			}
-			if !strings.HasPrefix(text, commentStart) {
+	for _, stmt := range file.Syntax.Stmt {
+		comment := stmt.Comment()
+		if comment == nil {
+			continue
+		}
+		// Handle the case for markers of `// indirect` to be on the line before
+		// the require statement.
+		// TODO(golang/go#36894): have a more intuitive approach for // indirect
+		for _, cmt := range comment.Before {
+			text, adjust := getAdjustedNote(cmt.Token)
+			if text == "" {
 				continue
 			}
-			text = text[len(commentStart):]
-			parsed, err := parse(fset, token.Pos(int(c.Pos())+4+adjust), text)
+			parsed, err := parse(fset, token.Pos(int(cmt.Start.Byte)+adjust), text)
+			if err != nil {
+				return nil, err
+			}
+			notes = append(notes, parsed...)
+		}
+		// Handle the normal case for markers on the same line.
+		for _, cmt := range comment.Suffix {
+			text, adjust := getAdjustedNote(cmt.Token)
+			if text == "" {
+				continue
+			}
+			parsed, err := parse(fset, token.Pos(int(cmt.Start.Byte)+adjust), text)
 			if err != nil {
 				return nil, err
 			}
@@ -89,6 +111,57 @@
 	return notes, nil
 }
 
+// ExtractGo collects all the notes present in an AST.
+// Each comment whose text starts with @ is parsed as a comma-separated
+// sequence of notes.
+// See the package documentation for details about the syntax of those
+// notes.
+func ExtractGo(fset *token.FileSet, file *ast.File) ([]*Note, error) {
+	var notes []*Note
+	for _, g := range file.Comments {
+		for _, c := range g.List {
+			text, adjust := getAdjustedNote(c.Text)
+			if text == "" {
+				continue
+			}
+			parsed, err := parse(fset, token.Pos(int(c.Pos())+adjust), text)
+			if err != nil {
+				return nil, err
+			}
+			notes = append(notes, parsed...)
+		}
+	}
+	return notes, nil
+}
+
+func getAdjustedNote(text string) (string, int) {
+	if strings.HasPrefix(text, "/*") {
+		text = strings.TrimSuffix(text, "*/")
+	}
+	text = text[2:] // remove "//" or "/*" prefix
+
+	// Allow notes to appear within comments.
+	// For example:
+	// "// //@mark()" is valid.
+	// "// @mark()" is not valid.
+	// "// /*@mark()*/" is not valid.
+	var adjust int
+	if i := strings.Index(text, commentStart); i > 2 {
+		// Get the text before the commentStart.
+		pre := text[i-2 : i]
+		if pre != "//" {
+			return "", 0
+		}
+		text = text[i:]
+		adjust = i
+	}
+	if !strings.HasPrefix(text, commentStart) {
+		return "", 0
+	}
+	text = text[commentStartLen:]
+	return text, commentStartLen + adjust + 1
+}
+
 const invalidToken rune = 0
 
 type tokens struct {
diff --git a/go/expect/testdata/go.mod b/go/expect/testdata/go.mod
new file mode 100644
index 0000000..d3c4f87
--- /dev/null
+++ b/go/expect/testdata/go.mod
@@ -0,0 +1,7 @@
+module αfake1α //@mark(αMarker, "αfake1α")
+
+go 1.14
+
+require golang.org/modfile v0.0.0 //@mark(βMarker, "require golang.org/modfile v0.0.0")
+//@mark(IndirectMarker, "// indirect")
+require example.com/extramodule v1.0.0 // indirect
\ No newline at end of file
diff --git a/go/internal/cgo/cgo.go b/go/internal/cgo/cgo.go
index 0f652ea..5db8b30 100644
--- a/go/internal/cgo/cgo.go
+++ b/go/internal/cgo/cgo.go
@@ -2,9 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package cgo
-
-// This file handles cgo preprocessing of files containing `import "C"`.
+// Package cgo handles cgo preprocessing of files containing `import "C"`.
 //
 // DESIGN
 //
@@ -51,6 +49,8 @@
 // its handling of function calls, analogous to the treatment of map
 // lookups in which y=m[k] and y,ok=m[k] are both legal.
 
+package cgo
+
 import (
 	"fmt"
 	"go/ast"
diff --git a/go/internal/gccgoimporter/ar.go b/go/internal/gccgoimporter/ar.go
index 7b25abb..b8869c3 100644
--- a/go/internal/gccgoimporter/ar.go
+++ b/go/internal/gccgoimporter/ar.go
@@ -84,7 +84,7 @@
 		}
 		off += arHdrSize
 
-		if bytes.Compare(hdrBuf[arFmagOff:arFmagOff+arFmagSize], []byte(arfmag)) != 0 {
+		if !bytes.Equal(hdrBuf[arFmagOff:arFmagOff+arFmagSize], []byte(arfmag)) {
 			return nil, fmt.Errorf("archive header format header (%q)", hdrBuf[:])
 		}
 
@@ -94,7 +94,7 @@
 		}
 
 		fn := hdrBuf[arNameOff : arNameOff+arNameSize]
-		if fn[0] == '/' && (fn[1] == ' ' || fn[1] == '/' || bytes.Compare(fn[:8], []byte("/SYM64/ ")) == 0) {
+		if fn[0] == '/' && (fn[1] == ' ' || fn[1] == '/' || bytes.Equal(fn[:8], []byte("/SYM64/ "))) {
 			// Archive symbol table or extended name table,
 			// which we don't care about.
 		} else {
diff --git a/go/internal/gccgoimporter/parser.go b/go/internal/gccgoimporter/parser.go
index 2a86734..29e8c60 100644
--- a/go/internal/gccgoimporter/parser.go
+++ b/go/internal/gccgoimporter/parser.go
@@ -1025,7 +1025,7 @@
 func (p *parser) parseTypes(pkg *types.Package) {
 	maxp1 := p.parseInt()
 	exportedp1 := p.parseInt()
-	p.typeList = make([]types.Type, maxp1, maxp1)
+	p.typeList = make([]types.Type, maxp1)
 
 	type typeOffset struct {
 		offset int
diff --git a/go/internal/gcimporter/gcimporter.go b/go/internal/gcimporter/gcimporter.go
index 9cf1866..8dcd8bb 100644
--- a/go/internal/gcimporter/gcimporter.go
+++ b/go/internal/gcimporter/gcimporter.go
@@ -344,7 +344,7 @@
 
 // PackageId = string_lit .
 //
-func (p *parser) parsePackageId() string {
+func (p *parser) parsePackageID() string {
 	id, err := strconv.Unquote(p.expect(scanner.String))
 	if err != nil {
 		p.error(err)
@@ -384,7 +384,7 @@
 //
 func (p *parser) parseQualifiedName() (id, name string) {
 	p.expect('@')
-	id = p.parsePackageId()
+	id = p.parsePackageID()
 	p.expect('.')
 	// Per rev f280b8a485fd (10/2/2013), qualified names may be used for anonymous fields.
 	if p.tok == '?' {
@@ -696,7 +696,7 @@
 
 	// Complete requires the type's embedded interfaces to be fully defined,
 	// but we do not define any
-	return types.NewInterface(methods, nil).Complete()
+	return newInterface(methods, nil).Complete()
 }
 
 // ChanType = ( "chan" [ "<-" ] | "<-" "chan" ) Type .
@@ -785,7 +785,7 @@
 func (p *parser) parseImportDecl() {
 	p.expectKeyword("import")
 	name := p.parsePackageName()
-	p.getPkg(p.parsePackageId(), name)
+	p.getPkg(p.parsePackageID(), name)
 }
 
 // int_lit = [ "+" | "-" ] { "0" ... "9" } .
diff --git a/go/internal/gcimporter/gcimporter11_test.go b/go/internal/gcimporter/gcimporter11_test.go
index 1818681..627300d 100644
--- a/go/internal/gcimporter/gcimporter11_test.go
+++ b/go/internal/gcimporter/gcimporter11_test.go
@@ -25,7 +25,7 @@
 	{"math.Pi", "const Pi untyped float"},
 	{"math.Sin", "func Sin(x float64) float64"},
 	{"go/ast.NotNilFilter", "func NotNilFilter(_ string, v reflect.Value) bool"},
-	{"go/internal/gcimporter.BImportData", "func BImportData(fset *go/token.FileSet, imports map[string]*go/types.Package, data []byte, path string) (_ int, pkg *go/types.Package, err error)"},
+	{"go/internal/gcimporter.FindPkg", "func FindPkg(path string, srcDir string) (filename string, id string)"},
 
 	// interfaces
 	{"context.Context", "type Context interface{Deadline() (deadline time.Time, ok bool); Done() <-chan struct{}; Err() error; Value(key interface{}) interface{}}"},
diff --git a/go/internal/packagesdriver/sizes.go b/go/internal/packagesdriver/sizes.go
index db0c9a7..5ee692d 100644
--- a/go/internal/packagesdriver/sizes.go
+++ b/go/internal/packagesdriver/sizes.go
@@ -11,11 +11,10 @@
 	"encoding/json"
 	"fmt"
 	"go/types"
-	"log"
-	"os"
 	"os/exec"
 	"strings"
-	"time"
+
+	"golang.org/x/tools/internal/gocommand"
 )
 
 var debug = false
@@ -78,97 +77,42 @@
 }
 
 func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) {
-	args := []string{"list", "-f", "{{context.GOARCH}} {{context.Compiler}}"}
-	args = append(args, buildFlags...)
-	args = append(args, "--", "unsafe")
-	stdout, stderr, err := invokeGo(ctx, env, dir, usesExportData, args...)
+	inv := gocommand.Invocation{
+		Verb:       "list",
+		Args:       []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"},
+		Env:        env,
+		BuildFlags: buildFlags,
+		WorkingDir: dir,
+	}
+	stdout, stderr, friendlyErr, rawErr := inv.RunRaw(ctx)
 	var goarch, compiler string
-	if err != nil {
-		if strings.Contains(err.Error(), "cannot find main module") {
+	if rawErr != nil {
+		if strings.Contains(rawErr.Error(), "cannot find main module") {
 			// User's running outside of a module. All bets are off. Get GOARCH and guess compiler is gc.
 			// TODO(matloob): Is this a problem in practice?
-			envout, _, enverr := invokeGo(ctx, env, dir, usesExportData, "env", "GOARCH")
+			inv := gocommand.Invocation{
+				Verb:       "env",
+				Args:       []string{"GOARCH"},
+				Env:        env,
+				WorkingDir: dir,
+			}
+			envout, enverr := inv.Run(ctx)
 			if enverr != nil {
-				return nil, err
+				return nil, enverr
 			}
 			goarch = strings.TrimSpace(envout.String())
 			compiler = "gc"
 		} else {
-			return nil, err
+			return nil, friendlyErr
 		}
 	} else {
 		fields := strings.Fields(stdout.String())
 		if len(fields) < 2 {
-			return nil, fmt.Errorf("could not parse GOARCH and Go compiler in format \"<GOARCH> <compiler>\" from stdout of go command:\n%s\ndir: %s\nstdout: <<%s>>\nstderr: <<%s>>",
-				cmdDebugStr(env, args...), dir, stdout.String(), stderr.String())
+			return nil, fmt.Errorf("could not parse GOARCH and Go compiler in format \"<GOARCH> <compiler>\":\nstdout: <<%s>>\nstderr: <<%s>>",
+				stdout.String(), stderr.String())
 		}
 		goarch = fields[0]
 		compiler = fields[1]
 	}
 	return types.SizesFor(compiler, goarch), nil
 }
-
-// invokeGo returns the stdout and stderr of a go command invocation.
-func invokeGo(ctx context.Context, env []string, dir string, usesExportData bool, args ...string) (*bytes.Buffer, *bytes.Buffer, error) {
-	if debug {
-		defer func(start time.Time) { log.Printf("%s for %v", time.Since(start), cmdDebugStr(env, args...)) }(time.Now())
-	}
-	stdout := new(bytes.Buffer)
-	stderr := new(bytes.Buffer)
-	cmd := exec.CommandContext(ctx, "go", args...)
-	// On darwin the cwd gets resolved to the real path, which breaks anything that
-	// expects the working directory to keep the original path, including the
-	// go command when dealing with modules.
-	// The Go stdlib has a special feature where if the cwd and the PWD are the
-	// same node then it trusts the PWD, so by setting it in the env for the child
-	// process we fix up all the paths returned by the go command.
-	cmd.Env = append(append([]string{}, env...), "PWD="+dir)
-	cmd.Dir = dir
-	cmd.Stdout = stdout
-	cmd.Stderr = stderr
-	if err := cmd.Run(); err != nil {
-		exitErr, ok := err.(*exec.ExitError)
-		if !ok {
-			// Catastrophic error:
-			// - executable not found
-			// - context cancellation
-			return nil, nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err)
-		}
-
-		// Export mode entails a build.
-		// If that build fails, errors appear on stderr
-		// (despite the -e flag) and the Export field is blank.
-		// Do not fail in that case.
-		if !usesExportData {
-			return nil, nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr)
-		}
-	}
-
-	// As of writing, go list -export prints some non-fatal compilation
-	// errors to stderr, even with -e set. We would prefer that it put
-	// them in the Package.Error JSON (see https://golang.org/issue/26319).
-	// In the meantime, there's nowhere good to put them, but they can
-	// be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS
-	// is set.
-	if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" {
-		fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(env, args...), stderr)
-	}
-
-	// debugging
-	if false {
-		fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(env, args...), stdout)
-	}
-
-	return stdout, stderr, nil
-}
-
-func cmdDebugStr(envlist []string, args ...string) string {
-	env := make(map[string]string)
-	for _, kv := range envlist {
-		split := strings.Split(kv, "=")
-		k, v := split[0], split[1]
-		env[k] = v
-	}
-
-	return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], args)
-}
diff --git a/go/packages/golist.go b/go/packages/golist.go
index 3089711..b4a13ef 100644
--- a/go/packages/golist.go
+++ b/go/packages/golist.go
@@ -16,13 +16,15 @@
 	"path"
 	"path/filepath"
 	"reflect"
+	"sort"
 	"strconv"
 	"strings"
 	"sync"
-	"time"
 	"unicode"
 
 	"golang.org/x/tools/go/internal/packagesdriver"
+	"golang.org/x/tools/internal/gocommand"
+	"golang.org/x/tools/internal/packagesinternal"
 )
 
 // debug controls verbose logging.
@@ -379,6 +381,7 @@
 	Imports         []string
 	ImportMap       map[string]string
 	Deps            []string
+	Module          *packagesinternal.Module
 	TestGoFiles     []string
 	TestImports     []string
 	XTestGoFiles    []string
@@ -420,6 +423,8 @@
 		return nil, err
 	}
 	seen := make(map[string]*jsonPackage)
+	pkgs := make(map[string]*Package)
+	additionalErrors := make(map[string][]Error)
 	// Decode the JSON and convert it to Package form.
 	var response driverResponse
 	for dec := json.NewDecoder(buf); dec.More(); {
@@ -460,11 +465,62 @@
 		}
 
 		if old, found := seen[p.ImportPath]; found {
-			if !reflect.DeepEqual(p, old) {
-				return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath)
+			// If one version of the package has an error, and the other doesn't, assume
+			// that this is a case where go list is reporting a fake dependency variant
+			// of the imported package: When a package tries to invalidly import another
+			// package, go list emits a variant of the imported package (with the same
+			// import path, but with an error on it, and the package will have a
+			// DepError set on it). An example of when this can happen is for imports of
+			// main packages: main packages can not be imported, but they may be
+			// separately matched and listed by another pattern.
+			// See golang.org/issue/36188 for more details.
+
+			// The plan is that eventually, hopefully in Go 1.15, the error will be
+			// reported on the importing package rather than the duplicate "fake"
+			// version of the imported package. Once all supported versions of Go
+			// have the new behavior this logic can be deleted.
+			// TODO(matloob): delete the workaround logic once all supported versions of
+			// Go return the errors on the proper package.
+
+			// There should be exactly one version of a package that doesn't have an
+			// error.
+			if old.Error == nil && p.Error == nil {
+				if !reflect.DeepEqual(p, old) {
+					return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath)
+				}
+				continue
 			}
-			// skip the duplicate
-			continue
+
+			// Determine if this package's error needs to be bubbled up.
+			// This is a hack, and we expect for go list to eventually set the error
+			// on the package.
+			if old.Error != nil {
+				var errkind string
+				if strings.Contains(old.Error.Err, "not an importable package") {
+					errkind = "not an importable package"
+				} else if strings.Contains(old.Error.Err, "use of internal package") && strings.Contains(old.Error.Err, "not allowed") {
+					errkind = "use of internal package not allowed"
+				}
+				if errkind != "" {
+					if len(old.Error.ImportStack) < 2 {
+						return nil, fmt.Errorf(`internal error: go list gave a %q error with an import stack with fewer than two elements`, errkind)
+					}
+					importingPkg := old.Error.ImportStack[len(old.Error.ImportStack)-2]
+					additionalErrors[importingPkg] = append(additionalErrors[importingPkg], Error{
+						Pos:  old.Error.Pos,
+						Msg:  old.Error.Err,
+						Kind: ListError,
+					})
+				}
+			}
+
+			// Make sure that if there's a version of the package without an error,
+			// that's the one reported to the user.
+			if old.Error == nil {
+				continue
+			}
+
+			// This package will replace the old one at the end of the loop.
 		}
 		seen[p.ImportPath] = p
 
@@ -475,6 +531,7 @@
 			CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles),
 			OtherFiles:      absJoin(p.Dir, otherFiles(p)...),
 			forTest:         p.ForTest,
+			module:          p.Module,
 		}
 
 		// Work around https://golang.org/issue/28749:
@@ -563,8 +620,18 @@
 			})
 		}
 
+		pkgs[pkg.ID] = pkg
+	}
+
+	for id, errs := range additionalErrors {
+		if p, ok := pkgs[id]; ok {
+			p.Errors = append(p.Errors, errs...)
+		}
+	}
+	for _, pkg := range pkgs {
 		response.Packages = append(response.Packages, pkg)
 	}
+	sort.Slice(response.Packages, func(i, j int) bool { return response.Packages[i].ID < response.Packages[j].ID })
 
 	return &response, nil
 }
@@ -640,29 +707,17 @@
 func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) {
 	cfg := state.cfg
 
-	stdout := new(bytes.Buffer)
-	stderr := new(bytes.Buffer)
-	goArgs := []string{verb}
-	if verb != "env" {
-		goArgs = append(goArgs, cfg.BuildFlags...)
+	inv := &gocommand.Invocation{
+		Verb:       verb,
+		Args:       args,
+		BuildFlags: cfg.BuildFlags,
+		Env:        cfg.Env,
+		Logf:       cfg.Logf,
+		WorkingDir: cfg.Dir,
 	}
-	goArgs = append(goArgs, args...)
-	cmd := exec.CommandContext(state.ctx, "go", goArgs...)
-	// On darwin the cwd gets resolved to the real path, which breaks anything that
-	// expects the working directory to keep the original path, including the
-	// go command when dealing with modules.
-	// The Go stdlib has a special feature where if the cwd and the PWD are the
-	// same node then it trusts the PWD, so by setting it in the env for the child
-	// process we fix up all the paths returned by the go command.
-	cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir)
-	cmd.Dir = cfg.Dir
-	cmd.Stdout = stdout
-	cmd.Stderr = stderr
-	defer func(start time.Time) {
-		cfg.Logf("%s for %v, stderr: <<%s>> stdout: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, goArgs...), stderr, stdout)
-	}(time.Now())
 
-	if err := cmd.Run(); err != nil {
+	stdout, stderr, _, err := inv.RunRaw(cfg.Context)
+	if err != nil {
 		// Check for 'go' executable not being found.
 		if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
 			return nil, fmt.Errorf("'go list' driver requires 'go', but %s", exec.ErrNotFound)
@@ -672,7 +727,7 @@
 		if !ok {
 			// Catastrophic error:
 			// - context cancellation
-			return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err)
+			return nil, fmt.Errorf("couldn't run 'go': %v", err)
 		}
 
 		// Old go version?
@@ -699,7 +754,12 @@
 				!strings.ContainsRune("!\"#$%&'()*,:;<=>?[\\]^`{|}\uFFFD", r)
 		}
 		if len(stderr.String()) > 0 && strings.HasPrefix(stderr.String(), "# ") {
-			if strings.HasPrefix(strings.TrimLeftFunc(stderr.String()[len("# "):], isPkgPathRune), "\n") {
+			msg := stderr.String()[len("# "):]
+			if strings.HasPrefix(strings.TrimLeftFunc(msg, isPkgPathRune), "\n") {
+				return stdout, nil
+			}
+			// Treat pkg-config errors as a special case (golang.org/issue/36770).
+			if strings.HasPrefix(msg, "pkg-config") {
 				return stdout, nil
 			}
 		}
@@ -788,16 +848,6 @@
 			return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr)
 		}
 	}
-
-	// As of writing, go list -export prints some non-fatal compilation
-	// errors to stderr, even with -e set. We would prefer that it put
-	// them in the Package.Error JSON (see https://golang.org/issue/26319).
-	// In the meantime, there's nowhere good to put them, but they can
-	// be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS
-	// is set.
-	if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" {
-		fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, args...), stderr)
-	}
 	return stdout, nil
 }
 
diff --git a/go/packages/golist_overlay.go b/go/packages/golist_overlay.go
index 3760744..7974a6c 100644
--- a/go/packages/golist_overlay.go
+++ b/go/packages/golist_overlay.go
@@ -50,7 +50,6 @@
 		}
 		return overlayFiles[i] < overlayFiles[j]
 	})
-
 	for _, opath := range overlayFiles {
 		contents := state.cfg.Overlay[opath]
 		base := filepath.Base(opath)
@@ -82,14 +81,8 @@
 					testVariantOf = p
 					continue nextPackage
 				}
+				// We must have already seen the package of which this is a test variant.
 				if pkg != nil && p != pkg && pkg.PkgPath == p.PkgPath {
-					// If we've already seen the test variant,
-					// make sure to label which package it is a test variant of.
-					if hasTestFiles(pkg) {
-						testVariantOf = p
-						continue nextPackage
-					}
-					// If we have already seen the package of which this is a test variant.
 					if hasTestFiles(p) {
 						testVariantOf = pkg
 					}
diff --git a/go/packages/packages.go b/go/packages/packages.go
index 586c714..1ac6558 100644
--- a/go/packages/packages.go
+++ b/go/packages/packages.go
@@ -299,12 +299,18 @@
 
 	// forTest is the package under test, if any.
 	forTest string
+
+	// module is the module information for the package if it exists.
+	module *packagesinternal.Module
 }
 
 func init() {
 	packagesinternal.GetForTest = func(p interface{}) string {
 		return p.(*Package).forTest
 	}
+	packagesinternal.GetModule = func(p interface{}) *packagesinternal.Module {
+		return p.(*Package).module
+	}
 }
 
 // An Error describes a problem with a package's metadata, syntax, or types.
diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go
index 81e0d43..0cc4379 100644
--- a/go/packages/packages_test.go
+++ b/go/packages/packages_test.go
@@ -983,10 +983,19 @@
 		packages.NeedImports |
 		packages.NeedDeps |
 		packages.NeedTypesSizes
-	initial, err := packages.Load(exported.Config, fmt.Sprintf("file=%s", exported.File("golang.org/fake", "c/c.go")))
+	pkgs, err := packages.Load(exported.Config, fmt.Sprintf("file=%s", exported.File("golang.org/fake", "c/c.go")))
 	if err != nil {
 		t.Error(err)
 	}
+
+	// Find package golang.org/fake/c
+	sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].ID < pkgs[j].ID })
+	pkgc := pkgs[0]
+	if pkgc.ID != "golang.org/fake/c" {
+		t.Errorf("expected first package in sorted list to be \"golang.org/fake/c\", got %v", pkgc.ID)
+	}
+
+	// Make sure golang.org/fake/c imports net/http, as per the overlay.
 	contains := func(imports map[string]*packages.Package, wantImport string) bool {
 		for imp := range imports {
 			if imp == wantImport {
@@ -995,11 +1004,11 @@
 		}
 		return false
 	}
-	for _, pkg := range initial {
-		if !contains(pkg.Imports, "net/http") {
-			t.Errorf("expected %s import in %s", "net/http", pkg.ID)
-		}
+	if !contains(pkgc.Imports, "net/http") {
+		t.Errorf("expected import of %s in package %s, got the following imports: %v",
+			"net/http", pkgc.ID, pkgc.Imports)
 	}
+
 }
 
 func TestNewPackagesInOverlay(t *testing.T) { packagestest.TestAll(t, testNewPackagesInOverlay) }
@@ -1105,7 +1114,9 @@
 }
 
 // Test that we can create a package and its test package in an overlay.
-func TestOverlayNewPackageAndTest(t *testing.T) { packagestest.TestAll(t, testOverlayNewPackageAndTest) }
+func TestOverlayNewPackageAndTest(t *testing.T) {
+	packagestest.TestAll(t, testOverlayNewPackageAndTest)
+}
 func testOverlayNewPackageAndTest(t *testing.T, exporter packagestest.Exporter) {
 	exported := packagestest.Export(t, exporter, []packagestest.Module{
 		{
@@ -2409,6 +2420,38 @@
 	}, nil)
 }
 
+func TestMultiplePackageVersionsIssue36188(t *testing.T) {
+	packagestest.TestAll(t, testMultiplePackageVersionsIssue36188)
+}
+
+func testMultiplePackageVersionsIssue36188(t *testing.T, exporter packagestest.Exporter) {
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go": `package a; import _ "golang.org/fake/b"`,
+			"b/b.go": `package main`,
+		}}})
+	pkgs, err := packages.Load(exported.Config, "golang.org/fake/a", "golang.org/fake/b")
+	if err != nil {
+		t.Fatal(err)
+	}
+	sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].ID < pkgs[j].ID })
+	if len(pkgs) != 2 {
+		t.Fatalf("expected two packages, got %v", pkgs)
+	}
+	if pkgs[0].ID != "golang.org/fake/a" && pkgs[1].ID != "golang.org/fake/b" {
+		t.Fatalf(`expected (sorted) IDs "golang.org/fake/a" and "golang.org/fake/b", got %q and %q`,
+			pkgs[0].ID, pkgs[1].ID)
+	}
+	if pkgs[0].Errors == nil {
+		t.Errorf(`expected error on package "golang.org/fake/a", got none`)
+	}
+	if pkgs[1].Errors != nil {
+		t.Errorf(`expected no errors on package "golang.org/fake/b", got %v`, pkgs[1].Errors)
+	}
+	defer exported.Cleanup()
+}
+
 func TestLoadModeStrings(t *testing.T) {
 	testcases := []struct {
 		mode     packages.LoadMode
diff --git a/go/packages/packagescgo_test.go b/go/packages/packagescgo_test.go
index ed51522..a149309 100644
--- a/go/packages/packagescgo_test.go
+++ b/go/packages/packagescgo_test.go
@@ -8,7 +8,10 @@
 
 import (
 	"fmt"
+	"io/ioutil"
 	"os"
+	"os/exec"
+	"path/filepath"
 	"runtime"
 	"strings"
 	"testing"
@@ -66,10 +69,6 @@
 	}
 }
 
-func TestCgoNoSyntax(t *testing.T) {
-	packagestest.TestAll(t, testCgoNoSyntax)
-}
-
 // Stolen from internal/testenv package in core.
 // hasGoBuild reports whether the current system can build programs with ``go build''
 // and then run them with os.StartProcess or exec.Command.
@@ -92,6 +91,9 @@
 	return true
 }
 
+func TestCgoNoSyntax(t *testing.T) {
+	packagestest.TestAll(t, testCgoNoSyntax)
+}
 func testCgoNoSyntax(t *testing.T, exporter packagestest.Exporter) {
 	// The android builders have a complex setup which causes this test to fail. See discussion on
 	// golang.org/cl/214943 for more details.
@@ -133,3 +135,78 @@
 		})
 	}
 }
+
+func TestCgoBadPkgConfig(t *testing.T) {
+	packagestest.TestAll(t, testCgoBadPkgConfig)
+}
+func testCgoBadPkgConfig(t *testing.T, exporter packagestest.Exporter) {
+	if !hasGoBuild() {
+		t.Skip("this test can't run on platforms without go build. See discussion on golang.org/cl/214943 for more details.")
+	}
+
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"c/c.go": `package c
+
+// #cgo pkg-config: --cflags --  foo
+import "C"`,
+		},
+	}})
+
+	dir := buildFakePkgconfig(t, exported.Config.Env)
+	defer os.RemoveAll(dir)
+	env := exported.Config.Env
+	for i, v := range env {
+		if strings.HasPrefix(v, "PATH=") {
+			env[i] = "PATH=" + dir + string(os.PathListSeparator) + v[len("PATH="):]
+		}
+	}
+
+	exported.Config.Env = append(exported.Config.Env, "CGO_ENABLED=1")
+
+	exported.Config.Mode = packages.NeedName | packages.NeedCompiledGoFiles
+	pkgs, err := packages.Load(exported.Config, "golang.org/fake/c")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(pkgs) != 1 {
+		t.Fatalf("Expected 1 package, got %v", pkgs)
+	}
+	if pkgs[0].Name != "c" {
+		t.Fatalf("Expected package to have name \"c\", got %q", pkgs[0].Name)
+	}
+}
+
+func buildFakePkgconfig(t *testing.T, env []string) string {
+	tmpdir, err := ioutil.TempDir("", "fakepkgconfig")
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = ioutil.WriteFile(filepath.Join(tmpdir, "pkg-config.go"), []byte(`
+package main
+
+import "fmt"
+import "os"
+
+func main() {
+	fmt.Fprintln(os.Stderr, "bad")
+	os.Exit(2)
+}
+`), 0644)
+	if err != nil {
+		os.RemoveAll(tmpdir)
+		t.Fatal(err)
+	}
+	cmd := exec.Command("go", "build", "-o", "pkg-config", "pkg-config.go")
+	cmd.Dir = tmpdir
+	cmd.Env = env
+
+	if b, err := cmd.CombinedOutput(); err != nil {
+		os.RemoveAll(tmpdir)
+		fmt.Println(os.Environ())
+		t.Log(string(b))
+		t.Fatal(err)
+	}
+	return tmpdir
+}
diff --git a/go/packages/packagestest/expect.go b/go/packages/packagestest/expect.go
index ad769ff..ddbec44 100644
--- a/go/packages/packagestest/expect.go
+++ b/go/packages/packagestest/expect.go
@@ -7,9 +7,12 @@
 import (
 	"fmt"
 	"go/token"
+	"io/ioutil"
+	"os"
 	"path/filepath"
 	"reflect"
 	"regexp"
+	"strings"
 
 	"golang.org/x/tools/go/expect"
 	"golang.org/x/tools/go/packages"
@@ -161,10 +164,47 @@
 			notes = append(notes, l...)
 		}
 	}
+	if _, ok := e.written[e.primary]; !ok {
+		e.notes = notes
+		return nil
+	}
+	// Check go.mod markers regardless of mode, we need to do this so that our marker count
+	// matches the counts in the summary.txt.golden file for the test directory.
+	if gomod, found := e.written[e.primary]["go.mod"]; found {
+		// If we are in Modules mode, then we need to check the contents of the go.mod.temp.
+		if e.Exporter == Modules {
+			gomod += ".temp"
+		}
+		l, err := goModMarkers(e, gomod)
+		if err != nil {
+			return fmt.Errorf("failed to extract expectations for go.mod: %v", err)
+		}
+		notes = append(notes, l...)
+	}
 	e.notes = notes
 	return nil
 }
 
+func goModMarkers(e *Exported, gomod string) ([]*expect.Note, error) {
+	if _, err := os.Stat(gomod); os.IsNotExist(err) {
+		// If there is no go.mod file, we want to be able to continue.
+		return nil, nil
+	}
+	content, err := e.FileContents(gomod)
+	if err != nil {
+		return nil, err
+	}
+	if e.Exporter == GOPATH {
+		return expect.Parse(e.ExpectFileSet, gomod, content)
+	}
+	gomod = strings.TrimSuffix(gomod, ".temp")
+	// If we are in Modules mode, copy the original contents file back into go.mod
+	if err := ioutil.WriteFile(gomod, content, 0644); err != nil {
+		return nil, nil
+	}
+	return expect.Parse(e.ExpectFileSet, gomod, content)
+}
+
 func (e *Exported) getMarkers() error {
 	if e.markers != nil {
 		return nil
diff --git a/go/packages/packagestest/export.go b/go/packages/packagestest/export.go
index 989e409..6b03926 100644
--- a/go/packages/packagestest/export.go
+++ b/go/packages/packagestest/export.go
@@ -118,11 +118,12 @@
 
 	ExpectFileSet *token.FileSet // The file set used when parsing expectations
 
-	temp    string                       // the temporary directory that was exported to
-	primary string                       // the first non GOROOT module that was exported
-	written map[string]map[string]string // the full set of exported files
-	notes   []*expect.Note               // The list of expectations extracted from go source files
-	markers map[string]span.Range        // The set of markers extracted from go source files
+	Exporter Exporter                     // the exporter used
+	temp     string                       // the temporary directory that was exported to
+	primary  string                       // the first non GOROOT module that was exported
+	written  map[string]map[string]string // the full set of exported files
+	notes    []*expect.Note               // The list of expectations extracted from go source files
+	markers  map[string]span.Range        // The set of markers extracted from go source files
 }
 
 // Exporter implementations are responsible for converting from the generic description of some
@@ -200,6 +201,7 @@
 			Mode:    packages.LoadImports,
 		},
 		Modules:       modules,
+		Exporter:      exporter,
 		temp:          temp,
 		primary:       modules[0].Name,
 		written:       map[string]map[string]string{},
@@ -303,6 +305,136 @@
 	}
 }
 
+// GroupFilesByModules attempts to map directories to the modules within each directory.
+// This function assumes that the folder is structured in the following way:
+// - dir
+//   - primarymod
+//     - .go files
+//		 - packages
+//		 - go.mod (optional)
+//	 - modules
+// 		 - repoa
+//		   - mod1
+//	       - .go files
+//			   -  packages
+//		  	 - go.mod (optional)
+// It scans the directory tree anchored at root and adds a Copy writer to the
+// map for every file found.
+// This is to enable the common case in tests where you have a full copy of the
+// package in your testdata.
+func GroupFilesByModules(root string) ([]Module, error) {
+	root = filepath.FromSlash(root)
+	primarymodPath := filepath.Join(root, "primarymod")
+
+	_, err := os.Stat(primarymodPath)
+	if os.IsNotExist(err) {
+		return nil, fmt.Errorf("could not find primarymod folder within %s", root)
+	}
+
+	primarymod := &Module{
+		Name:    root,
+		Files:   make(map[string]interface{}),
+		Overlay: make(map[string][]byte),
+	}
+	mods := map[string]*Module{
+		root: primarymod,
+	}
+	modules := []Module{*primarymod}
+
+	if err := filepath.Walk(primarymodPath, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		if info.IsDir() {
+			return nil
+		}
+		fragment, err := filepath.Rel(primarymodPath, path)
+		if err != nil {
+			return err
+		}
+		primarymod.Files[filepath.ToSlash(fragment)] = Copy(path)
+		return nil
+	}); err != nil {
+		return nil, err
+	}
+
+	modulesPath := filepath.Join(root, "modules")
+	if _, err := os.Stat(modulesPath); os.IsNotExist(err) {
+		return modules, nil
+	}
+
+	var currentRepo, currentModule string
+	updateCurrentModule := func(dir string) {
+		if dir == currentModule {
+			return
+		}
+		// Handle the case where we step into a nested directory that is a module
+		// and then step out into the parent which is also a module.
+		// Example:
+		// - repoa
+		//   - moda
+		//     - go.mod
+		//     - v2
+		//       - go.mod
+		//     - what.go
+		//   - modb
+		for dir != root {
+			if mods[dir] != nil {
+				currentModule = dir
+				return
+			}
+			dir = filepath.Dir(dir)
+		}
+	}
+
+	if err := filepath.Walk(modulesPath, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		enclosingDir := filepath.Dir(path)
+		// If the path is not a directory, then we want to add the path to
+		// the files map of the currentModule.
+		if !info.IsDir() {
+			updateCurrentModule(enclosingDir)
+			fragment, err := filepath.Rel(currentModule, path)
+			if err != nil {
+				return err
+			}
+			mods[currentModule].Files[filepath.ToSlash(fragment)] = Copy(path)
+			return nil
+		}
+		// If the path is a directory and it's enclosing folder is equal to
+		// the modules folder, then the path is a new repo.
+		if enclosingDir == modulesPath {
+			currentRepo = path
+			return nil
+		}
+		// If the path is a directory and it's enclosing folder is not the same
+		// as the current repo and it is not of the form `v1`,`v2`,...
+		// then the path is a folder/package of the current module.
+		if enclosingDir != currentRepo && !versionSuffixRE.MatchString(filepath.Base(path)) {
+			return nil
+		}
+		// If the path is a directory and it's enclosing folder is the current repo
+		// then the path is a new module.
+		module, err := filepath.Rel(modulesPath, path)
+		if err != nil {
+			return err
+		}
+		mods[path] = &Module{
+			Name:    filepath.ToSlash(module),
+			Files:   make(map[string]interface{}),
+			Overlay: make(map[string][]byte),
+		}
+		currentModule = path
+		modules = append(modules, *mods[path])
+		return nil
+	}); err != nil {
+		return nil, err
+	}
+	return modules, nil
+}
+
 // MustCopyFileTree returns a file set for a module based on a real directory tree.
 // It scans the directory tree anchored at root and adds a Copy writer to the
 // map for every file found.
diff --git a/go/packages/packagestest/export_test.go b/go/packages/packagestest/export_test.go
index e2a2355..e7bf124 100644
--- a/go/packages/packagestest/export_test.go
+++ b/go/packages/packagestest/export_test.go
@@ -32,6 +32,16 @@
 	Files: map[string]interface{}{
 		"other/a.go": "package fake2",
 	},
+}, {
+	Name: "golang.org/fake3@v1.0.0",
+	Files: map[string]interface{}{
+		"other/a.go": "package fake3",
+	},
+}, {
+	Name: "golang.org/fake3@v1.1.0",
+	Files: map[string]interface{}{
+		"other/a.go": "package fake3",
+	},
 }}
 
 type fileTest struct {
@@ -74,3 +84,98 @@
 		}
 	}
 }
+
+func TestGroupFilesByModules(t *testing.T) {
+	for _, tt := range []struct {
+		testdir string
+		want    []packagestest.Module
+	}{
+		{
+			testdir: "testdata/groups/one",
+			want: []packagestest.Module{
+				{
+					Name: "testdata/groups/one",
+					Files: map[string]interface{}{
+						"main.go": true,
+					},
+				},
+				{
+					Name: "example.com/extra",
+					Files: map[string]interface{}{
+						"help.go": true,
+					},
+				},
+			},
+		},
+		{
+			testdir: "testdata/groups/two",
+			want: []packagestest.Module{
+				{
+					Name: "testdata/groups/two",
+					Files: map[string]interface{}{
+						"main.go":      true,
+						"expect/yo.go": true,
+					},
+				},
+				{
+					Name: "example.com/extra",
+					Files: map[string]interface{}{
+						"yo.go":        true,
+						"geez/help.go": true,
+					},
+				},
+				{
+					Name: "example.com/extra/v2",
+					Files: map[string]interface{}{
+						"me.go":        true,
+						"geez/help.go": true,
+					},
+				},
+				{
+					Name: "example.com/tempmod",
+					Files: map[string]interface{}{
+						"main.go": true,
+					},
+				},
+				{
+					Name: "example.com/what@v1.0.0",
+					Files: map[string]interface{}{
+						"main.go": true,
+					},
+				},
+				{
+					Name: "example.com/what@v1.1.0",
+					Files: map[string]interface{}{
+						"main.go": true,
+					},
+				},
+			},
+		},
+	} {
+		t.Run(tt.testdir, func(t *testing.T) {
+			got, err := packagestest.GroupFilesByModules(tt.testdir)
+			if err != nil {
+				t.Fatalf("could not group files %v", err)
+			}
+			if len(got) != len(tt.want) {
+				t.Fatalf("%s: wanted %d modules but got %d", tt.testdir, len(tt.want), len(got))
+			}
+			for i, w := range tt.want {
+				g := got[i]
+				if filepath.FromSlash(g.Name) != filepath.FromSlash(w.Name) {
+					t.Fatalf("%s: wanted module[%d].Name to be %s but got %s", tt.testdir, i, filepath.FromSlash(w.Name), filepath.FromSlash(g.Name))
+				}
+				for fh := range w.Files {
+					if _, ok := g.Files[fh]; !ok {
+						t.Fatalf("%s, module[%d]: wanted %s but could not find", tt.testdir, i, fh)
+					}
+				}
+				for fh := range g.Files {
+					if _, ok := w.Files[fh]; !ok {
+						t.Fatalf("%s, module[%d]: found unexpected file %s", tt.testdir, i, fh)
+					}
+				}
+			}
+		})
+	}
+}
diff --git a/go/packages/packagestest/modules.go b/go/packages/packagestest/modules.go
index 6d46d9b..8bf830a 100644
--- a/go/packages/packagestest/modules.go
+++ b/go/packages/packagestest/modules.go
@@ -6,16 +6,16 @@
 
 import (
 	"archive/zip"
-	"bytes"
+	"context"
 	"fmt"
 	"io/ioutil"
 	"os"
-	"os/exec"
 	"path"
 	"path/filepath"
 	"regexp"
+	"strings"
 
-	"golang.org/x/tools/go/packages"
+	"golang.org/x/tools/internal/gocommand"
 )
 
 // Modules is the exporter that produces module layouts.
@@ -40,6 +40,11 @@
 
 type modules struct{}
 
+type moduleAtVersion struct {
+	module  string
+	version string
+}
+
 func (modules) Name() string {
 	return "Modules"
 }
@@ -63,13 +68,45 @@
 	if exported.written[exported.primary] == nil {
 		exported.written[exported.primary] = make(map[string]string)
 	}
+
+	// Create a map of modulepath -> {module, version} for modulepaths
+	// that are of the form `repoa/mod1@v1.1.0`.
+	versions := make(map[string]moduleAtVersion)
+	for module := range exported.written {
+		if splt := strings.Split(module, "@"); len(splt) > 1 {
+			versions[module] = moduleAtVersion{
+				module:  splt[0],
+				version: splt[1],
+			}
+		}
+	}
+
+	// If the primary module already has a go.mod, write the contents to a temp
+	// go.mod for now and then we will reset it when we are getting all the markers.
+	if gomod := exported.written[exported.primary]["go.mod"]; gomod != "" {
+		contents, err := ioutil.ReadFile(gomod)
+		if err != nil {
+			return err
+		}
+		if err := ioutil.WriteFile(gomod+".temp", contents, 0644); err != nil {
+			return err
+		}
+	}
+
 	exported.written[exported.primary]["go.mod"] = filepath.Join(primaryDir, "go.mod")
 	primaryGomod := "module " + exported.primary + "\nrequire (\n"
 	for other := range exported.written {
 		if other == exported.primary {
 			continue
 		}
-		primaryGomod += fmt.Sprintf("\t%v %v\n", other, moduleVersion(other))
+		version := moduleVersion(other)
+		// If other is of the form `repo1/mod1@v1.1.0`,
+		// then we need to extract the module and the version.
+		if v, ok := versions[other]; ok {
+			other = v.module
+			version = v.version
+		}
+		primaryGomod += fmt.Sprintf("\t%v %v\n", other, version)
 	}
 	primaryGomod += ")\n"
 	if err := ioutil.WriteFile(filepath.Join(primaryDir, "go.mod"), []byte(primaryGomod), 0644); err != nil {
@@ -87,8 +124,12 @@
 			continue
 		}
 		dir := moduleDir(exported, module)
-
 		modfile := filepath.Join(dir, "go.mod")
+		// If other is of the form `repo1/mod1@v1.1.0`,
+		// then we need to extract the module name without the version.
+		if v, ok := versions[module]; ok {
+			module = v.module
+		}
 		if err := ioutil.WriteFile(modfile, []byte("module "+module+"\n"), 0644); err != nil {
 			return err
 		}
@@ -101,9 +142,15 @@
 		if module == exported.primary {
 			continue
 		}
+		version := moduleVersion(module)
+		// If other is of the form `repo1/mod1@v1.1.0`,
+		// then we need to extract the module and the version.
+		if v, ok := versions[module]; ok {
+			module = v.module
+			version = v.version
+		}
 		dir := filepath.Join(proxyDir, module, "@v")
-
-		if err := writeModuleProxy(dir, module, files); err != nil {
+		if err := writeModuleProxy(dir, module, version, files); err != nil {
 			return fmt.Errorf("creating module proxy dir for %v: %v", module, err)
 		}
 	}
@@ -122,22 +169,33 @@
 
 	// Run go mod download to recreate the mod cache dir with all the extra
 	// stuff in cache. All the files created by Export should be recreated.
-	if err := invokeGo(exported.Config, "mod", "download"); err != nil {
+	inv := gocommand.Invocation{
+		Verb:       "mod",
+		Args:       []string{"download"},
+		Env:        exported.Config.Env,
+		BuildFlags: exported.Config.BuildFlags,
+		WorkingDir: exported.Config.Dir,
+	}
+	if _, err := inv.Run(context.Background()); err != nil {
 		return err
 	}
-
 	return nil
 }
 
 // writeModuleProxy creates a directory in the proxy dir for a module.
-func writeModuleProxy(dir, module string, files map[string]string) error {
-	ver := moduleVersion(module)
+func writeModuleProxy(dir, module, ver string, files map[string]string) error {
 	if err := os.MkdirAll(dir, 0755); err != nil {
 		return err
 	}
 
-	// list file. Just the single version.
-	if err := ioutil.WriteFile(filepath.Join(dir, "list"), []byte(ver+"\n"), 0644); err != nil {
+	// the modproxy checks for versions by looking at the "list" file,
+	// since we are supporting multiple versions, create the file if it does not exist or
+	// append the version number to the preexisting file.
+	f, err := os.OpenFile(filepath.Join(dir, "list"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+	if err != nil {
+		return err
+	}
+	if _, err := f.WriteString(ver + "\n"); err != nil {
 		return err
 	}
 
@@ -157,7 +215,7 @@
 	}
 
 	// zip of all the source files.
-	f, err := os.OpenFile(filepath.Join(dir, ver+".zip"), os.O_CREATE|os.O_WRONLY, 0644)
+	f, err = os.OpenFile(filepath.Join(dir, ver+".zip"), os.O_CREATE|os.O_WRONLY, 0644)
 	if err != nil {
 		return err
 	}
@@ -185,29 +243,18 @@
 	return nil
 }
 
-func invokeGo(cfg *packages.Config, args ...string) error {
-	stdout := new(bytes.Buffer)
-	stderr := new(bytes.Buffer)
-	cmd := exec.Command("go", args...)
-	cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir)
-	cmd.Dir = cfg.Dir
-	cmd.Stdout = stdout
-	cmd.Stderr = stderr
-	if err := cmd.Run(); err != nil {
-		return fmt.Errorf("go %v: %s: %s", args, err, stderr)
-	}
-	return nil
-}
-
 func modCache(exported *Exported) string {
 	return filepath.Join(exported.temp, "modcache/pkg/mod")
 }
 
 func primaryDir(exported *Exported) string {
-	return filepath.Join(exported.temp, "primarymod", path.Base(exported.primary))
+	return filepath.Join(exported.temp, path.Base(exported.primary))
 }
 
 func moduleDir(exported *Exported, module string) string {
+	if strings.Contains(module, "@") {
+		return filepath.Join(modCache(exported), module)
+	}
 	return filepath.Join(modCache(exported), path.Dir(module), path.Base(module)+"@"+moduleVersion(module))
 }
 
diff --git a/go/packages/packagestest/modules_test.go b/go/packages/packagestest/modules_test.go
index 8a85ae1..5d13176 100644
--- a/go/packages/packagestest/modules_test.go
+++ b/go/packages/packagestest/modules_test.go
@@ -17,16 +17,18 @@
 	exported := packagestest.Export(t, packagestest.Modules, testdata)
 	defer exported.Cleanup()
 	// Check that the cfg contains all the right bits
-	var expectDir = filepath.Join(exported.Temp(), "primarymod/fake1")
+	var expectDir = filepath.Join(exported.Temp(), "fake1")
 	if exported.Config.Dir != expectDir {
 		t.Errorf("Got working directory %v expected %v", exported.Config.Dir, expectDir)
 	}
 	checkFiles(t, exported, []fileTest{
-		{"golang.org/fake1", "go.mod", "primarymod/fake1/go.mod", nil},
-		{"golang.org/fake1", "a.go", "primarymod/fake1/a.go", checkLink("testdata/a.go")},
-		{"golang.org/fake1", "b.go", "primarymod/fake1/b.go", checkContent("package fake1")},
+		{"golang.org/fake1", "go.mod", "fake1/go.mod", nil},
+		{"golang.org/fake1", "a.go", "fake1/a.go", checkLink("testdata/a.go")},
+		{"golang.org/fake1", "b.go", "fake1/b.go", checkContent("package fake1")},
 		{"golang.org/fake2", "go.mod", "modcache/pkg/mod/golang.org/fake2@v1.0.0/go.mod", nil},
 		{"golang.org/fake2", "other/a.go", "modcache/pkg/mod/golang.org/fake2@v1.0.0/other/a.go", checkContent("package fake2")},
 		{"golang.org/fake2/v2", "other/a.go", "modcache/pkg/mod/golang.org/fake2/v2@v2.0.0/other/a.go", checkContent("package fake2")},
+		{"golang.org/fake3@v1.1.0", "other/a.go", "modcache/pkg/mod/golang.org/fake3@v1.1.0/other/a.go", checkContent("package fake3")},
+		{"golang.org/fake3@v1.0.0", "other/a.go", "modcache/pkg/mod/golang.org/fake3@v1.0.0/other/a.go", nil},
 	})
 }
diff --git a/go/packages/packagestest/testdata/groups/one/modules/example.com/extra/help.go b/go/packages/packagestest/testdata/groups/one/modules/example.com/extra/help.go
new file mode 100644
index 0000000..ee03293
--- /dev/null
+++ b/go/packages/packagestest/testdata/groups/one/modules/example.com/extra/help.go
@@ -0,0 +1 @@
+package extra
\ No newline at end of file
diff --git a/go/packages/packagestest/testdata/groups/one/primarymod/main.go b/go/packages/packagestest/testdata/groups/one/primarymod/main.go
new file mode 100644
index 0000000..54fe6e8
--- /dev/null
+++ b/go/packages/packagestest/testdata/groups/one/primarymod/main.go
@@ -0,0 +1 @@
+package one
\ No newline at end of file
diff --git a/go/packages/packagestest/testdata/groups/two/modules/example.com/extra/geez/help.go b/go/packages/packagestest/testdata/groups/two/modules/example.com/extra/geez/help.go
new file mode 100644
index 0000000..930ffdc
--- /dev/null
+++ b/go/packages/packagestest/testdata/groups/two/modules/example.com/extra/geez/help.go
@@ -0,0 +1 @@
+package example.com/extra/geez
\ No newline at end of file
diff --git a/go/packages/packagestest/testdata/groups/two/modules/example.com/extra/v2/geez/help.go b/go/packages/packagestest/testdata/groups/two/modules/example.com/extra/v2/geez/help.go
new file mode 100644
index 0000000..930ffdc
--- /dev/null
+++ b/go/packages/packagestest/testdata/groups/two/modules/example.com/extra/v2/geez/help.go
@@ -0,0 +1 @@
+package example.com/extra/geez
\ No newline at end of file
diff --git a/go/packages/packagestest/testdata/groups/two/modules/example.com/extra/v2/me.go b/go/packages/packagestest/testdata/groups/two/modules/example.com/extra/v2/me.go
new file mode 100644
index 0000000..6a8c7d3
--- /dev/null
+++ b/go/packages/packagestest/testdata/groups/two/modules/example.com/extra/v2/me.go
@@ -0,0 +1 @@
+package example.com/extra
\ No newline at end of file
diff --git a/go/packages/packagestest/testdata/groups/two/modules/example.com/extra/yo.go b/go/packages/packagestest/testdata/groups/two/modules/example.com/extra/yo.go
new file mode 100644
index 0000000..6a8c7d3
--- /dev/null
+++ b/go/packages/packagestest/testdata/groups/two/modules/example.com/extra/yo.go
@@ -0,0 +1 @@
+package example.com/extra
\ No newline at end of file
diff --git a/go/packages/packagestest/testdata/groups/two/modules/example.com/tempmod/main.go b/go/packages/packagestest/testdata/groups/two/modules/example.com/tempmod/main.go
new file mode 100644
index 0000000..85dbfa7
--- /dev/null
+++ b/go/packages/packagestest/testdata/groups/two/modules/example.com/tempmod/main.go
@@ -0,0 +1 @@
+package example.com/tempmod
\ No newline at end of file
diff --git a/go/packages/packagestest/testdata/groups/two/modules/example.com/what@v1.0.0/main.go b/go/packages/packagestest/testdata/groups/two/modules/example.com/what@v1.0.0/main.go
new file mode 100644
index 0000000..4723ee6
--- /dev/null
+++ b/go/packages/packagestest/testdata/groups/two/modules/example.com/what@v1.0.0/main.go
@@ -0,0 +1 @@
+package example.com/what
\ No newline at end of file
diff --git a/go/packages/packagestest/testdata/groups/two/modules/example.com/what@v1.1.0/main.go b/go/packages/packagestest/testdata/groups/two/modules/example.com/what@v1.1.0/main.go
new file mode 100644
index 0000000..4723ee6
--- /dev/null
+++ b/go/packages/packagestest/testdata/groups/two/modules/example.com/what@v1.1.0/main.go
@@ -0,0 +1 @@
+package example.com/what
\ No newline at end of file
diff --git a/go/packages/packagestest/testdata/groups/two/primarymod/expect/yo.go b/go/packages/packagestest/testdata/groups/two/primarymod/expect/yo.go
new file mode 100644
index 0000000..a706778
--- /dev/null
+++ b/go/packages/packagestest/testdata/groups/two/primarymod/expect/yo.go
@@ -0,0 +1 @@
+package expect
\ No newline at end of file
diff --git a/go/packages/packagestest/testdata/groups/two/primarymod/main.go b/go/packages/packagestest/testdata/groups/two/primarymod/main.go
new file mode 100644
index 0000000..0b26334
--- /dev/null
+++ b/go/packages/packagestest/testdata/groups/two/primarymod/main.go
@@ -0,0 +1 @@
+package two
\ No newline at end of file
diff --git a/go/pointer/api.go b/go/pointer/api.go
index 3c5c6dc..2a13a67 100644
--- a/go/pointer/api.go
+++ b/go/pointer/api.go
@@ -230,11 +230,11 @@
 	if s.pts != nil {
 		var space [50]int
 		for _, x := range s.pts.AppendTo(space[:0]) {
-			ifaceObjId := nodeid(x)
-			if !s.a.isTaggedObject(ifaceObjId) {
+			ifaceObjID := nodeid(x)
+			if !s.a.isTaggedObject(ifaceObjID) {
 				continue // !CanHaveDynamicTypes(tDyn)
 			}
-			tDyn, v, indirect := s.a.taggedValue(ifaceObjId)
+			tDyn, v, indirect := s.a.taggedValue(ifaceObjID)
 			if indirect {
 				panic("indirect tagged object") // implement later
 			}
@@ -251,13 +251,13 @@
 
 // Intersects reports whether this points-to set and the
 // argument points-to set contain common members.
-func (x PointsToSet) Intersects(y PointsToSet) bool {
-	if x.pts == nil || y.pts == nil {
+func (s PointsToSet) Intersects(y PointsToSet) bool {
+	if s.pts == nil || y.pts == nil {
 		return false
 	}
 	// This takes Θ(|x|+|y|) time.
 	var z intsets.Sparse
-	z.Intersection(&x.pts.Sparse, &y.pts.Sparse)
+	z.Intersection(&s.pts.Sparse, &y.pts.Sparse)
 	return !z.IsEmpty()
 }
 
diff --git a/go/pointer/gen.go b/go/pointer/gen.go
index f2a5171..5d2d621 100644
--- a/go/pointer/gen.go
+++ b/go/pointer/gen.go
@@ -20,7 +20,7 @@
 )
 
 var (
-	tEface     = types.NewInterface(nil, nil).Complete()
+	tEface     = types.NewInterfaceType(nil, nil).Complete()
 	tInvalid   = types.Typ[types.Invalid]
 	tUnsafePtr = types.Typ[types.UnsafePointer]
 )
@@ -503,8 +503,7 @@
 	y := instr.Call.Args[1]
 	tArray := sliceToArray(instr.Call.Args[0].Type())
 
-	var w nodeid
-	w = a.nextNode()
+	w := a.nextNode()
 	a.addNodes(tArray, "append")
 	a.endObject(w, cgn, instr)
 
diff --git a/go/pointer/hvn.go b/go/pointer/hvn.go
index 192e405..52fd479 100644
--- a/go/pointer/hvn.go
+++ b/go/pointer/hvn.go
@@ -391,10 +391,9 @@
 		if debugHVNVerbose && h.log != nil {
 			fmt.Fprintf(h.log, "\to%d --> o%d\n", h.ref(odst), osrc)
 		}
-	} else {
-		// We don't interpret store-with-offset.
-		// See discussion of soundness at markIndirectNodes.
 	}
+	// We don't interpret store-with-offset.
+	// See discussion of soundness at markIndirectNodes.
 }
 
 // dst = &src.offset
@@ -785,11 +784,11 @@
 		assert(peLabels.Len() == 1, "PE class is not a singleton")
 		label := peLabel(peLabels.Min())
 
-		canonId := canon[label]
-		if canonId == nodeid(h.N) {
+		canonID := canon[label]
+		if canonID == nodeid(h.N) {
 			// id becomes the representative of the PE label.
-			canonId = id
-			canon[label] = canonId
+			canonID = id
+			canon[label] = canonID
 
 			if h.a.log != nil {
 				fmt.Fprintf(h.a.log, "\tpts(n%d) is canonical : \t(%s)\n",
@@ -798,8 +797,8 @@
 
 		} else {
 			// Link the solver states for the two nodes.
-			assert(h.a.nodes[canonId].solve != nil, "missing solver state")
-			h.a.nodes[id].solve = h.a.nodes[canonId].solve
+			assert(h.a.nodes[canonID].solve != nil, "missing solver state")
+			h.a.nodes[id].solve = h.a.nodes[canonID].solve
 
 			if h.a.log != nil {
 				// TODO(adonovan): debug: reorganize the log so it prints
@@ -807,11 +806,11 @@
 				// 	pe y = x1, ..., xn
 				// for each canonical y.  Requires allocation.
 				fmt.Fprintf(h.a.log, "\tpts(n%d) = pts(n%d) : %s\n",
-					id, canonId, h.a.nodes[id].typ)
+					id, canonID, h.a.nodes[id].typ)
 			}
 		}
 
-		mapping[id] = canonId
+		mapping[id] = canonID
 	}
 
 	// Renumber the constraints, eliminate duplicates, and eliminate
diff --git a/go/pointer/opt.go b/go/pointer/opt.go
index 81f80aa..6defea1 100644
--- a/go/pointer/opt.go
+++ b/go/pointer/opt.go
@@ -34,8 +34,8 @@
 	}
 
 	N := nodeid(len(a.nodes))
-	newNodes := make([]*node, N, N)
-	renumbering := make([]nodeid, N, N) // maps old to new
+	newNodes := make([]*node, N)
+	renumbering := make([]nodeid, N) // maps old to new
 
 	var i, j nodeid
 
diff --git a/go/pointer/util.go b/go/pointer/util.go
index 683fddd..986dd09 100644
--- a/go/pointer/util.go
+++ b/go/pointer/util.go
@@ -277,8 +277,8 @@
 	return ns.Sparse.Insert(int(n))
 }
 
-func (x *nodeset) addAll(y *nodeset) bool {
-	return x.UnionWith(&y.Sparse)
+func (ns *nodeset) addAll(y *nodeset) bool {
+	return ns.UnionWith(&y.Sparse)
 }
 
 // Profiling & debugging -------------------------------------------------------
diff --git a/go/ssa/dom.go b/go/ssa/dom.go
index 12ef430..822fe97 100644
--- a/go/ssa/dom.go
+++ b/go/ssa/dom.go
@@ -53,7 +53,7 @@
 //
 func (f *Function) DomPreorder() []*BasicBlock {
 	n := len(f.Blocks)
-	order := make(byDomPreorder, n, n)
+	order := make(byDomPreorder, n)
 	copy(order, f.Blocks)
 	sort.Sort(order)
 	return order
@@ -123,7 +123,7 @@
 	n := len(f.Blocks)
 	// Allocate space for 5 contiguous [n]*BasicBlock arrays:
 	// sdom, parent, ancestor, preorder, buckets.
-	space := make([]*BasicBlock, 5*n, 5*n)
+	space := make([]*BasicBlock, 5*n)
 	lt := ltState{
 		sdom:     space[0:n],
 		parent:   space[n : 2*n],
diff --git a/go/ssa/interp/interp_test.go b/go/ssa/interp/interp_test.go
index a216ac7..28ebf5f 100644
--- a/go/ssa/interp/interp_test.go
+++ b/go/ssa/interp/interp_test.go
@@ -23,7 +23,6 @@
 	"log"
 	"os"
 	"path/filepath"
-	"runtime"
 	"strings"
 	"testing"
 	"time"
@@ -125,10 +124,10 @@
 }
 
 func run(t *testing.T, input string) bool {
-	// The recover2 test case is broken when run against tip. See golang/go#34089.
-	// TODO(matloob): Figure out what's going on or fix this before go1.14 is released.
-	if filepath.Base(input) == "recover2.go" && strings.HasPrefix(runtime.Version(), "devel") {
-		t.Skip("The recover2.go test is broken in tip. See golang.org/issue/34089.")
+	// The recover2 test case is broken on Go 1.14+. See golang/go#34089.
+	// TODO(matloob): Fix this.
+	if filepath.Base(input) == "recover2.go" {
+		t.Skip("The recover2.go test is broken in go1.14+. See golang.org/issue/34089.")
 	}
 
 	t.Logf("Input: %s\n", input)
diff --git a/go/ssa/source_test.go b/go/ssa/source_test.go
index 9dc3c66..24cf57e 100644
--- a/go/ssa/source_test.go
+++ b/go/ssa/source_test.go
@@ -50,7 +50,7 @@
 	// Each note of the form @ssa(x, "BinOp") in testdata/objlookup.go
 	// specifies an expectation that an object named x declared on the
 	// same line is associated with an an ssa.Value of type *ssa.BinOp.
-	notes, err := expect.Extract(conf.Fset, f)
+	notes, err := expect.ExtractGo(conf.Fset, f)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -271,7 +271,7 @@
 		return true
 	})
 
-	notes, err := expect.Extract(prog.Fset, f)
+	notes, err := expect.ExtractGo(prog.Fset, f)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/go/ssa/ssa.go b/go/ssa/ssa.go
index 78272c5..4dfdafd 100644
--- a/go/ssa/ssa.go
+++ b/go/ssa/ssa.go
@@ -1478,6 +1478,8 @@
 func (c *NamedConst) Package() *Package                    { return c.pkg }
 func (c *NamedConst) RelString(from *types.Package) string { return relString(c, from) }
 
+func (d *DebugRef) Object() types.Object { return d.object }
+
 // Func returns the package-level function of the specified name,
 // or nil if not found.
 //
diff --git a/go/types/typeutil/callee_test.go b/go/types/typeutil/callee_test.go
index a0d107d..272e1eb 100644
--- a/go/types/typeutil/callee_test.go
+++ b/go/types/typeutil/callee_test.go
@@ -63,7 +63,7 @@
 		Uses:       make(map[*ast.Ident]types.Object),
 		Selections: make(map[*ast.SelectorExpr]*types.Selection),
 	}
-	cfg := &types.Config{Importer: importer.For("source", nil)}
+	cfg := &types.Config{Importer: importer.ForCompiler(fset, "source", nil)}
 	if _, err := cfg.Check("p", fset, []*ast.File{f}, info); err != nil {
 		t.Fatal(err)
 	}
diff --git a/go/types/typeutil/map_test.go b/go/types/typeutil/map_test.go
index 34facbe..d4b0f63 100644
--- a/go/types/typeutil/map_test.go
+++ b/go/types/typeutil/map_test.go
@@ -47,7 +47,7 @@
 	tmap.At(tPStr1)
 	tmap.Delete(tPStr1)
 	tmap.KeysString()
-	tmap.String()
+	_ = tmap.String()
 
 	tmap = new(typeutil.Map)
 
diff --git a/godoc/dirtrees.go b/godoc/dirtrees.go
index e9483a0..82c9a06 100644
--- a/godoc/dirtrees.go
+++ b/godoc/dirtrees.go
@@ -332,8 +332,8 @@
 // If filter is set, only the directory entries whose paths match the filter
 // are included.
 //
-func (root *Directory) listing(skipRoot bool, filter func(string) bool) *DirList {
-	if root == nil {
+func (dir *Directory) listing(skipRoot bool, filter func(string) bool) *DirList {
+	if dir == nil {
 		return nil
 	}
 
@@ -341,7 +341,7 @@
 	n := 0
 	minDepth := 1 << 30 // infinity
 	maxDepth := 0
-	for d := range root.iter(skipRoot) {
+	for d := range dir.iter(skipRoot) {
 		n++
 		if minDepth > d.Depth {
 			minDepth = d.Depth
@@ -358,7 +358,7 @@
 
 	// create list
 	list := make([]DirEntry, 0, n)
-	for d := range root.iter(skipRoot) {
+	for d := range dir.iter(skipRoot) {
 		if filter != nil && !filter(d.Path) {
 			continue
 		}
@@ -368,7 +368,7 @@
 		// the path is relative to root.Path - remove the root.Path
 		// prefix (the prefix should always be present but avoid
 		// crashes and check)
-		path := strings.TrimPrefix(d.Path, root.Path)
+		path := strings.TrimPrefix(d.Path, dir.Path)
 		// remove leading separator if any - path must be relative
 		path = strings.TrimPrefix(path, "/")
 		p.Path = path
diff --git a/godoc/godoc.go b/godoc/godoc.go
index 810da32..6d4d115 100644
--- a/godoc/godoc.go
+++ b/godoc/godoc.go
@@ -312,9 +312,7 @@
 	//
 	// TODO: do this better, so it works for all
 	// comments, including unconventional ones.
-	if bytes.HasPrefix(line, commentPrefix) {
-		line = line[len(commentPrefix):]
-	}
+	line = bytes.TrimPrefix(line, commentPrefix)
 	id := scanIdentifier(line)
 	if len(id) == 0 {
 		// No leading identifier. Avoid map lookup for
diff --git a/godoc/redirect/redirect.go b/godoc/redirect/redirect.go
index b4599f6..f0e8a10 100644
--- a/godoc/redirect/redirect.go
+++ b/godoc/redirect/redirect.go
@@ -156,7 +156,7 @@
 	})
 }
 
-var validId = regexp.MustCompile(`^[A-Za-z0-9-]*/?$`)
+var validID = regexp.MustCompile(`^[A-Za-z0-9-]*/?$`)
 
 func PrefixHandler(prefix, baseURL string) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -166,7 +166,7 @@
 			return
 		}
 		id := r.URL.Path[len(prefix):]
-		if !validId.MatchString(id) {
+		if !validID.MatchString(id) {
 			http.Error(w, "Not found", http.StatusNotFound)
 			return
 		}
@@ -192,7 +192,7 @@
 	id := r.URL.Path[len(prefix):]
 	// support /cl/152700045/, which is used in commit 0edafefc36.
 	id = strings.TrimSuffix(id, "/")
-	if !validId.MatchString(id) {
+	if !validID.MatchString(id) {
 		http.Error(w, "Not found", http.StatusNotFound)
 		return
 	}
diff --git a/godoc/static/gen_test.go b/godoc/static/gen_test.go
index 1889b16..7f74329 100644
--- a/godoc/static/gen_test.go
+++ b/godoc/static/gen_test.go
@@ -27,7 +27,7 @@
 		t.Errorf("error while generating static.go: %v\n", err)
 	}
 
-	if bytes.Compare(oldBuf, newBuf) != 0 {
+	if !bytes.Equal(oldBuf, newBuf) {
 		t.Error(`static.go is stale.  Run:
   $ go generate golang.org/x/tools/godoc/static
   $ git diff
diff --git a/gopls/doc/status.md b/gopls/doc/status.md
index 740a09d..435b9ec 100644
--- a/gopls/doc/status.md
+++ b/gopls/doc/status.md
@@ -21,26 +21,17 @@
 
 ## Known issues
 
-1. Cursor resets to the beginning or end of file on format: [#31937]
 1. Editing multiple modules in one editor window: [#32394]
-1. Language features do not work with cgo: [#32898]
+1. Type checking does not work in cgo packages: [#35721]
 1. Does not work with build tags: [#29202]
-1. Find references and rename only work in a single package: [#32869], [#32877]
-1. Completion does not work well after go or defer statements: [#29313]
-1. Changes in files outside of the editor are not yet tracked: [#31553]
+1. Find references and rename only work in a single package: [#32877]
 
 [x/tools]: https://github.com/golang/tools
 [golang.org/x/tools/gopls]: https://github.com/golang/tools/tree/master/gopls
 [golang.org/x/tools/internal/lsp]: https://github.com/golang/tools/tree/master/internal/lsp
 
 
-[#31937]: https://github.com/golang/go/issues/31937
 [#32394]: https://github.com/golang/go/issues/32394
-[#32898]: https://github.com/golang/go/issues/32898
+[#35721]: https://github.com/golang/go/issues/35721
 [#29202]: https://github.com/golang/go/issues/29202
-[#32869]: https://github.com/golang/go/issues/32869
 [#32877]: https://github.com/golang/go/issues/32877
-[#29313]: https://github.com/golang/go/issues/29313
-[#31553]: https://github.com/golang/go/issues/31553
-
-
diff --git a/gopls/doc/vscode.md b/gopls/doc/vscode.md
index 3e834e2..f05c61b 100644
--- a/gopls/doc/vscode.md
+++ b/gopls/doc/vscode.md
@@ -5,18 +5,20 @@
 ```json5
 "go.useLanguageServer": true,
 "[go]": {
-    "editor.snippetSuggestions": "none",
     "editor.formatOnSave": true,
     "editor.codeActionsOnSave": {
         "source.organizeImports": true,
-    }
+    },
+    // Optional: Disable snippets, as they conflict with completion ranking.
+    "editor.snippetSuggestions": "none",
 },
 "gopls": {
-    "usePlaceholders": true, // add parameter placeholders when completing a function
+     // Add parameter placeholders when completing a function.
+    "usePlaceholders": true,
 
-    // Experimental settings
-    "completeUnimported": true, // autocomplete unimported packages
-    "deepCompletion": true,     // enable deep completion
+    // If true, enable additional analyses with staticcheck.
+    // Warning: This will significantly increase memory usage.
+    "staticcheck": false,
 }
 ```
 
diff --git a/gopls/go.mod b/gopls/go.mod
index 8496bca..1cf9b16 100644
--- a/gopls/go.mod
+++ b/gopls/go.mod
@@ -6,7 +6,7 @@
 	github.com/sergi/go-diff v1.0.0
 	github.com/stretchr/testify v1.4.0 // indirect
 	golang.org/x/tools v0.0.0-20200212213342-7a21e308cf6c
-	honnef.co/go/tools v0.0.1-2019.2.3
+	honnef.co/go/tools v0.0.1-2020.1.3
 	mvdan.cc/xurls/v2 v2.1.0
 )
 
diff --git a/gopls/go.sum b/gopls/go.sum
index f935705..0d1b780 100644
--- a/gopls/go.sum
+++ b/gopls/go.sum
@@ -38,7 +38,7 @@
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 mvdan.cc/xurls/v2 v2.1.0 h1:KaMb5GLhlcSX+e+qhbRJODnUUBvlw01jt4yrjFIHAuA=
 mvdan.cc/xurls/v2 v2.1.0/go.mod h1:5GrSd9rOnKOpZaji1OZLYL/yeAAtGDlo/cFe+8K5n8E=
diff --git a/gopls/integration/govim/Dockerfile b/gopls/integration/govim/Dockerfile
index 0ec78a8..614d7fe 100644
--- a/gopls/integration/govim/Dockerfile
+++ b/gopls/integration/govim/Dockerfile
@@ -10,15 +10,11 @@
 # that we're not sensitive to test breakages in govim.
 # TODO(findleyr): Once a version of govim has been tagged containing
 # https://github.com/govim/govim/pull/629, switch this to @latest.
-ENV GOPROXY=https://proxy.golang.org GOPATH=/go VIM_FLAVOR=vim \
-    GOVIM_VERSION=v0.0.27-0.20200122085353-96e61d22452d
+ENV GOPROXY=https://proxy.golang.org GOPATH=/go VIM_FLAVOR=vim
 WORKDIR /src
 
-# Get govim, as well as all dependencies for the ./cmd/govim tests. In order to
-# use the go command's module version resolution, we use `go mod download` for
-# this rather than git. Another approach would be to use a temporary module and
-# `go get -t -u github.com/govim/govim`, but that may cause test dependencies
-# to be upgraded.
-RUN GOVIM_DIR=$(go mod download -json github.com/govim/govim@$GOVIM_VERSION | jq -r .Dir) && \
-    cp -r $GOVIM_DIR /src/govim && cd /src/govim && \
-    go list -test -deps ./cmd/govim
+# Clone govim. In order to use the go command for resolving latest, we download
+# a redundant copy of govim to the build cache using `go mod download`.
+RUN GOVIM_VERSION=$(go mod download -json github.com/govim/govim@latest | jq -r .Version) && \
+    git clone https://github.com/govim/govim /src/govim && cd /src/govim && \
+    git checkout $GOVIM_VERSION
diff --git a/gopls/integration/govim/README.md b/gopls/integration/govim/README.md
index c3ae925..da3a98b 100644
--- a/gopls/integration/govim/README.md
+++ b/gopls/integration/govim/README.md
@@ -6,25 +6,36 @@
 ## Running on GCP
 
 To run these integration tests in Cloud Build, use the following steps.  Here
-we assume that `$PROJECT` is a valid GCP project and `$BUCKET` is a cloud
+we assume that `$PROJECT_ID` is a valid GCP project and `$BUCKET` is a cloud
 storage bucket owned by that project.
 
 - `cd` to the root directory of the tools project.
 - (at least once per GCP project) Build the test harness:
 ```
 $ gcloud builds submit \
-	--project="${PROJECT}" \
+	--project="${PROJECT_ID}" \
 	--config=gopls/integration/govim/cloudbuild.harness.yaml \
 	--substitutions=_RESULT_BUCKET="${BUCKET}"
 ```
 - Run the integration tests:
 ```
 $ gcloud builds submit \
-	--project="${PROJECT}" \
+	--project="${PROJECT_ID}" \
 	--config=gopls/integration/govim/cloudbuild.yaml \
 	--substitutions=_RESULT_BUCKET="${BUCKET}"
 ```
 
+## Fetching Artifacts
+
+Assuming the artifacts bucket is world readable, you can fetch integration from
+GCS. They are located at:
+
+- logs: `https://storage.googleapis.com/${BUCKET}/log-${EVALUATION_ID}.txt`
+- artifact tarball: `https://storage.googleapis.com/${BUCKET}/govim/${EVALUATION_ID}/artifacts.tar.gz`
+
+The `artifacts.go` command can be used to fetch both artifacts using an
+evaluation id.
+
 ## Running locally
 
 Run `gopls/integration/govim/run_local.sh`. This may take a while the first
diff --git a/gopls/integration/govim/artifacts.go b/gopls/integration/govim/artifacts.go
new file mode 100644
index 0000000..40a8547
--- /dev/null
+++ b/gopls/integration/govim/artifacts.go
@@ -0,0 +1,67 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path"
+)
+
+var bucket = flag.String("bucket", "golang-gopls_integration_tests", "GCS bucket holding test artifacts.")
+
+const usage = `
+artifacts [--bucket=<bucket ID>] <cloud build evaluation ID>
+
+Fetch artifacts from an integration test run. Evaluation ID should be extracted
+from the cloud build notification.
+
+In order for this to work, the GCS bucket that artifacts were written to must
+be publicly readable. By default, this fetches from the
+golang-gopls_integration_tests bucket.
+`
+
+func main() {
+	flag.Usage = func() {
+		fmt.Fprintf(flag.CommandLine.Output(), usage)
+	}
+	flag.Parse()
+	if flag.NArg() != 1 {
+		flag.Usage()
+		os.Exit(2)
+	}
+	evalID := flag.Arg(0)
+	logURL := fmt.Sprintf("https://storage.googleapis.com/%s/log-%s.txt", *bucket, evalID)
+	if err := download(logURL); err != nil {
+		fmt.Fprintf(os.Stderr, "downloading logs: %v", err)
+	}
+	tarURL := fmt.Sprintf("https://storage.googleapis.com/%s/govim/%s/artifacts.tar.gz", *bucket, evalID)
+	if err := download(tarURL); err != nil {
+		fmt.Fprintf(os.Stderr, "downloading artifact tarball: %v", err)
+	}
+}
+
+func download(artifactURL string) error {
+	name := path.Base(artifactURL)
+	resp, err := http.Get(artifactURL)
+	if err != nil {
+		return fmt.Errorf("fetching from GCS: %v", err)
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != http.StatusOK {
+		return fmt.Errorf("got status code %d from GCS", resp.StatusCode)
+	}
+	data, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return fmt.Errorf("reading result: %v", err)
+	}
+	if err := ioutil.WriteFile(name, data, 0644); err != nil {
+		return fmt.Errorf("writing artifact: %v", err)
+	}
+	return nil
+}
diff --git a/gopls/integration/govim/cloudbuild.harness.yaml b/gopls/integration/govim/cloudbuild.harness.yaml
index 0deb8af..13b0a34 100644
--- a/gopls/integration/govim/cloudbuild.harness.yaml
+++ b/gopls/integration/govim/cloudbuild.harness.yaml
@@ -10,7 +10,7 @@
       # To allow for breaking changes to this test harness, tag with a major
       # version number.
       '-t', 'gcr.io/$PROJECT_ID/govim-harness:latest',
-      '-t', 'gcr.io/$PROJECT_ID/govim-harness:2',
+      '-t', 'gcr.io/$PROJECT_ID/govim-harness:3',
       # It is assumed that this build is running from the root directory of the
       # tools repository.
       '-f', 'gopls/integration/govim/Dockerfile',
diff --git a/gopls/integration/govim/cloudbuild.yaml b/gopls/integration/govim/cloudbuild.yaml
index a19c787..f0b982a 100644
--- a/gopls/integration/govim/cloudbuild.yaml
+++ b/gopls/integration/govim/cloudbuild.yaml
@@ -20,7 +20,7 @@
   # Run the tests. Note that the script in this step does not return the exit
   # code from `go test`, but rather saves it for use in the final step after
   # uploading artifacts.
-  - name: 'gcr.io/$PROJECT_ID/govim-harness:2'
+  - name: 'gcr.io/$PROJECT_ID/govim-harness:3'
     dir: '/src/govim'
     env:
       - GOVIM_TESTSCRIPT_WORKDIR_ROOT=/workspace/artifacts
diff --git a/gopls/integration/govim/run_tests_for_cloudbuild.sh b/gopls/integration/govim/run_tests_for_cloudbuild.sh
index 47a9cfd..0f59938 100755
--- a/gopls/integration/govim/run_tests_for_cloudbuild.sh
+++ b/gopls/integration/govim/run_tests_for_cloudbuild.sh
@@ -14,3 +14,15 @@
 
 # Stash the error, for use in a later build step.
 echo "exit $?" > /workspace/govim_test_result.sh
+
+# Clean up unnecessary artifacts. This is based on govim/_scripts/tidyUp.bash.
+# Since we're fetching govim using the go command, we won't have this non-go
+# source directory available to us.
+if [[ -n "$GOVIM_TESTSCRIPT_WORKDIR_ROOT" ]]; then
+  echo "Cleaning up build artifacts..."
+  # Make artifacts writable so that rm -rf doesn't complain.
+  chmod -R u+w "$GOVIM_TESTSCRIPT_WORKDIR_ROOT"
+
+  # Remove directories we don't care about.
+  find "$GOVIM_TESTSCRIPT_WORKDIR_ROOT" -type d \( -name .vim -o -name gopath \) -prune -exec rm -rf '{}' \;
+fi
diff --git a/gopls/integration/replay/main.go b/gopls/integration/replay/main.go
index cd30dd6..1bd69ac 100644
--- a/gopls/integration/replay/main.go
+++ b/gopls/integration/replay/main.go
@@ -68,7 +68,7 @@
 	log.Printf("new %d, hist:%s", len(newMsgs), seen.Histogram)
 
 	ok := make(map[string]int)
-	f := func(x []*parse.Logmsg, label string, diags map[string][]p.Diagnostic) {
+	f := func(x []*parse.Logmsg, label string, diags map[p.DocumentURI][]p.Diagnostic) {
 		counts := make(map[parse.MsgType]int)
 		for _, l := range x {
 			if l.Method == "window/logMessage" {
@@ -102,9 +102,9 @@
 		}
 		log.Printf("%s: %s", label, msg)
 	}
-	mdiags := make(map[string][]p.Diagnostic)
+	mdiags := make(map[p.DocumentURI][]p.Diagnostic)
 	f(msgs, "old", mdiags)
-	vdiags := make(map[string][]p.Diagnostic)
+	vdiags := make(map[p.DocumentURI][]p.Diagnostic)
 	f(newMsgs, "new", vdiags)
 	buf := []string{}
 	for k := range ok {
diff --git a/gopls/main.go b/gopls/main.go
index 0b4d889..0de6531 100644
--- a/gopls/main.go
+++ b/gopls/main.go
@@ -6,6 +6,9 @@
 // The Language Server Protocol allows any text editor
 // to be extended with IDE-like features;
 // see https://langserver.org/ for details.
+//
+// See https://github.com/golang/tools/tree/master/gopls
+// for the most up-to-date information on the gopls status.
 package main // import "golang.org/x/tools/gopls"
 
 import (
diff --git a/gopls/test/gopls_test.go b/gopls/test/gopls_test.go
index d57a9a6..0afcf70 100644
--- a/gopls/test/gopls_test.go
+++ b/gopls/test/gopls_test.go
@@ -10,7 +10,11 @@
 
 	"golang.org/x/tools/go/packages/packagestest"
 	"golang.org/x/tools/gopls/internal/hooks"
+	"golang.org/x/tools/internal/jsonrpc2/servertest"
+	"golang.org/x/tools/internal/lsp/cache"
 	cmdtest "golang.org/x/tools/internal/lsp/cmd/test"
+	"golang.org/x/tools/internal/lsp/debug"
+	"golang.org/x/tools/internal/lsp/lsprpc"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/tests"
 	"golang.org/x/tools/internal/testenv"
@@ -37,6 +41,16 @@
 		t.Skip("testdata directory not present")
 	}
 	data := tests.Load(t, exporter, testdata)
-	defer data.Exported.Cleanup()
-	tests.Run(t, cmdtest.NewRunner(exporter, data, tests.Context(t), commandLineOptions), data)
+	ctx := tests.Context(t)
+	di := debug.NewInstance("", "")
+	cache := cache.New(commandLineOptions, di.State)
+	ss := lsprpc.NewStreamServer(cache, false, di)
+	ts := servertest.NewTCPServer(ctx, ss)
+	for _, data := range data {
+		defer data.Exported.Cleanup()
+		t.Run(data.Folder, func(t *testing.T) {
+			t.Helper()
+			tests.Run(t, cmdtest.NewRunner(exporter, data, tests.Context(t), ts.Addr, commandLineOptions), data)
+		})
+	}
 }
diff --git a/internal/fastwalk/fastwalk.go b/internal/fastwalk/fastwalk.go
index 7219c8e..9887f7e 100644
--- a/internal/fastwalk/fastwalk.go
+++ b/internal/fastwalk/fastwalk.go
@@ -14,14 +14,14 @@
 	"sync"
 )
 
-// TraverseLink is used as a return value from WalkFuncs to indicate that the
+// ErrTraverseLink is used as a return value from WalkFuncs to indicate that the
 // symlink named in the call may be traversed.
-var TraverseLink = errors.New("fastwalk: traverse symlink, assuming target is a directory")
+var ErrTraverseLink = errors.New("fastwalk: traverse symlink, assuming target is a directory")
 
-// SkipFiles is a used as a return value from WalkFuncs to indicate that the
+// ErrSkipFiles is a used as a return value from WalkFuncs to indicate that the
 // callback should not be called for any other files in the current directory.
 // Child directories will still be traversed.
-var SkipFiles = errors.New("fastwalk: skip remaining files in directory")
+var ErrSkipFiles = errors.New("fastwalk: skip remaining files in directory")
 
 // Walk is a faster implementation of filepath.Walk.
 //
@@ -167,7 +167,7 @@
 
 	err := w.fn(joined, typ)
 	if typ == os.ModeSymlink {
-		if err == TraverseLink {
+		if err == ErrTraverseLink {
 			// Set callbackDone so we don't call it twice for both the
 			// symlink-as-symlink and the symlink-as-directory later:
 			w.enqueue(walkItem{dir: joined, callbackDone: true})
diff --git a/internal/fastwalk/fastwalk_portable.go b/internal/fastwalk/fastwalk_portable.go
index a906b87..b0d6327 100644
--- a/internal/fastwalk/fastwalk_portable.go
+++ b/internal/fastwalk/fastwalk_portable.go
@@ -26,7 +26,7 @@
 			continue
 		}
 		if err := fn(dirName, fi.Name(), fi.Mode()&os.ModeType); err != nil {
-			if err == SkipFiles {
+			if err == ErrSkipFiles {
 				skipFiles = true
 				continue
 			}
diff --git a/internal/fastwalk/fastwalk_test.go b/internal/fastwalk/fastwalk_test.go
index a122ddf..a6d9bea 100644
--- a/internal/fastwalk/fastwalk_test.go
+++ b/internal/fastwalk/fastwalk_test.go
@@ -184,7 +184,7 @@
 				mu.Lock()
 				defer mu.Unlock()
 				want["/src/"+filepath.Base(path)] = 0
-				return fastwalk.SkipFiles
+				return fastwalk.ErrSkipFiles
 			}
 			return nil
 		},
@@ -208,7 +208,7 @@
 	},
 		func(path string, typ os.FileMode) error {
 			if typ == os.ModeSymlink {
-				return fastwalk.TraverseLink
+				return fastwalk.ErrTraverseLink
 			}
 			return nil
 		},
diff --git a/internal/fastwalk/fastwalk_unix.go b/internal/fastwalk/fastwalk_unix.go
index 3369b1a..ce38fdc 100644
--- a/internal/fastwalk/fastwalk_unix.go
+++ b/internal/fastwalk/fastwalk_unix.go
@@ -66,7 +66,7 @@
 			continue
 		}
 		if err := fn(dirName, name, typ); err != nil {
-			if err == SkipFiles {
+			if err == ErrSkipFiles {
 				skipFiles = true
 				continue
 			}
diff --git a/internal/gocommand/invoke.go b/internal/gocommand/invoke.go
new file mode 100644
index 0000000..75d73e7
--- /dev/null
+++ b/internal/gocommand/invoke.go
@@ -0,0 +1,121 @@
+// Package gocommand is a helper for calling the go command.
+package gocommand
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"os"
+	"os/exec"
+	"strings"
+	"time"
+)
+
+// An Invocation represents a call to the go command.
+type Invocation struct {
+	Verb       string
+	Args       []string
+	BuildFlags []string
+	Env        []string
+	WorkingDir string
+	Logf       func(format string, args ...interface{})
+}
+
+// Run runs the invocation, returning its stdout and an error suitable for
+// human consumption, including stderr.
+func (i *Invocation) Run(ctx context.Context) (*bytes.Buffer, error) {
+	stdout, _, friendly, _ := i.RunRaw(ctx)
+	return stdout, friendly
+}
+
+// RunRaw is like Run, but also returns the raw stderr and error for callers
+// that want to do low-level error handling/recovery.
+func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr *bytes.Buffer, friendlyError error, rawError error) {
+	log := i.Logf
+	if log == nil {
+		log = func(string, ...interface{}) {}
+	}
+
+	goArgs := []string{i.Verb}
+	switch i.Verb {
+	case "mod":
+		// mod needs the sub-verb before build flags.
+		goArgs = append(goArgs, i.Args[0])
+		goArgs = append(goArgs, i.BuildFlags...)
+		goArgs = append(goArgs, i.Args[1:]...)
+	case "env":
+		// env doesn't take build flags.
+		goArgs = append(goArgs, i.Args...)
+	default:
+		goArgs = append(goArgs, i.BuildFlags...)
+		goArgs = append(goArgs, i.Args...)
+	}
+	cmd := exec.Command("go", goArgs...)
+	stdout = &bytes.Buffer{}
+	stderr = &bytes.Buffer{}
+	cmd.Stdout = stdout
+	cmd.Stderr = stderr
+	// On darwin the cwd gets resolved to the real path, which breaks anything that
+	// expects the working directory to keep the original path, including the
+	// go command when dealing with modules.
+	// The Go stdlib has a special feature where if the cwd and the PWD are the
+	// same node then it trusts the PWD, so by setting it in the env for the child
+	// process we fix up all the paths returned by the go command.
+	cmd.Env = append(append([]string{}, i.Env...), "PWD="+i.WorkingDir)
+	cmd.Dir = i.WorkingDir
+
+	defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
+
+	rawError = runCmdContext(ctx, cmd)
+	friendlyError = rawError
+	if rawError != nil {
+		// Check for 'go' executable not being found.
+		if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
+			friendlyError = fmt.Errorf("go command required, not found: %v", ee)
+		}
+		if ctx.Err() != nil {
+			friendlyError = ctx.Err()
+		}
+		friendlyError = fmt.Errorf("err: %v: stderr: %s", rawError, stderr)
+	}
+	return
+}
+
+// runCmdContext is like exec.CommandContext except it sends os.Interrupt
+// before os.Kill.
+func runCmdContext(ctx context.Context, cmd *exec.Cmd) error {
+	if err := cmd.Start(); err != nil {
+		return err
+	}
+	resChan := make(chan error, 1)
+	go func() {
+		resChan <- cmd.Wait()
+	}()
+
+	select {
+	case err := <-resChan:
+		return err
+	case <-ctx.Done():
+	}
+	// Cancelled. Interrupt and see if it ends voluntarily.
+	cmd.Process.Signal(os.Interrupt)
+	select {
+	case err := <-resChan:
+		return err
+	case <-time.After(time.Second):
+	}
+	// Didn't shut down in response to interrupt. Kill it hard.
+	cmd.Process.Kill()
+	return <-resChan
+}
+
+func cmdDebugStr(cmd *exec.Cmd) string {
+	env := make(map[string]string)
+	for _, kv := range cmd.Env {
+		split := strings.Split(kv, "=")
+		k, v := split[0], split[1]
+		env[k] = v
+	}
+
+	return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], cmd.Args)
+}
diff --git a/internal/gopathwalk/walk.go b/internal/gopathwalk/walk.go
index d067562..64309db 100644
--- a/internal/gopathwalk/walk.go
+++ b/internal/gopathwalk/walk.go
@@ -189,14 +189,14 @@
 		if dir == w.root.Path && (w.root.Type == RootGOROOT || w.root.Type == RootGOPATH) {
 			// Doesn't make sense to have regular files
 			// directly in your $GOPATH/src or $GOROOT/src.
-			return fastwalk.SkipFiles
+			return fastwalk.ErrSkipFiles
 		}
 		if !strings.HasSuffix(path, ".go") {
 			return nil
 		}
 
 		w.add(w.root, dir)
-		return fastwalk.SkipFiles
+		return fastwalk.ErrSkipFiles
 	}
 	if typ == os.ModeDir {
 		base := filepath.Base(path)
@@ -224,7 +224,7 @@
 			return nil
 		}
 		if w.shouldTraverse(dir, fi) {
-			return fastwalk.TraverseLink
+			return fastwalk.ErrTraverseLink
 		}
 	}
 	return nil
diff --git a/internal/imports/fix.go b/internal/imports/fix.go
index e94d47e..5e0c9df 100644
--- a/internal/imports/fix.go
+++ b/internal/imports/fix.go
@@ -14,7 +14,6 @@
 	"go/token"
 	"io/ioutil"
 	"os"
-	"os/exec"
 	"path"
 	"path/filepath"
 	"reflect"
@@ -22,11 +21,11 @@
 	"strconv"
 	"strings"
 	"sync"
-	"time"
 	"unicode"
 	"unicode/utf8"
 
 	"golang.org/x/tools/go/ast/astutil"
+	"golang.org/x/tools/internal/gocommand"
 	"golang.org/x/tools/internal/gopathwalk"
 )
 
@@ -537,7 +536,7 @@
 	// derive package names from import paths, see if the file is already
 	// complete. We can't add any imports yet, because we don't know
 	// if missing references are actually package vars.
-	p := &pass{fset: fset, f: f, srcDir: srcDir}
+	p := &pass{fset: fset, f: f, srcDir: srcDir, env: env}
 	if fixes, done := p.load(); done {
 		return fixes, nil
 	}
@@ -559,8 +558,7 @@
 	}
 
 	// Third pass: get real package names where we had previously used
-	// the naive algorithm. This is the first step that will use the
-	// environment, so we provide it here for the first time.
+	// the naive algorithm.
 	p = &pass{fset: fset, f: f, srcDir: srcDir, env: env}
 	p.loadRealPackageNames = true
 	p.otherFiles = otherFiles
@@ -793,7 +791,7 @@
 	if e.resolver != nil {
 		return e.resolver
 	}
-	out, err := e.invokeGo("env", "GOMOD")
+	out, err := e.invokeGo(context.TODO(), "env", "GOMOD")
 	if err != nil || len(bytes.TrimSpace(out.Bytes())) == 0 {
 		e.resolver = newGopathResolver(e)
 		return e.resolver
@@ -824,42 +822,24 @@
 	return &ctx
 }
 
-func (e *ProcessEnv) invokeGo(verb string, args ...string) (*bytes.Buffer, error) {
-	goArgs := []string{verb}
-	if verb != "env" {
-		goArgs = append(goArgs, e.BuildFlags...)
+func (e *ProcessEnv) invokeGo(ctx context.Context, verb string, args ...string) (*bytes.Buffer, error) {
+	inv := gocommand.Invocation{
+		Verb:       verb,
+		Args:       args,
+		BuildFlags: e.BuildFlags,
+		Env:        e.env(),
+		Logf:       e.Logf,
+		WorkingDir: e.WorkingDir,
 	}
-	goArgs = append(goArgs, args...)
-	cmd := exec.Command("go", goArgs...)
-	stdout := &bytes.Buffer{}
-	stderr := &bytes.Buffer{}
-	cmd.Stdout = stdout
-	cmd.Stderr = stderr
-	cmd.Env = e.env()
-	cmd.Dir = e.WorkingDir
-
-	if e.Debug {
-		defer func(start time.Time) { e.Logf("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
-	}
-	if err := cmd.Run(); err != nil {
-		return nil, fmt.Errorf("running go: %v (stderr:\n%s)", err, stderr)
-	}
-	return stdout, nil
-}
-
-func cmdDebugStr(cmd *exec.Cmd) string {
-	env := make(map[string]string)
-	for _, kv := range cmd.Env {
-		split := strings.Split(kv, "=")
-		k, v := split[0], split[1]
-		env[k] = v
-	}
-
-	return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], cmd.Args)
+	return inv.Run(ctx)
 }
 
 func addStdlibCandidates(pass *pass, refs references) {
 	add := func(pkg string) {
+		// Prevent self-imports.
+		if path.Base(pkg) == pass.f.Name.Name && filepath.Join(pass.env.GOROOT, "src", pkg) == pass.srcDir {
+			return
+		}
 		exports := copyExports(stdlib[pkg])
 		pass.addCandidate(
 			&ImportInfo{ImportPath: pkg},
diff --git a/internal/imports/fix_test.go b/internal/imports/fix_test.go
index 67ebe53..f2bc5b6 100644
--- a/internal/imports/fix_test.go
+++ b/internal/imports/fix_test.go
@@ -1577,6 +1577,27 @@
 	})
 }
 
+func TestStdlibSelfImports(t *testing.T) {
+	const input = `package ecdsa
+
+var _ = ecdsa.GenerateKey
+`
+
+	testConfig{
+		module: packagestest.Module{
+			Name: "ignored.com",
+		},
+	}.test(t, func(t *goimportTest) {
+		got, err := t.processNonModule(filepath.Join(t.env.GOROOT, "src/crypto/ecdsa/foo.go"), []byte(input), nil)
+		if err != nil {
+			t.Fatalf("Process() = %v", err)
+		}
+		if string(got) != input {
+			t.Errorf("Got:\n%s\nWant:\n%s", got, input)
+		}
+	})
+}
+
 type testConfig struct {
 	gopathOnly bool
 	module     packagestest.Module
@@ -2665,7 +2686,7 @@
 				defer wg.Done()
 				_, err := t.process("foo.com", "p/first.go", nil, nil)
 				if err != nil {
-					t.Fatal(err)
+					t.Error(err)
 				}
 			}()
 		}
diff --git a/internal/imports/mod.go b/internal/imports/mod.go
index 3ae859e..28d4b1f 100644
--- a/internal/imports/mod.go
+++ b/internal/imports/mod.go
@@ -14,9 +14,9 @@
 	"strconv"
 	"strings"
 
+	"golang.org/x/mod/module"
+	"golang.org/x/mod/semver"
 	"golang.org/x/tools/internal/gopathwalk"
-	"golang.org/x/tools/internal/module"
-	"golang.org/x/tools/internal/semver"
 )
 
 // ModuleResolver implements resolver for modules using the go command as little
@@ -146,7 +146,7 @@
 }
 
 func (r *ModuleResolver) initAllMods() error {
-	stdout, err := r.env.invokeGo("list", "-m", "-json", "...")
+	stdout, err := r.env.invokeGo(context.TODO(), "list", "-m", "-json", "...")
 	if err != nil {
 		return err
 	}
@@ -162,6 +162,8 @@
 			// Can't do anything with a module that's not downloaded.
 			continue
 		}
+		// golang/go#36193: the go command doesn't always clean paths.
+		mod.Dir = filepath.Clean(mod.Dir)
 		r.modsByModPath = append(r.modsByModPath, mod)
 		r.modsByDir = append(r.modsByDir, mod)
 		if mod.Main {
@@ -579,7 +581,7 @@
 				err:    fmt.Errorf("invalid module cache path: %v", subdir),
 			}
 		}
-		modPath, err := module.DecodePath(filepath.ToSlash(matches[1]))
+		modPath, err := module.UnescapePath(filepath.ToSlash(matches[1]))
 		if err != nil {
 			if r.env.Debug {
 				r.env.Logf("decoding module cache path %q: %v", subdir, err)
@@ -697,7 +699,7 @@
 {{.GoVersion}}
 {{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}
 `
-	stdout, err := env.invokeGo("list", "-m", "-f", format)
+	stdout, err := env.invokeGo(context.TODO(), "list", "-m", "-f", format)
 	if err != nil {
 		return nil, false, nil
 	}
diff --git a/internal/imports/mod_112_test.go b/internal/imports/mod_112_test.go
index 0305f48..106db7c 100644
--- a/internal/imports/mod_112_test.go
+++ b/internal/imports/mod_112_test.go
@@ -3,6 +3,7 @@
 package imports
 
 import (
+	"context"
 	"testing"
 )
 
@@ -13,7 +14,7 @@
 package x
 `, "")
 	defer mt.cleanup()
-	if _, err := mt.env.invokeGo("mod", "download", "rsc.io/quote@v1.5.1"); err != nil {
+	if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote@v1.5.1"); err != nil {
 		t.Fatal(err)
 	}
 
diff --git a/internal/imports/mod_test.go b/internal/imports/mod_test.go
index 882be4b..8df024f 100644
--- a/internal/imports/mod_test.go
+++ b/internal/imports/mod_test.go
@@ -18,8 +18,8 @@
 	"sync"
 	"testing"
 
+	"golang.org/x/mod/module"
 	"golang.org/x/tools/internal/gopathwalk"
-	"golang.org/x/tools/internal/module"
 	"golang.org/x/tools/internal/testenv"
 	"golang.org/x/tools/txtar"
 )
@@ -231,10 +231,10 @@
 	mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `pkg.*mod.*/sampler@.*$`)
 
 	// Populate vendor/ and clear out the mod cache so we can't cheat.
-	if _, err := mt.env.invokeGo("mod", "vendor"); err != nil {
+	if _, err := mt.env.invokeGo(context.Background(), "mod", "vendor"); err != nil {
 		t.Fatal(err)
 	}
-	if _, err := mt.env.invokeGo("clean", "-modcache"); err != nil {
+	if _, err := mt.env.invokeGo(context.Background(), "clean", "-modcache"); err != nil {
 		t.Fatal(err)
 	}
 
@@ -259,7 +259,7 @@
 	defer mt.cleanup()
 
 	// Populate vendor/.
-	if _, err := mt.env.invokeGo("mod", "vendor"); err != nil {
+	if _, err := mt.env.invokeGo(context.Background(), "mod", "vendor"); err != nil {
 		t.Fatal(err)
 	}
 
@@ -686,7 +686,7 @@
 		t.Fatalf("checking if go.mod exists: %v", err)
 	}
 	if err == nil {
-		if _, err := env.invokeGo("mod", "download"); err != nil {
+		if _, err := env.invokeGo(context.Background(), "mod", "download"); err != nil {
 			t.Fatal(err)
 		}
 	}
@@ -739,7 +739,7 @@
 	i := strings.LastIndex(arName, "_v")
 	ver := strings.TrimSuffix(arName[i+1:], ".txt")
 	modDir := strings.Replace(arName[:i], "_", "/", -1)
-	modPath, err := module.DecodePath(modDir)
+	modPath, err := module.UnescapePath(modDir)
 	if err != nil {
 		return err
 	}
@@ -868,7 +868,7 @@
 `, "")
 	defer mt.cleanup()
 
-	if _, err := mt.env.invokeGo("mod", "download", "rsc.io/quote/v2@v2.0.1"); err != nil {
+	if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote/v2@v2.0.1"); err != nil {
 		t.Fatal(err)
 	}
 
diff --git a/internal/jsonrpc2/jsonrpc2.go b/internal/jsonrpc2/jsonrpc2.go
index 963f818..39ac3eb 100644
--- a/internal/jsonrpc2/jsonrpc2.go
+++ b/internal/jsonrpc2/jsonrpc2.go
@@ -147,14 +147,15 @@
 	for _, h := range c.handlers {
 		ctx = h.Request(ctx, c, Send, request)
 	}
-	// we have to add ourselves to the pending map before we send, otherwise we
-	// are racing the response
-	rchan := make(chan *WireResponse)
+	// We have to add ourselves to the pending map before we send, otherwise we
+	// are racing the response. Also add a buffer to rchan, so that if we get a
+	// wire response between the time this call is cancelled and id is deleted
+	// from c.pending, the send to rchan will not block.
+	rchan := make(chan *WireResponse, 1)
 	c.pendingMu.Lock()
 	c.pending[id] = rchan
 	c.pendingMu.Unlock()
 	defer func() {
-		// clean up the pending response handler on the way out
 		c.pendingMu.Lock()
 		delete(c.pending, id)
 		c.pendingMu.Unlock()
@@ -189,7 +190,7 @@
 		}
 		return nil
 	case <-ctx.Done():
-		// allow the handler to propagate the cancel
+		// Allow the handler to propagate the cancel.
 		cancelled := false
 		for _, h := range c.handlers {
 			if h.Cancel(ctx, c, id, cancelled) {
@@ -315,7 +316,8 @@
 		// get the data for a message
 		data, n, err := c.stream.Read(runCtx)
 		if err != nil {
-			// the stream failed, we cannot continue
+			// The stream failed, we cannot continue. If the client disconnected
+			// normally, we should get ErrDisconnected here.
 			return err
 		}
 		// read a combined message
@@ -328,10 +330,10 @@
 			}
 			continue
 		}
-		// work out which kind of message we have
+		// Work out whether this is a request or response.
 		switch {
 		case msg.Method != "":
-			// if method is set it must be a request
+			// If method is set it must be a request.
 			reqCtx, cancelReq := context.WithCancel(runCtx)
 			thisRequest := nextRequest
 			nextRequest = make(chan struct{})
@@ -373,21 +375,19 @@
 				}
 			}()
 		case msg.ID != nil:
-			// we have a response, get the pending entry from the map
+			// If method is not set, this should be a response, in which case we must
+			// have an id to send the response back to the caller.
 			c.pendingMu.Lock()
-			rchan := c.pending[*msg.ID]
-			if rchan != nil {
-				delete(c.pending, *msg.ID)
-			}
+			rchan, ok := c.pending[*msg.ID]
 			c.pendingMu.Unlock()
-			// and send the reply to the channel
-			response := &WireResponse{
-				Result: msg.Result,
-				Error:  msg.Error,
-				ID:     msg.ID,
+			if ok {
+				response := &WireResponse{
+					Result: msg.Result,
+					Error:  msg.Error,
+					ID:     msg.ID,
+				}
+				rchan <- response
 			}
-			rchan <- response
-			close(rchan)
 		default:
 			for _, h := range c.handlers {
 				h.Error(runCtx, fmt.Errorf("message not a call, notify or response, ignoring"))
diff --git a/internal/jsonrpc2/jsonrpc2_test.go b/internal/jsonrpc2/jsonrpc2_test.go
index 192a5e8..4fcca31 100644
--- a/internal/jsonrpc2/jsonrpc2_test.go
+++ b/internal/jsonrpc2/jsonrpc2_test.go
@@ -115,7 +115,7 @@
 			w.Close()
 		}()
 		if err := conn.Run(ctx); err != nil {
-			t.Fatalf("Stream failed: %v", err)
+			t.Errorf("Stream failed: %v", err)
 		}
 	}()
 	return conn
diff --git a/internal/jsonrpc2/serve.go b/internal/jsonrpc2/serve.go
new file mode 100644
index 0000000..c2cfa60
--- /dev/null
+++ b/internal/jsonrpc2/serve.go
@@ -0,0 +1,115 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jsonrpc2
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"log"
+	"net"
+	"os"
+	"time"
+)
+
+// NOTE: This file provides an experimental API for serving multiple remote
+// jsonrpc2 clients over the network. For now, it is intentionally similar to
+// net/http, but that may change in the future as we figure out the correct
+// semantics.
+
+// A StreamServer is used to serve incoming jsonrpc2 clients communicating over
+// a newly created stream.
+type StreamServer interface {
+	ServeStream(context.Context, Stream) error
+}
+
+// The ServerFunc type is an adapter that implements the StreamServer interface
+// using an ordinary function.
+type ServerFunc func(context.Context, Stream) error
+
+// ServeStream calls f(ctx, s).
+func (f ServerFunc) ServeStream(ctx context.Context, s Stream) error {
+	return f(ctx, s)
+}
+
+// HandlerServer returns a StreamServer that handles incoming streams using the
+// provided handler.
+func HandlerServer(h Handler) StreamServer {
+	return ServerFunc(func(ctx context.Context, s Stream) error {
+		conn := NewConn(s)
+		conn.AddHandler(h)
+		return conn.Run(ctx)
+	})
+}
+
+// ListenAndServe starts an jsonrpc2 server on the given address.  If
+// idleTimeout is non-zero, ListenAndServe exits after there are no clients for
+// this duration, otherwise it exits only on error.
+func ListenAndServe(ctx context.Context, network, addr string, server StreamServer, idleTimeout time.Duration) error {
+	ln, err := net.Listen(network, addr)
+	if err != nil {
+		return err
+	}
+	defer ln.Close()
+	if network == "unix" {
+		defer os.Remove(addr)
+	}
+	return Serve(ctx, ln, server, idleTimeout)
+}
+
+// ErrIdleTimeout is returned when serving timed out waiting for new connections.
+var ErrIdleTimeout = errors.New("timed out waiting for new connections")
+
+// Serve accepts incoming connections from the network, and handles them using
+// the provided server. If idleTimeout is non-zero, ListenAndServe exits after
+// there are no clients for this duration, otherwise it exits only on error.
+func Serve(ctx context.Context, ln net.Listener, server StreamServer, idleTimeout time.Duration) error {
+	// Max duration: ~290 years; surely that's long enough.
+	const forever = 1<<63 - 1
+	if idleTimeout <= 0 {
+		idleTimeout = forever
+	}
+	connTimer := time.NewTimer(idleTimeout)
+
+	newConns := make(chan net.Conn)
+	doneListening := make(chan error)
+	closedConns := make(chan error)
+
+	go func() {
+		for {
+			nc, err := ln.Accept()
+			if err != nil {
+				doneListening <- fmt.Errorf("Accept(): %v", err)
+				return
+			}
+			newConns <- nc
+		}
+	}()
+
+	activeConns := 0
+	for {
+		select {
+		case netConn := <-newConns:
+			activeConns++
+			connTimer.Stop()
+			stream := NewHeaderStream(netConn, netConn)
+			go func() {
+				closedConns <- server.ServeStream(ctx, stream)
+			}()
+		case err := <-doneListening:
+			return err
+		case err := <-closedConns:
+			log.Printf("closed a connection with error: %v", err)
+			activeConns--
+			if activeConns == 0 {
+				connTimer.Reset(idleTimeout)
+			}
+		case <-connTimer.C:
+			return ErrIdleTimeout
+		case <-ctx.Done():
+			return ctx.Err()
+		}
+	}
+}
diff --git a/internal/jsonrpc2/serve_test.go b/internal/jsonrpc2/serve_test.go
new file mode 100644
index 0000000..ec6e1e1
--- /dev/null
+++ b/internal/jsonrpc2/serve_test.go
@@ -0,0 +1,59 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package jsonrpc2
+
+import (
+	"context"
+	"net"
+	"sync"
+	"testing"
+	"time"
+)
+
+func TestIdleTimeout(t *testing.T) {
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()
+
+	ln, err := net.Listen("tcp", ":0")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer ln.Close()
+
+	connect := func() net.Conn {
+		conn, err := net.DialTimeout("tcp", ln.Addr().String(), 5*time.Second)
+		if err != nil {
+			panic(err)
+		}
+		return conn
+	}
+
+	server := HandlerServer(EmptyHandler{})
+	// connTimer := &fakeTimer{c: make(chan time.Time, 1)}
+	var (
+		runErr error
+		wg     sync.WaitGroup
+	)
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		runErr = Serve(ctx, ln, server, 100*time.Millisecond)
+	}()
+
+	// Exercise some connection/disconnection patterns, and then assert that when
+	// our timer fires, the server exits.
+	conn1 := connect()
+	conn2 := connect()
+	conn1.Close()
+	conn2.Close()
+	conn3 := connect()
+	conn3.Close()
+
+	wg.Wait()
+
+	if runErr != ErrIdleTimeout {
+		t.Errorf("run() returned error %v, want %v", runErr, ErrIdleTimeout)
+	}
+}
diff --git a/internal/jsonrpc2/servertest/servertest.go b/internal/jsonrpc2/servertest/servertest.go
new file mode 100644
index 0000000..1217591
--- /dev/null
+++ b/internal/jsonrpc2/servertest/servertest.go
@@ -0,0 +1,126 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package servertest provides utilities for running tests against a remote LSP
+// server.
+package servertest
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"net"
+	"sync"
+
+	"golang.org/x/tools/internal/jsonrpc2"
+)
+
+// Connector is the interface used to connect to a server.
+type Connector interface {
+	Connect(context.Context) *jsonrpc2.Conn
+}
+
+// TCPServer is a helper for executing tests against a remote jsonrpc2
+// connection. Once initialized, its Addr field may be used to connect a
+// jsonrpc2 client.
+type TCPServer struct {
+	Addr string
+
+	ln  net.Listener
+	cls *closerList
+}
+
+// NewTCPServer returns a new test server listening on local tcp port and
+// serving incoming jsonrpc2 streams using the provided stream server. It
+// panics on any error.
+func NewTCPServer(ctx context.Context, server jsonrpc2.StreamServer) *TCPServer {
+	ln, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		panic(fmt.Sprintf("servertest: failed to listen: %v", err))
+	}
+	go jsonrpc2.Serve(ctx, ln, server, 0)
+	return &TCPServer{Addr: ln.Addr().String(), ln: ln, cls: &closerList{}}
+}
+
+// Connect dials the test server and returns a jsonrpc2 Connection that is
+// ready for use.
+func (s *TCPServer) Connect(ctx context.Context) *jsonrpc2.Conn {
+	netConn, err := net.Dial("tcp", s.Addr)
+	if err != nil {
+		panic(fmt.Sprintf("servertest: failed to connect to test instance: %v", err))
+	}
+	s.cls.add(func() {
+		netConn.Close()
+	})
+	conn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn, netConn))
+	go conn.Run(ctx)
+	return conn
+}
+
+// Close closes all connected pipes.
+func (s *TCPServer) Close() error {
+	s.cls.closeAll()
+	return nil
+}
+
+// PipeServer is a test server that handles connections over io.Pipes.
+type PipeServer struct {
+	server jsonrpc2.StreamServer
+	cls    *closerList
+}
+
+// NewPipeServer returns a test server that can be connected to via io.Pipes.
+func NewPipeServer(ctx context.Context, server jsonrpc2.StreamServer) *PipeServer {
+	return &PipeServer{server: server, cls: &closerList{}}
+}
+
+// Connect creates new io.Pipes and binds them to the underlying StreamServer.
+func (s *PipeServer) Connect(ctx context.Context) *jsonrpc2.Conn {
+	// Pipes connect like this:
+	// Client🡒(sWriter)🡒(sReader)🡒Server
+	//       🡔(cReader)🡐(cWriter)🡗
+	sReader, sWriter := io.Pipe()
+	cReader, cWriter := io.Pipe()
+	s.cls.add(func() {
+		sReader.Close()
+		sWriter.Close()
+		cReader.Close()
+		cWriter.Close()
+	})
+	serverStream := jsonrpc2.NewStream(sReader, cWriter)
+	go s.server.ServeStream(ctx, serverStream)
+
+	clientStream := jsonrpc2.NewStream(cReader, sWriter)
+	clientConn := jsonrpc2.NewConn(clientStream)
+	go clientConn.Run(ctx)
+	return clientConn
+}
+
+// Close closes all connected pipes.
+func (s *PipeServer) Close() error {
+	s.cls.closeAll()
+	return nil
+}
+
+// closerList tracks closers to run when a testserver is closed.  This is a
+// convenience, so that callers don't have to worry about closing each
+// connection.
+type closerList struct {
+	mu      sync.Mutex
+	closers []func()
+}
+
+func (l *closerList) add(closer func()) {
+	l.mu.Lock()
+	defer l.mu.Unlock()
+	l.closers = append(l.closers, closer)
+}
+
+func (l *closerList) closeAll() {
+	l.mu.Lock()
+	defer l.mu.Unlock()
+	for _, closer := range l.closers {
+		closer()
+	}
+}
diff --git a/internal/jsonrpc2/servertest/servertest_test.go b/internal/jsonrpc2/servertest/servertest_test.go
new file mode 100644
index 0000000..21b540a
--- /dev/null
+++ b/internal/jsonrpc2/servertest/servertest_test.go
@@ -0,0 +1,59 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package servertest
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"golang.org/x/tools/internal/jsonrpc2"
+)
+
+type fakeHandler struct {
+	jsonrpc2.EmptyHandler
+}
+
+type msg struct {
+	Msg string
+}
+
+func (fakeHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
+	if err := r.Reply(ctx, &msg{"pong"}, nil); err != nil {
+		panic(err)
+	}
+	return true
+}
+
+func TestTestServer(t *testing.T) {
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+	server := jsonrpc2.HandlerServer(fakeHandler{})
+	tcpTS := NewTCPServer(ctx, server)
+	defer tcpTS.Close()
+	pipeTS := NewPipeServer(ctx, server)
+	defer pipeTS.Close()
+
+	tests := []struct {
+		name      string
+		connector Connector
+	}{
+		{"tcp", tcpTS},
+		{"pipe", pipeTS},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			conn := test.connector.Connect(ctx)
+			var got msg
+			if err := conn.Call(ctx, "ping", &msg{"ping"}, &got); err != nil {
+				t.Fatal(err)
+			}
+			if want := "pong"; got.Msg != want {
+				t.Errorf("conn.Call(...): returned %q, want %q", got, want)
+			}
+		})
+	}
+}
diff --git a/internal/jsonrpc2/stream.go b/internal/jsonrpc2/stream.go
index f850c27..2d1e7c4 100644
--- a/internal/jsonrpc2/stream.go
+++ b/internal/jsonrpc2/stream.go
@@ -8,6 +8,7 @@
 	"bufio"
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"strconv"
@@ -28,6 +29,9 @@
 	Write(context.Context, []byte) (int64, error)
 }
 
+// ErrDisconnected signals that the stream or connection exited normally.
+var ErrDisconnected = errors.New("disconnected")
+
 // NewStream returns a Stream built on top of an io.Reader and io.Writer
 // The messages are sent with no wrapping, and rely on json decode consistency
 // to determine message boundaries.
@@ -52,6 +56,9 @@
 	}
 	var raw json.RawMessage
 	if err := s.in.Decode(&raw); err != nil {
+		if err == io.EOF {
+			return nil, 0, ErrDisconnected
+		}
 		return nil, 0, err
 	}
 	return raw, int64(len(raw)), nil
@@ -96,6 +103,10 @@
 	for {
 		line, err := s.in.ReadString('\n')
 		total += int64(len(line))
+		if err == io.EOF {
+			// A normal disconnection will terminate with EOF before the next header.
+			return nil, total, ErrDisconnected
+		}
 		if err != nil {
 			return nil, total, fmt.Errorf("failed reading header line %q", err)
 		}
diff --git a/internal/jsonrpc2/wire.go b/internal/jsonrpc2/wire.go
index 3e891e0..127feb1 100644
--- a/internal/jsonrpc2/wire.go
+++ b/internal/jsonrpc2/wire.go
@@ -104,7 +104,7 @@
 		return err
 	}
 	if version != "2.0" {
-		return fmt.Errorf("Invalid RPC version %v", version)
+		return fmt.Errorf("invalid RPC version %v", version)
 	}
 	return nil
 }
diff --git a/internal/lsp/cache/analysis.go b/internal/lsp/cache/analysis.go
index f4c99cd..2fd4714 100644
--- a/internal/lsp/cache/analysis.go
+++ b/internal/lsp/cache/analysis.go
@@ -27,7 +27,7 @@
 	var roots []*actionHandle
 
 	for _, a := range analyzers {
-		ah, err := s.actionHandle(ctx, packageID(id), source.ParseFull, a)
+		ah, err := s.actionHandle(ctx, packageID(id), a)
 		if err != nil {
 			return nil, err
 		}
@@ -50,6 +50,8 @@
 	return results, nil
 }
 
+type actionHandleKey string
+
 // An action represents one unit of analysis work: the application of
 // one analysis to one package. Actions form a DAG, both within a
 // package (as different analyzers are applied, either in sequence or
@@ -79,15 +81,15 @@
 	typ reflect.Type
 }
 
-func (s *snapshot) actionHandle(ctx context.Context, id packageID, mode source.ParseMode, a *analysis.Analyzer) (*actionHandle, error) {
-	act := s.getActionHandle(id, mode, a)
+func (s *snapshot) actionHandle(ctx context.Context, id packageID, a *analysis.Analyzer) (*actionHandle, error) {
+	ph := s.getPackage(id, source.ParseFull)
+	if ph == nil {
+		return nil, errors.Errorf("no PackageHandle for %s", id)
+	}
+	act := s.getActionHandle(id, ph.mode, a)
 	if act != nil {
 		return act, nil
 	}
-	ph := s.getPackage(id, mode)
-	if ph == nil {
-		return nil, errors.Errorf("no PackageHandle for %s:%v", id, mode == source.ParseExported)
-	}
 	if len(ph.key) == 0 {
 		return nil, errors.Errorf("no key for PackageHandle %s", id)
 	}
@@ -102,7 +104,7 @@
 	var deps []*actionHandle
 	// Add a dependency on each required analyzers.
 	for _, req := range a.Requires {
-		reqActionHandle, err := s.actionHandle(ctx, id, mode, req)
+		reqActionHandle, err := s.actionHandle(ctx, id, req)
 		if err != nil {
 			return nil, err
 		}
@@ -122,7 +124,7 @@
 			}
 			sort.Strings(importIDs) // for determinism
 			for _, importID := range importIDs {
-				depActionHandle, err := s.actionHandle(ctx, packageID(importID), source.ParseExported, a)
+				depActionHandle, err := s.actionHandle(ctx, packageID(importID), a)
 				if err != nil {
 					return nil, err
 				}
@@ -164,8 +166,8 @@
 	return data.diagnostics, data.result, data.err
 }
 
-func buildActionKey(a *analysis.Analyzer, ph *packageHandle) string {
-	return hashContents([]byte(fmt.Sprintf("%p %s", a, string(ph.key))))
+func buildActionKey(a *analysis.Analyzer, ph *packageHandle) actionHandleKey {
+	return actionHandleKey(hashContents([]byte(fmt.Sprintf("%p %s", a, string(ph.key)))))
 }
 
 func (act *actionHandle) String() string {
diff --git a/internal/lsp/cache/cache.go b/internal/lsp/cache/cache.go
index 56531dd..4b71ebc 100644
--- a/internal/lsp/cache/cache.go
+++ b/internal/lsp/cache/cache.go
@@ -9,6 +9,7 @@
 	"crypto/sha1"
 	"fmt"
 	"go/token"
+	"reflect"
 	"strconv"
 	"sync/atomic"
 
@@ -18,23 +19,28 @@
 	"golang.org/x/tools/internal/span"
 )
 
-func New(options func(*source.Options)) source.Cache {
+func New(options func(*source.Options), debugState *debug.State) *Cache {
+	if debugState == nil {
+		debugState = &debug.State{}
+	}
 	index := atomic.AddInt64(&cacheIndex, 1)
-	c := &cache{
+	c := &Cache{
 		fs:      &nativeFileSystem{},
 		id:      strconv.FormatInt(index, 10),
 		fset:    token.NewFileSet(),
 		options: options,
+		debug:   debugState,
 	}
-	debug.AddCache(debugCache{c})
+	debugState.AddCache(debugCache{c})
 	return c
 }
 
-type cache struct {
+type Cache struct {
 	fs      source.FileSystem
 	id      string
 	fset    *token.FileSet
 	options func(*source.Options)
+	debug   *debug.State
 
 	store memoize.Store
 }
@@ -44,7 +50,7 @@
 }
 
 type fileHandle struct {
-	cache      *cache
+	cache      *Cache
 	underlying source.FileHandle
 	handle     *memoize.Handle
 }
@@ -56,7 +62,7 @@
 	err   error
 }
 
-func (c *cache) GetFile(uri span.URI) source.FileHandle {
+func (c *Cache) GetFile(uri span.URI) source.FileHandle {
 	underlying := c.fs.GetFile(uri)
 	key := fileKey{
 		identity: underlying.Identity(),
@@ -73,19 +79,19 @@
 	}
 }
 
-func (c *cache) NewSession() source.Session {
+func (c *Cache) NewSession() *Session {
 	index := atomic.AddInt64(&sessionIndex, 1)
-	s := &session{
+	s := &Session{
 		cache:    c,
 		id:       strconv.FormatInt(index, 10),
-		options:  source.DefaultOptions,
+		options:  source.DefaultOptions(),
 		overlays: make(map[span.URI]*overlay),
 	}
-	debug.AddSession(debugSession{s})
+	c.debug.AddSession(DebugSession{s})
 	return s
 }
 
-func (c *cache) FileSet() *token.FileSet {
+func (c *Cache) FileSet() *token.FileSet {
 	return c.fset
 }
 
@@ -114,7 +120,8 @@
 
 var cacheIndex, sessionIndex, viewIndex int64
 
-type debugCache struct{ *cache }
+type debugCache struct{ *Cache }
 
-func (c *cache) ID() string                  { return c.id }
-func (c debugCache) FileSet() *token.FileSet { return c.fset }
+func (c *Cache) ID() string                         { return c.id }
+func (c debugCache) FileSet() *token.FileSet        { return c.fset }
+func (c debugCache) MemStats() map[reflect.Type]int { return c.store.Stats() }
diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go
index b578bae..be472fd 100644
--- a/internal/lsp/cache/check.go
+++ b/internal/lsp/cache/check.go
@@ -13,6 +13,7 @@
 	"go/types"
 	"path"
 	"sort"
+	"strings"
 	"sync"
 
 	"golang.org/x/tools/go/packages"
@@ -25,6 +26,8 @@
 	errors "golang.org/x/xerrors"
 )
 
+type packageHandleKey string
+
 // packageHandle implements source.PackageHandle.
 type packageHandle struct {
 	handle *memoize.Handle
@@ -41,7 +44,7 @@
 	m *metadata
 
 	// key is the hashed key for the package.
-	key []byte
+	key packageHandleKey
 }
 
 func (ph *packageHandle) packageKey() packageKey {
@@ -61,7 +64,6 @@
 
 // buildPackageHandle returns a source.PackageHandle for a given package and config.
 func (s *snapshot) buildPackageHandle(ctx context.Context, id packageID, mode source.ParseMode) (*packageHandle, error) {
-	// Check if we already have this PackageHandle cached.
 	if ph := s.getPackage(id, mode); ph != nil {
 		return ph, nil
 	}
@@ -86,7 +88,7 @@
 	key := ph.key
 	fset := s.view.session.cache.fset
 
-	h := s.view.session.cache.store.Bind(string(key), func(ctx context.Context) interface{} {
+	h := s.view.session.cache.store.Bind(key, func(ctx context.Context) interface{} {
 		// Begin loading the direct dependencies, in parallel.
 		for _, dep := range deps {
 			go func(dep *packageHandle) {
@@ -134,7 +136,7 @@
 	deps := make(map[packagePath]*packageHandle)
 
 	// Begin computing the key by getting the depKeys for all dependencies.
-	var depKeys [][]byte
+	var depKeys []packageHandleKey
 	for _, depID := range depList {
 		mode := source.ParseExported
 		if _, ok := s.isWorkspacePackage(depID); ok {
@@ -146,7 +148,7 @@
 
 			// One bad dependency should not prevent us from checking the entire package.
 			// Add a special key to mark a bad dependency.
-			depKeys = append(depKeys, []byte(fmt.Sprintf("%s import not found", id)))
+			depKeys = append(depKeys, packageHandleKey(fmt.Sprintf("%s import not found", id)))
 			continue
 		}
 		deps[depHandle.m.pkgPath] = depHandle
@@ -156,8 +158,12 @@
 	return ph, deps, nil
 }
 
-func checkPackageKey(id packageID, pghs []source.ParseGoHandle, cfg *packages.Config, deps [][]byte) []byte {
-	return []byte(hashContents([]byte(fmt.Sprintf("%s%s%s%s", id, hashParseKeys(pghs), hashConfig(cfg), hashContents(bytes.Join(deps, nil))))))
+func checkPackageKey(id packageID, pghs []source.ParseGoHandle, cfg *packages.Config, deps []packageHandleKey) packageHandleKey {
+	var depBytes []byte
+	for _, dep := range deps {
+		depBytes = append(depBytes, []byte(dep)...)
+	}
+	return packageHandleKey(hashContents([]byte(fmt.Sprintf("%s%s%s%s", id, hashParseKeys(pghs), hashConfig(cfg), hashContents(depBytes)))))
 }
 
 // hashConfig returns the hash for the *packages.Config.
@@ -206,6 +212,28 @@
 	return md
 }
 
+func hashImports(ctx context.Context, wsPackages []source.PackageHandle) (string, error) {
+	results := make(map[string]bool)
+	var imports []string
+	for _, ph := range wsPackages {
+		// Check package since we do not always invalidate the metadata.
+		pkg, err := ph.Check(ctx)
+		if err != nil {
+			return "", err
+		}
+		for _, path := range pkg.Imports() {
+			imp := path.PkgPath()
+			if _, ok := results[imp]; !ok {
+				results[imp] = true
+				imports = append(imports, imp)
+			}
+		}
+	}
+	sort.Strings(imports)
+	hashed := strings.Join(imports, ",")
+	return hashContents([]byte(hashed)), nil
+}
+
 func (ph *packageHandle) Cached() (source.Package, error) {
 	return ph.cached()
 }
@@ -246,6 +274,7 @@
 		mode:            mode,
 		goFiles:         goFiles,
 		compiledGoFiles: compiledGoFiles,
+		module:          m.module,
 		imports:         make(map[packagePath]*pkg),
 		typesSizes:      m.typesSizes,
 		typesInfo: &types.Info{
@@ -256,6 +285,7 @@
 			Selections: make(map[*ast.SelectorExpr]*types.Selection),
 			Scopes:     make(map[ast.Node]*types.Scope),
 		},
+		forTest: m.forTest,
 	}
 	var (
 		files        = make([]*ast.File, len(pkg.compiledGoFiles))
@@ -266,7 +296,7 @@
 	for i, ph := range pkg.compiledGoFiles {
 		wg.Add(1)
 		go func(i int, ph source.ParseGoHandle) {
-			files[i], _, parseErrors[i], actualErrors[i] = ph.Parse(ctx)
+			files[i], _, _, parseErrors[i], actualErrors[i] = ph.Parse(ctx)
 			wg.Done()
 		}(i, ph)
 	}
@@ -298,6 +328,9 @@
 	// Use the default type information for the unsafe package.
 	if pkg.pkgPath == "unsafe" {
 		pkg.types = types.Unsafe
+		// Don't type check Unsafe: it's unnecessary, and doing so exposes a data
+		// race to Unsafe.completed.
+		return pkg, nil
 	} else if len(files) == 0 { // not the unsafe package, no parsed files
 		return nil, errors.Errorf("no parsed files for package %s, expected: %s, errors: %v, list errors: %v", pkg.pkgPath, pkg.compiledGoFiles, actualErrors, rawErrors)
 	} else {
@@ -309,6 +342,10 @@
 			rawErrors = append(rawErrors, e)
 		},
 		Importer: importerFunc(func(pkgPath string) (*types.Package, error) {
+			// If the context was cancelled, we should abort.
+			if ctx.Err() != nil {
+				return nil, ctx.Err()
+			}
 			dep := deps[packagePath(pkgPath)]
 			if dep == nil {
 				// We may be in GOPATH mode, in which case we need to check vendor dirs.
@@ -331,6 +368,9 @@
 			if dep == nil {
 				return nil, errors.Errorf("no package for import %s", pkgPath)
 			}
+			if !isValidImport(m.pkgPath, dep.m.pkgPath) {
+				return nil, errors.Errorf("invalid use of internal package %s", pkgPath)
+			}
 			depPkg, err := dep.check(ctx)
 			if err != nil {
 				return nil, err
@@ -363,6 +403,17 @@
 	return pkg, nil
 }
 
+func isValidImport(pkgPath, importPkgPath packagePath) bool {
+	i := strings.LastIndex(string(importPkgPath), "/internal/")
+	if i == -1 {
+		return true
+	}
+	if pkgPath == "command-line-arguments" {
+		return true
+	}
+	return strings.HasPrefix(string(pkgPath), string(importPkgPath[:i]))
+}
+
 // An importFunc is an implementation of the single-method
 // types.Importer interface based on a function value.
 type importerFunc func(path string) (*types.Package, error)
diff --git a/internal/lsp/cache/debug.go b/internal/lsp/cache/debug.go
index 95d3fa2..d4ea528 100644
--- a/internal/lsp/cache/debug.go
+++ b/internal/lsp/cache/debug.go
@@ -14,13 +14,14 @@
 type debugView struct{ *view }
 
 func (v debugView) ID() string             { return v.id }
-func (v debugView) Session() debug.Session { return debugSession{v.session} }
+func (v debugView) Session() debug.Session { return DebugSession{v.session} }
+func (v debugView) Env() []string          { return v.Options().Env }
 
-type debugSession struct{ *session }
+type DebugSession struct{ *Session }
 
-func (s debugSession) ID() string         { return s.id }
-func (s debugSession) Cache() debug.Cache { return debugCache{s.cache} }
-func (s debugSession) Files() []*debug.File {
+func (s DebugSession) ID() string         { return s.id }
+func (s DebugSession) Cache() debug.Cache { return debugCache{s.cache} }
+func (s DebugSession) Files() []*debug.File {
 	var files []*debug.File
 	seen := make(map[span.URI]*debug.File)
 	s.overlayMu.Lock()
@@ -42,7 +43,7 @@
 	return files
 }
 
-func (s debugSession) File(hash string) *debug.File {
+func (s DebugSession) File(hash string) *debug.File {
 	s.overlayMu.Lock()
 	defer s.overlayMu.Unlock()
 	for _, overlay := range s.overlays {
diff --git a/internal/lsp/cache/errors.go b/internal/lsp/cache/errors.go
index f910fc1..16ddd50 100644
--- a/internal/lsp/cache/errors.go
+++ b/internal/lsp/cache/errors.go
@@ -177,11 +177,11 @@
 
 func typeErrorRange(ctx context.Context, fset *token.FileSet, pkg *pkg, pos token.Pos) (span.Span, error) {
 	posn := fset.Position(pos)
-	ph, _, err := findFileInPackage(pkg, span.FileURI(posn.Filename))
+	ph, _, err := source.FindFileInPackage(pkg, span.URIFromPath(posn.Filename))
 	if err != nil {
 		return span.Span{}, err
 	}
-	_, m, _, err := ph.Cached()
+	_, _, m, _, err := ph.Cached()
 	if err != nil {
 		return span.Span{}, err
 	}
@@ -213,11 +213,11 @@
 }
 
 func scannerErrorRange(ctx context.Context, fset *token.FileSet, pkg *pkg, posn token.Position) (span.Span, error) {
-	ph, _, err := findFileInPackage(pkg, span.FileURI(posn.Filename))
+	ph, _, err := source.FindFileInPackage(pkg, span.URIFromPath(posn.Filename))
 	if err != nil {
 		return span.Span{}, err
 	}
-	file, _, _, err := ph.Cached()
+	file, _, _, _, err := ph.Cached()
 	if err != nil {
 		return span.Span{}, err
 	}
@@ -232,38 +232,17 @@
 // spanToRange converts a span.Span to a protocol.Range,
 // assuming that the span belongs to the package whose diagnostics are being computed.
 func spanToRange(ctx context.Context, pkg *pkg, spn span.Span) (protocol.Range, error) {
-	ph, _, err := findFileInPackage(pkg, spn.URI())
+	ph, _, err := source.FindFileInPackage(pkg, spn.URI())
 	if err != nil {
 		return protocol.Range{}, err
 	}
-	_, m, _, err := ph.Cached()
+	_, _, m, _, err := ph.Cached()
 	if err != nil {
 		return protocol.Range{}, err
 	}
 	return m.Range(spn)
 }
 
-func findFileInPackage(pkg source.Package, uri span.URI) (source.ParseGoHandle, source.Package, error) {
-	queue := []source.Package{pkg}
-	seen := make(map[string]bool)
-
-	for len(queue) > 0 {
-		pkg := queue[0]
-		queue = queue[1:]
-		seen[pkg.ID()] = true
-
-		if f, err := pkg.File(uri); err == nil {
-			return f, pkg, nil
-		}
-		for _, dep := range pkg.Imports() {
-			if !seen[dep.ID()] {
-				queue = append(queue, dep)
-			}
-		}
-	}
-	return nil, nil, errors.Errorf("no file for %s in package %s", uri, pkg.ID())
-}
-
 // parseGoListError attempts to parse a standard `go list` error message
 // by stripping off the trailing error message.
 //
@@ -297,7 +276,7 @@
 	// Imports have quotation marks around them.
 	circImp := strconv.Quote(importList[1])
 	for _, ph := range pkg.compiledGoFiles {
-		fh, _, _, err := ph.Parse(ctx)
+		fh, _, _, _, err := ph.Parse(ctx)
 		if err != nil {
 			continue
 		}
diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go
index 7248a65..268bc6b 100644
--- a/internal/lsp/cache/load.go
+++ b/internal/lsp/cache/load.go
@@ -33,13 +33,15 @@
 	errors          []packages.Error
 	deps            []packageID
 	missingDeps     map[packagePath]struct{}
+	module          *packagesinternal.Module
 
 	// config is the *packages.Config associated with the loaded package.
 	config *packages.Config
 }
 
-func (s *snapshot) load(ctx context.Context, scopes ...interface{}) ([]*metadata, error) {
+func (s *snapshot) load(ctx context.Context, scopes ...interface{}) error {
 	var query []string
+	var containsDir bool // for logging
 	for _, scope := range scopes {
 		switch scope := scope.(type) {
 		case packagePath:
@@ -70,84 +72,31 @@
 		default:
 			panic(fmt.Sprintf("unknown scope type %T", scope))
 		}
+		switch scope.(type) {
+		case directoryURI, viewLoadScope:
+			containsDir = true
+		}
 	}
 	sort.Strings(query) // for determinism
 
 	ctx, done := trace.StartSpan(ctx, "cache.view.load", telemetry.Query.Of(query))
 	defer done()
 
-	cfg := s.view.Config(ctx)
+	cfg := s.Config(ctx)
 	pkgs, err := s.view.loadPackages(cfg, query...)
 
 	// If the context was canceled, return early. Otherwise, we might be
 	// type-checking an incomplete result. Check the context directly,
 	// because go/packages adds extra information to the error.
 	if ctx.Err() != nil {
-		return nil, ctx.Err()
+		return ctx.Err()
 	}
 
 	log.Print(ctx, "go/packages.Load", tag.Of("snapshot", s.ID()), tag.Of("query", query), tag.Of("packages", len(pkgs)))
 	if len(pkgs) == 0 {
-		return nil, err
+		return err
 	}
-	return s.updateMetadata(ctx, scopes, pkgs, cfg)
-}
-
-// shouldLoad reparses a file's package and import declarations to
-// determine if the file requires a metadata reload.
-func (c *cache) shouldLoad(ctx context.Context, s *snapshot, originalFH, currentFH source.FileHandle) bool {
-	if originalFH == nil {
-		return currentFH.Identity().Kind == source.Go
-	}
-	// If the file hasn't changed, there's no need to reload.
-	if originalFH.Identity().String() == currentFH.Identity().String() {
-		return false
-	}
-	// If a go.mod file's contents have changed, always invalidate metadata.
-	if kind := originalFH.Identity().Kind; kind == source.Mod {
-		return true
-	}
-	// Get the original and current parsed files in order to check package name and imports.
-	original, _, _, originalErr := c.ParseGoHandle(originalFH, source.ParseHeader).Parse(ctx)
-	current, _, _, currentErr := c.ParseGoHandle(currentFH, source.ParseHeader).Parse(ctx)
-	if originalErr != nil || currentErr != nil {
-		return (originalErr == nil) != (currentErr == nil)
-	}
-
-	// Check if the package's metadata has changed. The cases handled are:
-	//    1. A package's name has changed
-	//    2. A file's imports have changed
-	if original.Name.Name != current.Name.Name {
-		return true
-	}
-	// If the package's imports have increased, definitely re-run `go list`.
-	if len(original.Imports) < len(current.Imports) {
-		return true
-	}
-	importSet := make(map[string]struct{})
-	for _, importSpec := range original.Imports {
-		importSet[importSpec.Path.Value] = struct{}{}
-	}
-	// If any of the current imports were not in the original imports.
-	for _, importSpec := range current.Imports {
-		if _, ok := importSet[importSpec.Path.Value]; !ok {
-			return true
-		}
-	}
-	return false
-}
-
-func (s *snapshot) updateMetadata(ctx context.Context, scopes []interface{}, pkgs []*packages.Package, cfg *packages.Config) ([]*metadata, error) {
-	var results []*metadata
 	for _, pkg := range pkgs {
-		// Don't log output for full workspace packages.Loads.
-		var containsDir bool
-		for _, scope := range scopes {
-			switch scope.(type) {
-			case directoryURI, viewLoadScope:
-				containsDir = true
-			}
-		}
 		if !containsDir || s.view.Options().VerboseOutput {
 			log.Print(ctx, "go/packages.Load", tag.Of("snapshot", s.ID()), tag.Of("package", pkg.PkgPath), tag.Of("files", pkg.CompiledGoFiles))
 		}
@@ -156,32 +105,36 @@
 		if len(pkg.GoFiles) == 0 && len(pkg.CompiledGoFiles) == 0 {
 			continue
 		}
+		// Special case for the builtin package, as it has no dependencies.
+		if pkg.PkgPath == "builtin" {
+			if err := s.view.buildBuiltinPackage(ctx, pkg.GoFiles); err != nil {
+				return err
+			}
+			continue
+		}
 		// Skip test main packages.
 		if isTestMain(ctx, pkg, s.view.gocache) {
 			continue
 		}
 		// Set the metadata for this package.
-		if err := s.updateImports(ctx, packagePath(pkg.PkgPath), pkg, cfg, map[packageID]struct{}{}); err != nil {
-			return nil, err
+		m, err := s.setMetadata(ctx, packagePath(pkg.PkgPath), pkg, cfg, map[packageID]struct{}{})
+		if err != nil {
+			return err
 		}
-		if m := s.getMetadata(packageID(pkg.ID)); m != nil {
-			results = append(results, m)
+		if _, err := s.buildPackageHandle(ctx, m.id, source.ParseFull); err != nil {
+			return err
 		}
 	}
-
 	// Rebuild the import graph when the metadata is updated.
 	s.clearAndRebuildImportGraph()
 
-	if len(results) == 0 {
-		return nil, errors.Errorf("no metadata for %s", scopes)
-	}
-	return results, nil
+	return nil
 }
 
-func (s *snapshot) updateImports(ctx context.Context, pkgPath packagePath, pkg *packages.Package, cfg *packages.Config, seen map[packageID]struct{}) error {
+func (s *snapshot) setMetadata(ctx context.Context, pkgPath packagePath, pkg *packages.Package, cfg *packages.Config, seen map[packageID]struct{}) (*metadata, error) {
 	id := packageID(pkg.ID)
 	if _, ok := seen[id]; ok {
-		return errors.Errorf("import cycle detected: %q", id)
+		return nil, errors.Errorf("import cycle detected: %q", id)
 	}
 	// Recreate the metadata rather than reusing it to avoid locking.
 	m := &metadata{
@@ -192,21 +145,23 @@
 		typesSizes: pkg.TypesSizes,
 		errors:     pkg.Errors,
 		config:     cfg,
+		module:     packagesinternal.GetModule(pkg),
 	}
 
 	for _, filename := range pkg.CompiledGoFiles {
-		uri := span.FileURI(filename)
+		uri := span.URIFromPath(filename)
 		m.compiledGoFiles = append(m.compiledGoFiles, uri)
 		s.addID(uri, m.id)
 	}
 	for _, filename := range pkg.GoFiles {
-		uri := span.FileURI(filename)
+		uri := span.URIFromPath(filename)
 		m.goFiles = append(m.goFiles, uri)
 		s.addID(uri, m.id)
 	}
 
-	seen[id] = struct{}{}
-	copied := make(map[packageID]struct{})
+	copied := map[packageID]struct{}{
+		id: struct{}{},
+	}
 	for k, v := range seen {
 		copied[k] = v
 	}
@@ -225,14 +180,41 @@
 			continue
 		}
 		if s.getMetadata(importID) == nil {
-			if err := s.updateImports(ctx, importPkgPath, importPkg, cfg, copied); err != nil {
+			if _, err := s.setMetadata(ctx, importPkgPath, importPkg, cfg, copied); err != nil {
 				log.Error(ctx, "error in dependency", err)
 			}
 		}
 	}
+
 	// Add the metadata to the cache.
-	s.setMetadata(m)
-	return nil
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	// TODO: We should make sure not to set duplicate metadata,
+	// and instead panic here. This can be done by making sure not to
+	// reset metadata information for packages we've already seen.
+	if original, ok := s.metadata[m.id]; ok {
+		m = original
+	} else {
+		s.metadata[m.id] = m
+	}
+
+	// Set the workspace packages. If any of the package's files belong to the
+	// view, then the package is considered to be a workspace package.
+	for _, uri := range append(m.compiledGoFiles, m.goFiles...) {
+		// If the package's files are in this view, mark it as a workspace package.
+		if s.view.contains(uri) {
+			// A test variant of a package can only be loaded directly by loading
+			// the non-test variant with -test. Track the import path of the non-test variant.
+			if m.forTest != "" {
+				s.workspacePackages[m.id] = m.forTest
+			} else {
+				s.workspacePackages[m.id] = pkgPath
+			}
+			break
+		}
+	}
+	return m, nil
 }
 
 func isTestMain(ctx context.Context, pkg *packages.Package, gocache string) bool {
diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go
new file mode 100644
index 0000000..d730d6e
--- /dev/null
+++ b/internal/lsp/cache/mod.go
@@ -0,0 +1,631 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cache
+
+import (
+	"context"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"regexp"
+	"strconv"
+	"strings"
+
+	"golang.org/x/mod/modfile"
+	"golang.org/x/tools/go/packages"
+	"golang.org/x/tools/internal/gocommand"
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
+	"golang.org/x/tools/internal/lsp/telemetry"
+	"golang.org/x/tools/internal/memoize"
+	"golang.org/x/tools/internal/span"
+	"golang.org/x/tools/internal/telemetry/log"
+	"golang.org/x/tools/internal/telemetry/trace"
+	errors "golang.org/x/xerrors"
+)
+
+const (
+	ModTidyError = "go mod tidy"
+	SyntaxError  = "syntax"
+)
+
+type modKey struct {
+	cfg   string
+	gomod string
+	view  string
+}
+
+type modTidyKey struct {
+	cfg     string
+	gomod   string
+	imports string
+	view    string
+}
+
+type modHandle struct {
+	handle *memoize.Handle
+	file   source.FileHandle
+	cfg    *packages.Config
+}
+
+type modData struct {
+	memoize.NoCopy
+
+	// origfh is the file handle for the original go.mod file.
+	origfh source.FileHandle
+
+	// origParsedFile contains the parsed contents that are used to diff with
+	// the ideal contents.
+	origParsedFile *modfile.File
+
+	// origMapper is the column mapper for the original go.mod file.
+	origMapper *protocol.ColumnMapper
+
+	// idealParsedFile contains the parsed contents for the go.mod file
+	// after it has been "tidied".
+	idealParsedFile *modfile.File
+
+	// unusedDeps is the map containing the dependencies that are left after
+	// removing the ones that are identical in the original and ideal go.mods.
+	unusedDeps map[string]*modfile.Require
+
+	// missingDeps is the map containing the dependencies that are left after
+	// removing the ones that are identical in the original and ideal go.mods.
+	missingDeps map[string]*modfile.Require
+
+	// upgrades is a map of path->version that contains any upgrades for the go.mod.
+	upgrades map[string]string
+
+	// why is a map of path->explanation that contains all the "go mod why" contents
+	// for each require statement.
+	why map[string]string
+
+	// parseErrors are the errors that arise when we diff between a user's go.mod
+	// and the "tidied" go.mod.
+	parseErrors []source.Error
+
+	// err is any error that occurs while we are calculating the parseErrors.
+	err error
+}
+
+func (mh *modHandle) String() string {
+	return mh.File().Identity().URI.Filename()
+}
+
+func (mh *modHandle) File() source.FileHandle {
+	return mh.file
+}
+
+func (mh *modHandle) Parse(ctx context.Context) (*modfile.File, *protocol.ColumnMapper, error) {
+	v := mh.handle.Get(ctx)
+	if v == nil {
+		return nil, nil, errors.Errorf("no parsed file for %s", mh.File().Identity().URI)
+	}
+	data := v.(*modData)
+	return data.origParsedFile, data.origMapper, data.err
+}
+
+func (mh *modHandle) Upgrades(ctx context.Context) (*modfile.File, *protocol.ColumnMapper, map[string]string, error) {
+	v := mh.handle.Get(ctx)
+	if v == nil {
+		return nil, nil, nil, errors.Errorf("no parsed file for %s", mh.File().Identity().URI)
+	}
+	data := v.(*modData)
+	return data.origParsedFile, data.origMapper, data.upgrades, data.err
+}
+
+func (mh *modHandle) Why(ctx context.Context) (*modfile.File, *protocol.ColumnMapper, map[string]string, error) {
+	v := mh.handle.Get(ctx)
+	if v == nil {
+		return nil, nil, nil, errors.Errorf("no parsed file for %s", mh.File().Identity().URI)
+	}
+	data := v.(*modData)
+	return data.origParsedFile, data.origMapper, data.why, data.err
+}
+
+func (s *snapshot) ModHandle(ctx context.Context, fh source.FileHandle) source.ModHandle {
+	uri := fh.Identity().URI
+	if handle := s.getModHandle(uri); handle != nil {
+		return handle
+	}
+
+	realURI, tempURI := s.view.ModFiles()
+	folder := s.View().Folder().Filename()
+	cfg := s.Config(ctx)
+
+	key := modKey{
+		cfg:   hashConfig(cfg),
+		gomod: fh.Identity().String(),
+		view:  folder,
+	}
+	h := s.view.session.cache.store.Bind(key, func(ctx context.Context) interface{} {
+		ctx, done := trace.StartSpan(ctx, "cache.ModHandle", telemetry.File.Of(uri))
+		defer done()
+
+		contents, _, err := fh.Read(ctx)
+		if err != nil {
+			return &modData{
+				err: err,
+			}
+		}
+		parsedFile, err := modfile.Parse(uri.Filename(), contents, nil)
+		if err != nil {
+			return &modData{
+				err: err,
+			}
+		}
+		data := &modData{
+			origfh:         fh,
+			origParsedFile: parsedFile,
+			origMapper: &protocol.ColumnMapper{
+				URI:       uri,
+				Converter: span.NewContentConverter(uri.Filename(), contents),
+				Content:   contents,
+			},
+		}
+		// If the go.mod file is not the view's go.mod file, then we just want to parse.
+		if uri != realURI {
+			return data
+		}
+
+		// If we have a tempModfile, copy the real go.mod file content into the temp go.mod file.
+		if tempURI != "" {
+			if err := ioutil.WriteFile(tempURI.Filename(), contents, os.ModePerm); err != nil {
+				data.err = err
+				return data
+			}
+		}
+		// Only get dependency upgrades if the go.mod file is the same as the view's.
+		if err := dependencyUpgrades(ctx, cfg, folder, data); err != nil {
+			data.err = err
+			return data
+		}
+		// Only run "go mod why" if the go.mod file is the same as the view's.
+		if err := goModWhy(ctx, cfg, folder, data); err != nil {
+			data.err = err
+			return data
+		}
+		return data
+	})
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.modHandles[uri] = &modHandle{
+		handle: h,
+		file:   fh,
+		cfg:    cfg,
+	}
+	return s.modHandles[uri]
+}
+
+func goModWhy(ctx context.Context, cfg *packages.Config, folder string, data *modData) error {
+	if len(data.origParsedFile.Require) == 0 {
+		return nil
+	}
+	// Run "go mod why" on all the dependencies to get information about the usages.
+	inv := gocommand.Invocation{
+		Verb:       "mod",
+		Args:       []string{"why", "-m"},
+		BuildFlags: cfg.BuildFlags,
+		Env:        cfg.Env,
+		WorkingDir: folder,
+	}
+	for _, req := range data.origParsedFile.Require {
+		inv.Args = append(inv.Args, req.Mod.Path)
+	}
+	stdout, err := inv.Run(ctx)
+	if err != nil {
+		return err
+	}
+	whyList := strings.Split(stdout.String(), "\n\n")
+	if len(whyList) <= 1 || len(whyList) > len(data.origParsedFile.Require) {
+		return nil
+	}
+	data.why = make(map[string]string)
+	for i, req := range data.origParsedFile.Require {
+		data.why[req.Mod.Path] = whyList[i]
+	}
+	return nil
+}
+
+func dependencyUpgrades(ctx context.Context, cfg *packages.Config, folder string, data *modData) error {
+	if len(data.origParsedFile.Require) == 0 {
+		return nil
+	}
+	// Run "go list -u -m all" to be able to see which deps can be upgraded.
+	inv := gocommand.Invocation{
+		Verb:       "list",
+		Args:       []string{"-u", "-m", "all"},
+		BuildFlags: cfg.BuildFlags,
+		Env:        cfg.Env,
+		WorkingDir: folder,
+	}
+	stdout, err := inv.Run(ctx)
+	if err != nil {
+		return err
+	}
+	upgradesList := strings.Split(stdout.String(), "\n")
+	if len(upgradesList) <= 1 {
+		return nil
+	}
+	data.upgrades = make(map[string]string)
+	for _, upgrade := range upgradesList[1:] {
+		// Example: "github.com/x/tools v1.1.0 [v1.2.0]"
+		info := strings.Split(upgrade, " ")
+		if len(info) < 3 {
+			continue
+		}
+		dep, version := info[0], info[2]
+		latest := version[1:]                    // remove the "["
+		latest = strings.TrimSuffix(latest, "]") // remove the "]"
+		data.upgrades[dep] = latest
+	}
+	return nil
+}
+
+func (mh *modHandle) Tidy(ctx context.Context) (*modfile.File, *protocol.ColumnMapper, map[string]*modfile.Require, []source.Error, error) {
+	v := mh.handle.Get(ctx)
+	if v == nil {
+		return nil, nil, nil, nil, errors.Errorf("no parsed file for %s", mh.File().Identity().URI)
+	}
+	data := v.(*modData)
+	return data.origParsedFile, data.origMapper, data.missingDeps, data.parseErrors, data.err
+}
+
+func (s *snapshot) ModTidyHandle(ctx context.Context, realfh source.FileHandle) (source.ModTidyHandle, error) {
+	realURI, tempURI := s.view.ModFiles()
+	cfg := s.Config(ctx)
+	options := s.View().Options()
+	folder := s.View().Folder().Filename()
+
+	wsPackages, err := s.WorkspacePackages(ctx)
+	if ctx.Err() != nil {
+		return nil, ctx.Err()
+	}
+	if err != nil {
+		return nil, err
+	}
+	imports, err := hashImports(ctx, wsPackages)
+	if err != nil {
+		return nil, err
+	}
+	key := modTidyKey{
+		view:    folder,
+		imports: imports,
+		gomod:   realfh.Identity().Identifier,
+		cfg:     hashConfig(cfg),
+	}
+	h := s.view.session.cache.store.Bind(key, func(ctx context.Context) interface{} {
+		data := &modData{}
+
+		// Check the case when the tempModfile flag is turned off.
+		if realURI == "" || tempURI == "" {
+			return data
+		}
+
+		ctx, done := trace.StartSpan(ctx, "cache.ModTidyHandle", telemetry.File.Of(realURI))
+		defer done()
+
+		realContents, _, err := realfh.Read(ctx)
+		if err != nil {
+			data.err = err
+			return data
+		}
+		realMapper := &protocol.ColumnMapper{
+			URI:       realURI,
+			Converter: span.NewContentConverter(realURI.Filename(), realContents),
+			Content:   realContents,
+		}
+		origParsedFile, err := modfile.Parse(realURI.Filename(), realContents, nil)
+		if err != nil {
+			if parseErr, err := extractModParseErrors(ctx, realURI, realMapper, err, realContents); err == nil {
+				data.parseErrors = []source.Error{parseErr}
+				return data
+			}
+			data.err = err
+			return data
+		}
+
+		// Copy the real go.mod file content into the temp go.mod file.
+		if err := ioutil.WriteFile(tempURI.Filename(), realContents, os.ModePerm); err != nil {
+			data.err = err
+			return data
+		}
+
+		// We want to run "go mod tidy" to be able to diff between the real and the temp files.
+		inv := gocommand.Invocation{
+			Verb:       "mod",
+			Args:       []string{"tidy"},
+			BuildFlags: cfg.BuildFlags,
+			Env:        cfg.Env,
+			WorkingDir: folder,
+		}
+		if _, err := inv.Run(ctx); err != nil {
+			// Ignore concurrency errors here.
+			if !modConcurrencyError.MatchString(err.Error()) {
+				data.err = err
+				return data
+			}
+		}
+
+		// Go directly to disk to get the temporary mod file, since it is always on disk.
+		tempContents, err := ioutil.ReadFile(tempURI.Filename())
+		if err != nil {
+			data.err = err
+			return data
+		}
+		idealParsedFile, err := modfile.Parse(tempURI.Filename(), tempContents, nil)
+		if err != nil {
+			// We do not need to worry about the temporary file's parse errors since it has been "tidied".
+			data.err = err
+			return data
+		}
+
+		data = &modData{
+			origfh:          realfh,
+			origParsedFile:  origParsedFile,
+			origMapper:      realMapper,
+			idealParsedFile: idealParsedFile,
+			unusedDeps:      make(map[string]*modfile.Require, len(origParsedFile.Require)),
+			missingDeps:     make(map[string]*modfile.Require, len(idealParsedFile.Require)),
+		}
+		// Get the dependencies that are different between the original and ideal mod files.
+		for _, req := range origParsedFile.Require {
+			data.unusedDeps[req.Mod.Path] = req
+		}
+		for _, req := range idealParsedFile.Require {
+			origDep := data.unusedDeps[req.Mod.Path]
+			if origDep != nil && origDep.Indirect == req.Indirect {
+				delete(data.unusedDeps, req.Mod.Path)
+			} else {
+				data.missingDeps[req.Mod.Path] = req
+			}
+		}
+		data.parseErrors, data.err = modRequireErrors(ctx, options, data)
+
+		for _, req := range data.missingDeps {
+			if data.unusedDeps[req.Mod.Path] != nil {
+				delete(data.missingDeps, req.Mod.Path)
+			}
+		}
+		return data
+	})
+	return &modHandle{
+		handle: h,
+		file:   realfh,
+		cfg:    cfg,
+	}, nil
+}
+
+// extractModParseErrors processes the raw errors returned by modfile.Parse,
+// extracting the filenames and line numbers that correspond to the errors.
+func extractModParseErrors(ctx context.Context, uri span.URI, m *protocol.ColumnMapper, parseErr error, content []byte) (source.Error, error) {
+	re := regexp.MustCompile(`.*:([\d]+): (.+)`)
+	matches := re.FindStringSubmatch(strings.TrimSpace(parseErr.Error()))
+	if len(matches) < 3 {
+		log.Error(ctx, "could not parse golang/x/mod error message", parseErr)
+		return source.Error{}, parseErr
+	}
+	line, err := strconv.Atoi(matches[1])
+	if err != nil {
+		return source.Error{}, parseErr
+	}
+	lines := strings.Split(string(content), "\n")
+	if len(lines) <= line {
+		return source.Error{}, errors.Errorf("could not parse goland/x/mod error message, line number out of range")
+	}
+	// The error returned from the modfile package only returns a line number,
+	// so we assume that the diagnostic should be for the entire line.
+	endOfLine := len(lines[line-1])
+	sOffset, err := m.Converter.ToOffset(line, 0)
+	if err != nil {
+		return source.Error{}, err
+	}
+	eOffset, err := m.Converter.ToOffset(line, endOfLine)
+	if err != nil {
+		return source.Error{}, err
+	}
+	spn := span.New(uri, span.NewPoint(line, 0, sOffset), span.NewPoint(line, endOfLine, eOffset))
+	rng, err := m.Range(spn)
+	if err != nil {
+		return source.Error{}, err
+	}
+	return source.Error{
+		Category: SyntaxError,
+		Message:  matches[2],
+		Range:    rng,
+		URI:      uri,
+	}, nil
+}
+
+// modRequireErrors extracts the errors that occur on the require directives.
+// It checks for directness issues and unused dependencies.
+func modRequireErrors(ctx context.Context, options source.Options, data *modData) ([]source.Error, error) {
+	var errors []source.Error
+	for dep, req := range data.unusedDeps {
+		if req.Syntax == nil {
+			continue
+		}
+		// Handle dependencies that are incorrectly labeled indirect and vice versa.
+		if data.missingDeps[dep] != nil && req.Indirect != data.missingDeps[dep].Indirect {
+			directErr, err := modDirectnessErrors(ctx, options, data, req)
+			if err != nil {
+				return nil, err
+			}
+			errors = append(errors, directErr)
+		}
+		// Handle unused dependencies.
+		if data.missingDeps[dep] == nil {
+			rng, err := rangeFromPositions(data.origfh.Identity().URI, data.origMapper, req.Syntax.Start, req.Syntax.End)
+			if err != nil {
+				return nil, err
+			}
+			edits, err := dropDependencyEdits(ctx, options, data, req)
+			if err != nil {
+				return nil, err
+			}
+			errors = append(errors, source.Error{
+				Category: ModTidyError,
+				Message:  fmt.Sprintf("%s is not used in this module.", dep),
+				Range:    rng,
+				URI:      data.origfh.Identity().URI,
+				SuggestedFixes: []source.SuggestedFix{{
+					Title: fmt.Sprintf("Remove dependency: %s", dep),
+					Edits: map[span.URI][]protocol.TextEdit{data.origfh.Identity().URI: edits},
+				}},
+			})
+		}
+	}
+	return errors, nil
+}
+
+// modDirectnessErrors extracts errors when a dependency is labeled indirect when it should be direct and vice versa.
+func modDirectnessErrors(ctx context.Context, options source.Options, data *modData, req *modfile.Require) (source.Error, error) {
+	rng, err := rangeFromPositions(data.origfh.Identity().URI, data.origMapper, req.Syntax.Start, req.Syntax.End)
+	if err != nil {
+		return source.Error{}, err
+	}
+	if req.Indirect {
+		// If the dependency should be direct, just highlight the // indirect.
+		if comments := req.Syntax.Comment(); comments != nil && len(comments.Suffix) > 0 {
+			end := comments.Suffix[0].Start
+			end.LineRune += len(comments.Suffix[0].Token)
+			end.Byte += len([]byte(comments.Suffix[0].Token))
+			rng, err = rangeFromPositions(data.origfh.Identity().URI, data.origMapper, comments.Suffix[0].Start, end)
+			if err != nil {
+				return source.Error{}, err
+			}
+		}
+		edits, err := changeDirectnessEdits(ctx, options, data, req, false)
+		if err != nil {
+			return source.Error{}, err
+		}
+		return source.Error{
+			Category: ModTidyError,
+			Message:  fmt.Sprintf("%s should be a direct dependency.", req.Mod.Path),
+			Range:    rng,
+			URI:      data.origfh.Identity().URI,
+			SuggestedFixes: []source.SuggestedFix{{
+				Title: fmt.Sprintf("Make %s direct", req.Mod.Path),
+				Edits: map[span.URI][]protocol.TextEdit{data.origfh.Identity().URI: edits},
+			}},
+		}, nil
+	}
+	// If the dependency should be indirect, add the // indirect.
+	edits, err := changeDirectnessEdits(ctx, options, data, req, true)
+	if err != nil {
+		return source.Error{}, err
+	}
+	return source.Error{
+		Category: ModTidyError,
+		Message:  fmt.Sprintf("%s should be an indirect dependency.", req.Mod.Path),
+		Range:    rng,
+		URI:      data.origfh.Identity().URI,
+		SuggestedFixes: []source.SuggestedFix{{
+			Title: fmt.Sprintf("Make %s indirect", req.Mod.Path),
+			Edits: map[span.URI][]protocol.TextEdit{data.origfh.Identity().URI: edits},
+		}},
+	}, nil
+}
+
+// dropDependencyEdits gets the edits needed to remove the dependency from the go.mod file.
+// As an example, this function will codify the edits needed to convert the before go.mod file to the after.
+// Before:
+// 	module t
+//
+// 	go 1.11
+//
+// 	require golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee
+// After:
+// 	module t
+//
+// 	go 1.11
+func dropDependencyEdits(ctx context.Context, options source.Options, data *modData, req *modfile.Require) ([]protocol.TextEdit, error) {
+	if err := data.origParsedFile.DropRequire(req.Mod.Path); err != nil {
+		return nil, err
+	}
+	data.origParsedFile.Cleanup()
+	newContents, err := data.origParsedFile.Format()
+	if err != nil {
+		return nil, err
+	}
+	// Reset the *modfile.File back to before we dropped the dependency.
+	data.origParsedFile.AddNewRequire(req.Mod.Path, req.Mod.Version, req.Indirect)
+	// Calculate the edits to be made due to the change.
+	diff := options.ComputeEdits(data.origfh.Identity().URI, string(data.origMapper.Content), string(newContents))
+	edits, err := source.ToProtocolEdits(data.origMapper, diff)
+	if err != nil {
+		return nil, err
+	}
+	return edits, nil
+}
+
+// changeDirectnessEdits gets the edits needed to change an indirect dependency to direct and vice versa.
+// As an example, this function will codify the edits needed to convert the before go.mod file to the after.
+// Before:
+// 	module t
+//
+// 	go 1.11
+//
+// 	require golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee
+// After:
+// 	module t
+//
+// 	go 1.11
+//
+// 	require golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee // indirect
+func changeDirectnessEdits(ctx context.Context, options source.Options, data *modData, req *modfile.Require, indirect bool) ([]protocol.TextEdit, error) {
+	var newReq []*modfile.Require
+	prevIndirect := false
+	// Change the directness in the matching require statement.
+	for _, r := range data.origParsedFile.Require {
+		if req.Mod.Path == r.Mod.Path {
+			prevIndirect = req.Indirect
+			req.Indirect = indirect
+		}
+		newReq = append(newReq, r)
+	}
+	data.origParsedFile.SetRequire(newReq)
+	data.origParsedFile.Cleanup()
+	newContents, err := data.origParsedFile.Format()
+	if err != nil {
+		return nil, err
+	}
+	// Change the dependency back to the way it was before we got the newContents.
+	for _, r := range data.origParsedFile.Require {
+		if req.Mod.Path == r.Mod.Path {
+			req.Indirect = prevIndirect
+		}
+		newReq = append(newReq, r)
+	}
+	data.origParsedFile.SetRequire(newReq)
+	// Calculate the edits to be made due to the change.
+	diff := options.ComputeEdits(data.origfh.Identity().URI, string(data.origMapper.Content), string(newContents))
+	edits, err := source.ToProtocolEdits(data.origMapper, diff)
+	if err != nil {
+		return nil, err
+	}
+	return edits, nil
+}
+
+func rangeFromPositions(uri span.URI, m *protocol.ColumnMapper, s, e modfile.Position) (protocol.Range, error) {
+	line, col, err := m.Converter.ToPosition(s.Byte)
+	if err != nil {
+		return protocol.Range{}, err
+	}
+	start := span.NewPoint(line, col, s.Byte)
+
+	line, col, err = m.Converter.ToPosition(e.Byte)
+	if err != nil {
+		return protocol.Range{}, err
+	}
+	end := span.NewPoint(line, col, e.Byte)
+
+	spn := span.New(uri, start, end)
+	rng, err := m.Range(spn)
+	if err != nil {
+		return protocol.Range{}, err
+	}
+	return rng, nil
+}
diff --git a/internal/lsp/cache/mod_tidy.go b/internal/lsp/cache/mod_tidy.go
deleted file mode 100644
index e301245..0000000
--- a/internal/lsp/cache/mod_tidy.go
+++ /dev/null
@@ -1,428 +0,0 @@
-// Copyright 2019 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package cache
-
-import (
-	"context"
-	"fmt"
-	"io/ioutil"
-	"os"
-	"regexp"
-	"strconv"
-	"strings"
-
-	"golang.org/x/mod/modfile"
-	"golang.org/x/tools/go/packages"
-	"golang.org/x/tools/internal/lsp/protocol"
-	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/lsp/telemetry"
-	"golang.org/x/tools/internal/memoize"
-	"golang.org/x/tools/internal/span"
-	"golang.org/x/tools/internal/telemetry/log"
-	"golang.org/x/tools/internal/telemetry/trace"
-	errors "golang.org/x/xerrors"
-)
-
-const ModTidyError = "go mod tidy"
-const SyntaxError = "syntax"
-
-type parseModKey struct {
-	snapshot source.Snapshot
-	cfg      string
-}
-
-type modTidyHandle struct {
-	handle *memoize.Handle
-	file   source.FileHandle
-	cfg    *packages.Config
-}
-
-type modTidyData struct {
-	memoize.NoCopy
-
-	// origfh is the file handle for the original go.mod file.
-	origfh source.FileHandle
-
-	// origParsedFile contains the parsed contents that are used to diff with
-	// the ideal contents.
-	origParsedFile *modfile.File
-
-	// origMapper is the column mapper for the original go.mod file.
-	origMapper *protocol.ColumnMapper
-
-	// idealParsedFile contains the parsed contents for the go.mod file
-	// after it has been "tidied".
-	idealParsedFile *modfile.File
-
-	// unusedDeps is the map containing the dependencies that are left after
-	// removing the ones that are identical in the original and ideal go.mods.
-	unusedDeps map[string]*modfile.Require
-
-	// missingDeps is the map containing the dependencies that are left after
-	// removing the ones that are identical in the original and ideal go.mods.
-	missingDeps map[string]*modfile.Require
-
-	// parseErrors are the errors that arise when we diff between a user's go.mod
-	// and the "tidied" go.mod.
-	parseErrors []source.Error
-
-	// err is any error that occurs while we are calculating the parseErrors.
-	err error
-}
-
-func (mth *modTidyHandle) String() string {
-	return mth.File().Identity().URI.Filename()
-}
-
-func (mth *modTidyHandle) File() source.FileHandle {
-	return mth.file
-}
-
-func (mth *modTidyHandle) Tidy(ctx context.Context) (*modfile.File, *protocol.ColumnMapper, map[string]*modfile.Require, []source.Error, error) {
-	v := mth.handle.Get(ctx)
-	if v == nil {
-		return nil, nil, nil, nil, errors.Errorf("no parsed file for %s", mth.File().Identity().URI)
-	}
-	data := v.(*modTidyData)
-	return data.origParsedFile, data.origMapper, data.missingDeps, data.parseErrors, data.err
-}
-
-func (s *snapshot) ModTidyHandle(ctx context.Context, realfh source.FileHandle) source.ModTidyHandle {
-	realURI, tempURI := s.view.ModFiles()
-	cfg := s.View().Config(ctx)
-	options := s.View().Options()
-	folder := s.View().Folder().Filename()
-
-	key := parseModKey{
-		snapshot: s,
-		cfg:      hashConfig(cfg),
-	}
-	h := s.view.session.cache.store.Bind(key, func(ctx context.Context) interface{} {
-		data := &modTidyData{}
-
-		// Check the case when the tempModfile flag is turned off.
-		if realURI == "" || tempURI == "" {
-			return data
-		}
-
-		ctx, done := trace.StartSpan(ctx, "cache.ModTidyHandle", telemetry.File.Of(realURI))
-		defer done()
-
-		// Copy the real go.mod file content into the temp go.mod file.
-		realContents, _, err := realfh.Read(ctx)
-		if err != nil {
-			data.err = err
-			return data
-		}
-		if err := ioutil.WriteFile(tempURI.Filename(), realContents, os.ModePerm); err != nil {
-			data.err = err
-			return data
-		}
-
-		// We want to run "go mod tidy" to be able to diff between the real and the temp files.
-		args := append([]string{"mod", "tidy"}, cfg.BuildFlags...)
-		if _, err := source.InvokeGo(ctx, folder, cfg.Env, args...); err != nil {
-			// Ignore parse errors here. They'll be handled below.
-			if !strings.Contains(err.Error(), "errors parsing go.mod") {
-				data.err = err
-				return data
-			}
-		}
-
-		realMapper := &protocol.ColumnMapper{
-			URI:       realURI,
-			Converter: span.NewContentConverter(realURI.Filename(), realContents),
-			Content:   realContents,
-		}
-		origParsedFile, err := modfile.Parse(realURI.Filename(), realContents, nil)
-		if err != nil {
-			if parseErr, err := extractModParseErrors(ctx, realURI, realMapper, err, realContents); err == nil {
-				data.parseErrors = []source.Error{parseErr}
-				return data
-			}
-			data.err = err
-			return data
-		}
-
-		// Go directly to disk to get the temporary mod file, since it is always on disk.
-		tempContents, err := ioutil.ReadFile(tempURI.Filename())
-		if err != nil {
-			data.err = err
-			return data
-		}
-		idealParsedFile, err := modfile.Parse(tempURI.Filename(), tempContents, nil)
-		if err != nil {
-			// We do not need to worry about the temporary file's parse errors since it has been "tidied".
-			data.err = err
-			return data
-		}
-
-		data = &modTidyData{
-			origfh:          realfh,
-			origParsedFile:  origParsedFile,
-			origMapper:      realMapper,
-			idealParsedFile: idealParsedFile,
-			unusedDeps:      make(map[string]*modfile.Require, len(origParsedFile.Require)),
-			missingDeps:     make(map[string]*modfile.Require, len(idealParsedFile.Require)),
-		}
-		// Get the dependencies that are different between the original and ideal mod files.
-		for _, req := range origParsedFile.Require {
-			data.unusedDeps[req.Mod.Path] = req
-		}
-		for _, req := range idealParsedFile.Require {
-			origDep := data.unusedDeps[req.Mod.Path]
-			if origDep != nil && origDep.Indirect == req.Indirect {
-				delete(data.unusedDeps, req.Mod.Path)
-			} else {
-				data.missingDeps[req.Mod.Path] = req
-			}
-		}
-		data.parseErrors, data.err = modRequireErrors(ctx, options, data)
-
-		for _, req := range data.missingDeps {
-			if data.unusedDeps[req.Mod.Path] != nil {
-				delete(data.missingDeps, req.Mod.Path)
-			}
-		}
-		return data
-	})
-	return &modTidyHandle{
-		handle: h,
-		file:   realfh,
-		cfg:    cfg,
-	}
-}
-
-// extractModParseErrors processes the raw errors returned by modfile.Parse,
-// extracting the filenames and line numbers that correspond to the errors.
-func extractModParseErrors(ctx context.Context, uri span.URI, m *protocol.ColumnMapper, parseErr error, content []byte) (source.Error, error) {
-	re := regexp.MustCompile(`.*:([\d]+): (.+)`)
-	matches := re.FindStringSubmatch(strings.TrimSpace(parseErr.Error()))
-	if len(matches) < 3 {
-		log.Error(ctx, "could not parse golang/x/mod error message", parseErr)
-		return source.Error{}, parseErr
-	}
-	line, err := strconv.Atoi(matches[1])
-	if err != nil {
-		return source.Error{}, parseErr
-	}
-	lines := strings.Split(string(content), "\n")
-	if len(lines) <= line {
-		return source.Error{}, errors.Errorf("could not parse goland/x/mod error message, line number out of range")
-	}
-	// The error returned from the modfile package only returns a line number,
-	// so we assume that the diagnostic should be for the entire line.
-	endOfLine := len(lines[line-1])
-	sOffset, err := m.Converter.ToOffset(line, 0)
-	if err != nil {
-		return source.Error{}, err
-	}
-	eOffset, err := m.Converter.ToOffset(line, endOfLine)
-	if err != nil {
-		return source.Error{}, err
-	}
-	spn := span.New(uri, span.NewPoint(line, 0, sOffset), span.NewPoint(line, endOfLine, eOffset))
-	rng, err := m.Range(spn)
-	if err != nil {
-		return source.Error{}, err
-	}
-	return source.Error{
-		Category: SyntaxError,
-		Message:  matches[2],
-		Range:    rng,
-		URI:      uri,
-	}, nil
-}
-
-// modRequireErrors extracts the errors that occur on the require directives.
-// It checks for directness issues and unused dependencies.
-func modRequireErrors(ctx context.Context, options source.Options, modData *modTidyData) ([]source.Error, error) {
-	var errors []source.Error
-	for dep, req := range modData.unusedDeps {
-		if req.Syntax == nil {
-			continue
-		}
-		// Handle dependencies that are incorrectly labeled indirect and vice versa.
-		if modData.missingDeps[dep] != nil && req.Indirect != modData.missingDeps[dep].Indirect {
-			directErr, err := modDirectnessErrors(ctx, options, modData, req)
-			if err != nil {
-				return nil, err
-			}
-			errors = append(errors, directErr)
-		}
-		// Handle unused dependencies.
-		if modData.missingDeps[dep] == nil {
-			rng, err := rangeFromPositions(modData.origfh.Identity().URI, modData.origMapper, req.Syntax.Start, req.Syntax.End)
-			if err != nil {
-				return nil, err
-			}
-			edits, err := dropDependencyEdits(ctx, options, modData, req)
-			if err != nil {
-				return nil, err
-			}
-			errors = append(errors, source.Error{
-				Category: ModTidyError,
-				Message:  fmt.Sprintf("%s is not used in this module.", dep),
-				Range:    rng,
-				URI:      modData.origfh.Identity().URI,
-				SuggestedFixes: []source.SuggestedFix{{
-					Title: fmt.Sprintf("Remove dependency: %s", dep),
-					Edits: map[span.URI][]protocol.TextEdit{modData.origfh.Identity().URI: edits},
-				}},
-			})
-		}
-	}
-	return errors, nil
-}
-
-// modDirectnessErrors extracts errors when a dependency is labeled indirect when it should be direct and vice versa.
-func modDirectnessErrors(ctx context.Context, options source.Options, modData *modTidyData, req *modfile.Require) (source.Error, error) {
-	rng, err := rangeFromPositions(modData.origfh.Identity().URI, modData.origMapper, req.Syntax.Start, req.Syntax.End)
-	if err != nil {
-		return source.Error{}, err
-	}
-	if req.Indirect {
-		// If the dependency should be direct, just highlight the // indirect.
-		if comments := req.Syntax.Comment(); comments != nil && len(comments.Suffix) > 0 {
-			end := comments.Suffix[0].Start
-			end.LineRune += len(comments.Suffix[0].Token)
-			end.Byte += len([]byte(comments.Suffix[0].Token))
-			rng, err = rangeFromPositions(modData.origfh.Identity().URI, modData.origMapper, comments.Suffix[0].Start, end)
-			if err != nil {
-				return source.Error{}, err
-			}
-		}
-		edits, err := changeDirectnessEdits(ctx, options, modData, req, false)
-		if err != nil {
-			return source.Error{}, err
-		}
-		return source.Error{
-			Category: ModTidyError,
-			Message:  fmt.Sprintf("%s should be a direct dependency.", req.Mod.Path),
-			Range:    rng,
-			URI:      modData.origfh.Identity().URI,
-			SuggestedFixes: []source.SuggestedFix{{
-				Title: fmt.Sprintf("Make %s direct", req.Mod.Path),
-				Edits: map[span.URI][]protocol.TextEdit{modData.origfh.Identity().URI: edits},
-			}},
-		}, nil
-	}
-	// If the dependency should be indirect, add the // indirect.
-	edits, err := changeDirectnessEdits(ctx, options, modData, req, true)
-	if err != nil {
-		return source.Error{}, err
-	}
-	return source.Error{
-		Category: ModTidyError,
-		Message:  fmt.Sprintf("%s should be an indirect dependency.", req.Mod.Path),
-		Range:    rng,
-		URI:      modData.origfh.Identity().URI,
-		SuggestedFixes: []source.SuggestedFix{{
-			Title: fmt.Sprintf("Make %s indirect", req.Mod.Path),
-			Edits: map[span.URI][]protocol.TextEdit{modData.origfh.Identity().URI: edits},
-		}},
-	}, nil
-}
-
-// dropDependencyEdits gets the edits needed to remove the dependency from the go.mod file.
-// As an example, this function will codify the edits needed to convert the before go.mod file to the after.
-// Before:
-// 	module t
-//
-// 	go 1.11
-//
-// 	require golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee
-// After:
-// 	module t
-//
-// 	go 1.11
-func dropDependencyEdits(ctx context.Context, options source.Options, modData *modTidyData, req *modfile.Require) ([]protocol.TextEdit, error) {
-	if err := modData.origParsedFile.DropRequire(req.Mod.Path); err != nil {
-		return nil, err
-	}
-	modData.origParsedFile.Cleanup()
-	newContents, err := modData.origParsedFile.Format()
-	if err != nil {
-		return nil, err
-	}
-	// Reset the *modfile.File back to before we dropped the dependency.
-	modData.origParsedFile.AddNewRequire(req.Mod.Path, req.Mod.Version, req.Indirect)
-	// Calculate the edits to be made due to the change.
-	diff := options.ComputeEdits(modData.origfh.Identity().URI, string(modData.origMapper.Content), string(newContents))
-	edits, err := source.ToProtocolEdits(modData.origMapper, diff)
-	if err != nil {
-		return nil, err
-	}
-	return edits, nil
-}
-
-// changeDirectnessEdits gets the edits needed to change an indirect dependency to direct and vice versa.
-// As an example, this function will codify the edits needed to convert the before go.mod file to the after.
-// Before:
-// 	module t
-//
-// 	go 1.11
-//
-// 	require golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee
-// After:
-// 	module t
-//
-// 	go 1.11
-//
-// 	require golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee // indirect
-func changeDirectnessEdits(ctx context.Context, options source.Options, modData *modTidyData, req *modfile.Require, indirect bool) ([]protocol.TextEdit, error) {
-	var newReq []*modfile.Require
-	prevIndirect := false
-	// Change the directness in the matching require statement.
-	for _, r := range modData.origParsedFile.Require {
-		if req.Mod.Path == r.Mod.Path {
-			prevIndirect = req.Indirect
-			req.Indirect = indirect
-		}
-		newReq = append(newReq, r)
-	}
-	modData.origParsedFile.SetRequire(newReq)
-	modData.origParsedFile.Cleanup()
-	newContents, err := modData.origParsedFile.Format()
-	if err != nil {
-		return nil, err
-	}
-	// Change the dependency back to the way it was before we got the newContents.
-	for _, r := range modData.origParsedFile.Require {
-		if req.Mod.Path == r.Mod.Path {
-			req.Indirect = prevIndirect
-		}
-		newReq = append(newReq, r)
-	}
-	modData.origParsedFile.SetRequire(newReq)
-	// Calculate the edits to be made due to the change.
-	diff := options.ComputeEdits(modData.origfh.Identity().URI, string(modData.origMapper.Content), string(newContents))
-	edits, err := source.ToProtocolEdits(modData.origMapper, diff)
-	if err != nil {
-		return nil, err
-	}
-	return edits, nil
-}
-
-func rangeFromPositions(uri span.URI, m *protocol.ColumnMapper, s, e modfile.Position) (protocol.Range, error) {
-	line, col, err := m.Converter.ToPosition(s.Byte)
-	if err != nil {
-		return protocol.Range{}, err
-	}
-	start := span.NewPoint(line, col, s.Byte)
-
-	line, col, err = m.Converter.ToPosition(e.Byte)
-	if err != nil {
-		return protocol.Range{}, err
-	}
-	end := span.NewPoint(line, col, e.Byte)
-
-	spn := span.New(uri, start, end)
-	rng, err := m.Range(spn)
-	if err != nil {
-		return protocol.Range{}, err
-	}
-	return rng, nil
-}
diff --git a/internal/lsp/cache/overlay.go b/internal/lsp/cache/overlay.go
deleted file mode 100644
index dfe8687..0000000
--- a/internal/lsp/cache/overlay.go
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright 2019 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package cache
-
-import (
-	"context"
-
-	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
-	errors "golang.org/x/xerrors"
-)
-
-type overlay struct {
-	session *session
-	uri     span.URI
-	text    []byte
-	hash    string
-	version float64
-	kind    source.FileKind
-
-	// saved is true if a file has been saved on disk,
-	// and therefore does not need to be part of the overlay sent to go/packages.
-	saved bool
-}
-
-func (o *overlay) FileSystem() source.FileSystem {
-	return o.session
-}
-
-func (o *overlay) Identity() source.FileIdentity {
-	return source.FileIdentity{
-		URI:        o.uri,
-		Identifier: o.hash,
-		Version:    o.version,
-		Kind:       o.kind,
-	}
-}
-func (o *overlay) Read(ctx context.Context) ([]byte, string, error) {
-	return o.text, o.hash, nil
-}
-
-func (s *session) updateOverlay(ctx context.Context, c source.FileModification) error {
-	// Make sure that the file was not changed on disk.
-	if c.OnDisk {
-		return errors.Errorf("updateOverlay called for an on-disk change: %s", c.URI)
-	}
-
-	s.overlayMu.Lock()
-	defer s.overlayMu.Unlock()
-
-	o, ok := s.overlays[c.URI]
-
-	// Determine the file kind on open, otherwise, assume it has been cached.
-	var kind source.FileKind
-	switch c.Action {
-	case source.Open:
-		kind = source.DetectLanguage(c.LanguageID, c.URI.Filename())
-	default:
-		if !ok {
-			return errors.Errorf("updateOverlay: modifying unopened overlay %v", c.URI)
-		}
-		kind = o.kind
-	}
-	if kind == source.UnknownKind {
-		return errors.Errorf("updateOverlay: unknown file kind for %s", c.URI)
-	}
-
-	// Closing a file just deletes its overlay.
-	if c.Action == source.Close {
-		delete(s.overlays, c.URI)
-		return nil
-	}
-
-	// If the file is on disk, check if its content is the same as the overlay.
-	text := c.Text
-	if text == nil {
-		text = o.text
-	}
-	hash := hashContents(text)
-	var sameContentOnDisk bool
-	switch c.Action {
-	case source.Open:
-		_, h, err := s.cache.GetFile(c.URI).Read(ctx)
-		sameContentOnDisk = (err == nil && h == hash)
-	case source.Save:
-		// Make sure the version and content (if present) is the same.
-		if o.version != c.Version {
-			return errors.Errorf("updateOverlay: saving %s at version %v, currently at %v", c.URI, c.Version, o.version)
-		}
-		if c.Text != nil && o.hash != hash {
-			return errors.Errorf("updateOverlay: overlay %s changed on save", c.URI)
-		}
-		sameContentOnDisk = true
-	}
-	s.overlays[c.URI] = &overlay{
-		session: s,
-		uri:     c.URI,
-		version: c.Version,
-		text:    text,
-		kind:    kind,
-		hash:    hash,
-		saved:   sameContentOnDisk,
-	}
-	return nil
-}
-
-func (s *session) readOverlay(uri span.URI) *overlay {
-	s.overlayMu.Lock()
-	defer s.overlayMu.Unlock()
-
-	// We might have the content saved in an overlay.
-	if overlay, ok := s.overlays[uri]; ok {
-		return overlay
-	}
-	return nil
-}
-
-func (s *session) buildOverlay() map[string][]byte {
-	s.overlayMu.Lock()
-	defer s.overlayMu.Unlock()
-
-	overlays := make(map[string][]byte)
-	for uri, overlay := range s.overlays {
-		// TODO(rstambler): Make sure not to send overlays outside of the current view.
-		if overlay.saved {
-			continue
-		}
-		overlays[uri.Filename()] = overlay.text
-	}
-	return overlays
-}
diff --git a/internal/lsp/cache/parse.go b/internal/lsp/cache/parse.go
index 9031fc8..8ddd56e 100644
--- a/internal/lsp/cache/parse.go
+++ b/internal/lsp/cache/parse.go
@@ -18,7 +18,6 @@
 	"golang.org/x/tools/internal/lsp/telemetry"
 	"golang.org/x/tools/internal/memoize"
 	"golang.org/x/tools/internal/span"
-	"golang.org/x/tools/internal/telemetry/log"
 	"golang.org/x/tools/internal/telemetry/trace"
 	errors "golang.org/x/xerrors"
 )
@@ -41,22 +40,21 @@
 type parseGoData struct {
 	memoize.NoCopy
 
+	src        []byte
 	ast        *ast.File
 	parseError error // errors associated with parsing the file
 	mapper     *protocol.ColumnMapper
 	err        error
 }
 
-func (c *cache) ParseGoHandle(fh source.FileHandle, mode source.ParseMode) source.ParseGoHandle {
+func (c *Cache) ParseGoHandle(fh source.FileHandle, mode source.ParseMode) source.ParseGoHandle {
 	key := parseKey{
 		file: fh.Identity(),
 		mode: mode,
 	}
 	fset := c.fset
 	h := c.store.Bind(key, func(ctx context.Context) interface{} {
-		data := &parseGoData{}
-		data.ast, data.mapper, data.parseError, data.err = parseGo(ctx, fset, fh, mode)
-		return data
+		return parseGo(ctx, fset, fh, mode)
 	})
 	return &parseGoHandle{
 		handle: h,
@@ -77,22 +75,22 @@
 	return pgh.mode
 }
 
-func (pgh *parseGoHandle) Parse(ctx context.Context) (*ast.File, *protocol.ColumnMapper, error, error) {
+func (pgh *parseGoHandle) Parse(ctx context.Context) (*ast.File, []byte, *protocol.ColumnMapper, error, error) {
 	v := pgh.handle.Get(ctx)
 	if v == nil {
-		return nil, nil, nil, errors.Errorf("no parsed file for %s", pgh.File().Identity().URI)
+		return nil, nil, nil, nil, errors.Errorf("no parsed file for %s", pgh.File().Identity().URI)
 	}
 	data := v.(*parseGoData)
-	return data.ast, data.mapper, data.parseError, data.err
+	return data.ast, data.src, data.mapper, data.parseError, data.err
 }
 
-func (pgh *parseGoHandle) Cached() (*ast.File, *protocol.ColumnMapper, error, error) {
+func (pgh *parseGoHandle) Cached() (*ast.File, []byte, *protocol.ColumnMapper, error, error) {
 	v := pgh.handle.Cached()
 	if v == nil {
-		return nil, nil, nil, errors.Errorf("no cached AST for %s", pgh.file.Identity().URI)
+		return nil, nil, nil, nil, errors.Errorf("no cached AST for %s", pgh.file.Identity().URI)
 	}
 	data := v.(*parseGoData)
-	return data.ast, data.mapper, data.parseError, data.err
+	return data.ast, data.src, data.mapper, data.parseError, data.err
 }
 
 func hashParseKey(ph source.ParseGoHandle) string {
@@ -110,16 +108,16 @@
 	return hashContents(b.Bytes())
 }
 
-func parseGo(ctx context.Context, fset *token.FileSet, fh source.FileHandle, mode source.ParseMode) (file *ast.File, mapper *protocol.ColumnMapper, parseError error, err error) {
+func parseGo(ctx context.Context, fset *token.FileSet, fh source.FileHandle, mode source.ParseMode) *parseGoData {
 	ctx, done := trace.StartSpan(ctx, "cache.parseGo", telemetry.File.Of(fh.Identity().URI.Filename()))
 	defer done()
 
 	if fh.Identity().Kind != source.Go {
-		return nil, nil, nil, errors.Errorf("cannot parse non-Go file %s", fh.Identity().URI)
+		return &parseGoData{err: errors.Errorf("cannot parse non-Go file %s", fh.Identity().URI)}
 	}
 	buf, _, err := fh.Read(ctx)
 	if err != nil {
-		return nil, nil, nil, err
+		return &parseGoData{err: err}
 	}
 	parseLimit <- struct{}{}
 	defer func() { <-parseLimit }()
@@ -127,21 +125,36 @@
 	if mode == source.ParseHeader {
 		parserMode = parser.ImportsOnly | parser.ParseComments
 	}
-	file, parseError = parser.ParseFile(fset, fh.Identity().URI.Filename(), buf, parserMode)
+	file, parseError := parser.ParseFile(fset, fh.Identity().URI.Filename(), buf, parserMode)
 	var tok *token.File
 	if file != nil {
-		// Fix any badly parsed parts of the AST.
 		tok = fset.File(file.Pos())
 		if tok == nil {
-			return nil, nil, nil, errors.Errorf("successfully parsed but no token.File for %s (%v)", fh.Identity().URI, parseError)
+			return &parseGoData{err: errors.Errorf("successfully parsed but no token.File for %s (%v)", fh.Identity().URI, parseError)}
 		}
+
+		// Fix any badly parsed parts of the AST.
+		_ = fixAST(ctx, file, tok, buf)
+
+		// Fix certain syntax errors that render the file unparseable.
+		newSrc := fixSrc(file, tok, buf)
+		if newSrc != nil {
+			newFile, _ := parser.ParseFile(fset, fh.Identity().URI.Filename(), newSrc, parserMode)
+			if newFile != nil {
+				// Maintain the original parseError so we don't try formatting the doctored file.
+				file = newFile
+				buf = newSrc
+				tok = fset.File(file.Pos())
+
+				_ = fixAST(ctx, file, tok, buf)
+			}
+		}
+
 		if mode == source.ParseExported {
 			trimAST(file)
 		}
-		if err := fix(ctx, file, tok, buf); err != nil {
-			log.Error(ctx, "failed to fix AST", err)
-		}
 	}
+
 	if file == nil {
 		// If the file is nil only due to parse errors,
 		// the parse errors are the actual errors.
@@ -149,19 +162,20 @@
 		if err == nil {
 			err = errors.Errorf("no AST for %s", fh.Identity().URI)
 		}
-		return nil, nil, parseError, err
-	}
-	uri := fh.Identity().URI
-	content, _, err := fh.Read(ctx)
-	if err != nil {
-		return nil, nil, parseError, err
+		return &parseGoData{parseError: parseError, err: err}
 	}
 	m := &protocol.ColumnMapper{
-		URI:       uri,
+		URI:       fh.Identity().URI,
 		Converter: span.NewTokenConverter(fset, tok),
-		Content:   content,
+		Content:   buf,
 	}
-	return file, m, parseError, nil
+
+	return &parseGoData{
+		src:        buf,
+		ast:        file,
+		mapper:     m,
+		parseError: parseError,
+	}
 }
 
 // trimAST clears any part of the AST not relevant to type checking
@@ -201,13 +215,59 @@
 	return ok
 }
 
-// fix inspects the AST and potentially modifies any *ast.BadStmts so that it can be
+// fixAST inspects the AST and potentially modifies any *ast.BadStmts so that it can be
 // type-checked more effectively.
-func fix(ctx context.Context, n ast.Node, tok *token.File, src []byte) error {
-	var (
-		ancestors []ast.Node
-		err       error
-	)
+func fixAST(ctx context.Context, n ast.Node, tok *token.File, src []byte) error {
+	var err error
+	walkASTWithParent(n, func(n, parent ast.Node) bool {
+		switch n := n.(type) {
+		case *ast.BadStmt:
+			err = fixDeferOrGoStmt(n, parent, tok, src) // don't shadow err
+			if err == nil {
+				// Recursively fix in our fixed node.
+				err = fixAST(ctx, parent, tok, src)
+			} else {
+				err = errors.Errorf("unable to parse defer or go from *ast.BadStmt: %v", err)
+			}
+			return false
+		case *ast.BadExpr:
+			// Don't propagate this error since *ast.BadExpr is very common
+			// and it is only sometimes due to array types. Errors from here
+			// are expected and not actionable in general.
+			if fixArrayType(n, parent, tok, src) == nil {
+				// Recursively fix in our fixed node.
+				err = fixAST(ctx, parent, tok, src)
+				return false
+			}
+
+			// Fix cases where parser interprets if/for/switch "init"
+			// statement as "cond" expression, e.g.:
+			//
+			//   // "i := foo" is init statement, not condition.
+			//   for i := foo
+			//
+			fixInitStmt(n, parent, tok, src)
+
+			return false
+		case *ast.SelectorExpr:
+			// Fix cases where a keyword prefix results in a phantom "_" selector, e.g.:
+			//
+			//   foo.var<> // want to complete to "foo.variance"
+			//
+			fixPhantomSelector(n, tok, src)
+			return true
+		default:
+			return true
+		}
+	})
+
+	return err
+}
+
+// walkASTWithParent walks the AST rooted at n. The semantics are
+// similar to ast.Inspect except it does not call f(nil).
+func walkASTWithParent(n ast.Node, f func(n ast.Node, parent ast.Node) bool) {
+	var ancestors []ast.Node
 	ast.Inspect(n, func(n ast.Node) (recurse bool) {
 		defer func() {
 			if recurse {
@@ -225,90 +285,151 @@
 			parent = ancestors[len(ancestors)-1]
 		}
 
-		switch n := n.(type) {
-		case *ast.BadStmt:
-			err = fixDeferOrGoStmt(n, parent, tok, src) // don't shadow err
-			if err == nil {
-				// Recursively fix in our fixed node.
-				err = fix(ctx, parent, tok, src)
-			} else {
-				err = errors.Errorf("unable to parse defer or go from *ast.BadStmt: %v", err)
-			}
-			return false
-		case *ast.BadExpr:
-			// Don't propagate this error since *ast.BadExpr is very common
-			// and it is only sometimes due to array types. Errors from here
-			// are expected and not actionable in general.
-			if fixArrayType(n, parent, tok, src) == nil {
-				// Recursively fix in our fixed node.
-				err = fix(ctx, parent, tok, src)
-				return false
-			}
-
-			// Fix cases where the parser expects an expression but finds a keyword, e.g.:
-			//
-			//   someFunc(var<>) // want to complete to "variance"
-			//
-			fixAccidentalKeyword(n, parent, tok, src)
-
-			return false
-		case *ast.DeclStmt:
-			// Fix cases where the completion prefix looks like a decl, e.g.:
-			//
-			//   func typeName(obj interface{}) string {}
-			//   type<> // want to call "typeName()" but looks like a "type" decl
-			//
-			fixAccidentalDecl(n, parent, tok, src)
-			return false
-		case *ast.SelectorExpr:
-			// Fix cases where a keyword prefix results in a phantom "_" selector, e.g.:
-			//
-			//   foo.var<> // want to complete to "foo.variance"
-			//
-			fixPhantomSelector(n, tok, src)
-			return true
-		default:
-			return true
-		}
+		return f(n, parent)
 	})
-
-	return err
 }
 
-// fixAccidentalDecl tries to fix "accidental" declarations. For example:
-//
-// func typeOf() {}
-// type<> // want to call typeOf(), not declare a type
-//
-// If we find an *ast.DeclStmt with only a single phantom "_" spec, we
-// replace the decl statement with an expression statement containing
-// only the keyword. This allows completion to work to some degree.
-func fixAccidentalDecl(decl *ast.DeclStmt, parent ast.Node, tok *token.File, src []byte) {
-	genDecl, _ := decl.Decl.(*ast.GenDecl)
-	if genDecl == nil || len(genDecl.Specs) != 1 {
-		return
-	}
-
-	switch spec := genDecl.Specs[0].(type) {
-	case *ast.TypeSpec:
-		// If the name isn't a phantom "_" identifier inserted by the
-		// parser then the decl is likely legitimate and we shouldn't mess
-		// with it.
-		if !isPhantomUnderscore(spec.Name, tok, src) {
-			return
+// fixSrc attempts to modify the file's source code to fix certain
+// syntax errors that leave the rest of the file unparsed.
+func fixSrc(f *ast.File, tok *token.File, src []byte) (newSrc []byte) {
+	walkASTWithParent(f, func(n, parent ast.Node) bool {
+		if newSrc != nil {
+			return false
 		}
-	case *ast.ValueSpec:
-		if len(spec.Names) != 1 || !isPhantomUnderscore(spec.Names[0], tok, src) {
-			return
-		}
-	}
 
-	replaceNode(parent, decl, &ast.ExprStmt{
-		X: &ast.Ident{
-			Name:    genDecl.Tok.String(),
-			NamePos: decl.Pos(),
-		},
+		switch n := n.(type) {
+		case *ast.BlockStmt:
+			newSrc = fixMissingCurlies(f, n, parent, tok, src)
+		case *ast.SelectorExpr:
+			newSrc = fixDanglingSelector(f, n, parent, tok, src)
+		}
+
+		return newSrc == nil
 	})
+
+	return newSrc
+}
+
+// fixMissingCurlies adds in curly braces for block statements that
+// are missing curly braces. For example:
+//
+//   if foo
+//
+// becomes
+//
+//   if foo {}
+func fixMissingCurlies(f *ast.File, b *ast.BlockStmt, parent ast.Node, tok *token.File, src []byte) []byte {
+	// If the "{" is already in the source code, there isn't anything to
+	// fix since we aren't mising curlies.
+	if b.Lbrace.IsValid() {
+		braceOffset := tok.Offset(b.Lbrace)
+		if braceOffset < len(src) && src[braceOffset] == '{' {
+			return nil
+		}
+	}
+
+	parentLine := tok.Line(parent.Pos())
+
+	if parentLine >= tok.LineCount() {
+		// If we are the last line in the file, no need to fix anything.
+		return nil
+	}
+
+	// Insert curlies at the end of parent's starting line. The parent
+	// is the statement that contains the block, e.g. *ast.IfStmt. The
+	// block's Pos()/End() can't be relied upon because they are based
+	// on the (missing) curly braces. We assume the statement is a
+	// single line for now and try sticking the curly braces at the end.
+	insertPos := tok.LineStart(parentLine+1) - 1
+
+	// Scootch position backwards until it's not in a comment. For example:
+	//
+	// if foo<> // some amazing comment |
+	// someOtherCode()
+	//
+	// insertPos will be located at "|", so we back it out of the comment.
+	didSomething := true
+	for didSomething {
+		didSomething = false
+		for _, c := range f.Comments {
+			if c.Pos() < insertPos && insertPos <= c.End() {
+				insertPos = c.Pos()
+				didSomething = true
+			}
+		}
+	}
+
+	// Bail out if line doesn't end in an ident or ".". This is to avoid
+	// cases like below where we end up making things worse by adding
+	// curlies:
+	//
+	//   if foo &&
+	//     bar<>
+	switch precedingToken(insertPos, tok, src) {
+	case token.IDENT, token.PERIOD:
+		// ok
+	default:
+		return nil
+	}
+
+	var buf bytes.Buffer
+	buf.Grow(len(src) + 3)
+	buf.Write(src[:tok.Offset(insertPos)])
+
+	// Detect if we need to insert a semicolon to fix "for" loop situations like:
+	//
+	//   for i := foo(); foo<>
+	//
+	// Just adding curlies is not sufficient to make things parse well.
+	if fs, ok := parent.(*ast.ForStmt); ok {
+		if _, ok := fs.Cond.(*ast.BadExpr); !ok {
+			if xs, ok := fs.Post.(*ast.ExprStmt); ok {
+				if _, ok := xs.X.(*ast.BadExpr); ok {
+					buf.WriteByte(';')
+				}
+			}
+		}
+	}
+
+	// Insert "{}" at insertPos.
+	buf.WriteByte('{')
+	buf.WriteByte('}')
+	buf.Write(src[tok.Offset(insertPos):])
+	return buf.Bytes()
+}
+
+// fixDanglingSelector inserts real "_" selector expressions in place
+// of phantom "_" selectors. For example:
+//
+// func _() {
+//   x.<>
+// }
+// var x struct { i int }
+//
+// To fix completion at "<>", we insert a real "_" after the "." so the
+// following declaration of "x" can be parsed and type checked
+// normally.
+func fixDanglingSelector(f *ast.File, s *ast.SelectorExpr, parent ast.Node, tok *token.File, src []byte) []byte {
+	if !isPhantomUnderscore(s.Sel, tok, src) {
+		return nil
+	}
+
+	if !s.X.End().IsValid() {
+		return nil
+	}
+
+	// Insert directly after the selector's ".".
+	insertOffset := tok.Offset(s.X.End()) + 1
+	if src[insertOffset-1] != '.' {
+		return nil
+	}
+
+	var buf bytes.Buffer
+	buf.Grow(len(src) + 1)
+	buf.Write(src[:insertOffset])
+	buf.WriteByte('_')
+	buf.Write(src[insertOffset:])
+	return buf.Bytes()
 }
 
 // fixPhantomSelector tries to fix selector expressions with phantom
@@ -323,6 +444,16 @@
 		return
 	}
 
+	// Only consider selectors directly abutting the selector ".". This
+	// avoids false positives in cases like:
+	//
+	//   foo. // don't think "var" is our selector
+	//   var bar = 123
+	//
+	if sel.Sel.Pos() != sel.X.End()+1 {
+		return
+	}
+
 	maybeKeyword := readKeyword(sel.Sel.Pos(), tok, src)
 	if maybeKeyword == "" {
 		return
@@ -348,25 +479,46 @@
 	return len(src) <= offset || src[offset] != '_'
 }
 
-// fixAccidentalKeyword tries to fix "accidental" keyword expressions. For example:
-//
-// variance := 123
-// doMath(var<>)
-//
-// If we find an *ast.BadExpr that begins with a keyword, we replace
-// the BadExpr with an *ast.Ident containing the text of the keyword.
-// This allows completion to work to some degree.
-func fixAccidentalKeyword(bad *ast.BadExpr, parent ast.Node, tok *token.File, src []byte) {
-	if !bad.Pos().IsValid() {
+// fixInitStmt fixes cases where the parser misinterprets an
+// if/for/switch "init" statement as the "cond" conditional. In cases
+// like "if i := 0" the user hasn't typed the semicolon yet so the
+// parser is looking for the conditional expression. However, "i := 0"
+// are not valid expressions, so we get a BadExpr.
+func fixInitStmt(bad *ast.BadExpr, parent ast.Node, tok *token.File, src []byte) {
+	if !bad.Pos().IsValid() || !bad.End().IsValid() {
 		return
 	}
 
-	maybeKeyword := readKeyword(bad.Pos(), tok, src)
-	if maybeKeyword == "" {
+	// Try to extract a statement from the BadExpr.
+	stmtBytes := src[tok.Offset(bad.Pos()) : tok.Offset(bad.End()-1)+1]
+	stmt, err := parseStmt(bad.Pos(), stmtBytes)
+	if err != nil {
 		return
 	}
 
-	replaceNode(parent, bad, &ast.Ident{Name: maybeKeyword, NamePos: bad.Pos()})
+	// If the parent statement doesn't already have an "init" statement,
+	// move the extracted statement into the "init" field and insert a
+	// dummy expression into the required "cond" field.
+	switch p := parent.(type) {
+	case *ast.IfStmt:
+		if p.Init != nil {
+			return
+		}
+		p.Init = stmt
+		p.Cond = &ast.Ident{Name: "_"}
+	case *ast.ForStmt:
+		if p.Init != nil {
+			return
+		}
+		p.Init = stmt
+		p.Cond = &ast.Ident{Name: "_"}
+	case *ast.SwitchStmt:
+		if p.Init != nil {
+			return
+		}
+		p.Init = stmt
+		p.Tag = nil
+	}
 }
 
 // readKeyword reads the keyword starting at pos, if any.
@@ -443,6 +595,23 @@
 	return nil
 }
 
+// precedingToken scans src to find the token preceding pos.
+func precedingToken(pos token.Pos, tok *token.File, src []byte) token.Token {
+	s := &scanner.Scanner{}
+	s.Init(tok, src, nil, 0)
+
+	var lastTok token.Token
+	for {
+		p, t, _ := s.Scan()
+		if t == token.EOF || p >= pos {
+			break
+		}
+
+		lastTok = t
+	}
+	return lastTok
+}
+
 // fixDeferOrGoStmt tries to parse an *ast.BadStmt into a defer or a go statement.
 //
 // go/parser packages a statement of the form "defer x." as an *ast.BadStmt because
@@ -597,9 +766,9 @@
 	return nil
 }
 
-// parseExpr parses the expression in src and updates its position to
+// parseStmt parses the statement in src and updates its position to
 // start at pos.
-func parseExpr(pos token.Pos, src []byte) (ast.Expr, error) {
+func parseStmt(pos token.Pos, src []byte) (ast.Stmt, error) {
 	// Wrap our expression to make it a valid Go file we can pass to ParseFile.
 	fileSrc := bytes.Join([][]byte{
 		[]byte("package fake;func _(){"),
@@ -624,25 +793,36 @@
 		return nil, errors.Errorf("no statement in %s: %v", src, err)
 	}
 
-	exprStmt, ok := fakeDecl.Body.List[0].(*ast.ExprStmt)
+	stmt := fakeDecl.Body.List[0]
+
+	// parser.ParseFile returns undefined positions.
+	// Adjust them for the current file.
+	offsetPositions(stmt, pos-1-(stmt.Pos()-1))
+
+	return stmt, nil
+}
+
+// parseExpr parses the expression in src and updates its position to
+// start at pos.
+func parseExpr(pos token.Pos, src []byte) (ast.Expr, error) {
+	stmt, err := parseStmt(pos, src)
+	if err != nil {
+		return nil, err
+	}
+
+	exprStmt, ok := stmt.(*ast.ExprStmt)
 	if !ok {
 		return nil, errors.Errorf("no expr in %s: %v", src, err)
 	}
 
-	expr := exprStmt.X
-
-	// parser.ParseExpr returns undefined positions.
-	// Adjust them for the current file.
-	offsetPositions(expr, pos-1-(expr.Pos()-1))
-
-	return expr, nil
+	return exprStmt.X, nil
 }
 
 var tokenPosType = reflect.TypeOf(token.NoPos)
 
 // offsetPositions applies an offset to the positions in an ast.Node.
-func offsetPositions(expr ast.Expr, offset token.Pos) {
-	ast.Inspect(expr, func(n ast.Node) bool {
+func offsetPositions(n ast.Node, offset token.Pos) {
+	ast.Inspect(n, func(n ast.Node) bool {
 		if n == nil {
 			return false
 		}
@@ -670,7 +850,7 @@
 }
 
 // replaceNode updates parent's child oldChild to be newChild. It
-// retuns whether it replaced successfully.
+// returns whether it replaced successfully.
 func replaceNode(parent, oldChild, newChild ast.Node) bool {
 	if parent == nil || oldChild == nil || newChild == nil {
 		return false
diff --git a/internal/lsp/cache/pkg.go b/internal/lsp/cache/pkg.go
index f7ae413..db32834 100644
--- a/internal/lsp/cache/pkg.go
+++ b/internal/lsp/cache/pkg.go
@@ -11,6 +11,7 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
+	"golang.org/x/tools/internal/packagesinternal"
 	"golang.org/x/tools/internal/span"
 	errors "golang.org/x/xerrors"
 )
@@ -18,14 +19,15 @@
 // pkg contains the type information needed by the source package.
 type pkg struct {
 	// ID and package path have their own types to avoid being used interchangeably.
-	id      packageID
-	pkgPath packagePath
-	mode    source.ParseMode
-
+	id              packageID
+	pkgPath         packagePath
+	mode            source.ParseMode
+	forTest         packagePath
 	goFiles         []source.ParseGoHandle
 	compiledGoFiles []source.ParseGoHandle
 	errors          []*source.Error
 	imports         map[packagePath]*pkg
+	module          *packagesinternal.Module
 	types           *types.Package
 	typesInfo       *types.Info
 	typesSizes      types.Sizes
@@ -71,7 +73,7 @@
 func (p *pkg) GetSyntax() []*ast.File {
 	var syntax []*ast.File
 	for _, ph := range p.compiledGoFiles {
-		file, _, _, err := ph.Cached()
+		file, _, _, _, err := ph.Cached()
 		if err == nil {
 			syntax = append(syntax, file)
 		}
@@ -99,6 +101,10 @@
 	return p.types == nil || p.typesInfo == nil || p.typesSizes == nil
 }
 
+func (p *pkg) ForTest() string {
+	return string(p.forTest)
+}
+
 func (p *pkg) GetImport(pkgPath string) (source.Package, error) {
 	if imp := p.imports[packagePath(pkgPath)]; imp != nil {
 		return imp, nil
@@ -115,12 +121,16 @@
 	return result
 }
 
+func (p *pkg) Module() *packagesinternal.Module {
+	return p.module
+}
+
 func (s *snapshot) FindAnalysisError(ctx context.Context, pkgID, analyzerName, msg string, rng protocol.Range) (*source.Error, error) {
 	analyzer, ok := s.View().Options().Analyzers[analyzerName]
 	if !ok {
 		return nil, errors.Errorf("unexpected analyzer: %s", analyzerName)
 	}
-	act, err := s.actionHandle(ctx, packageID(pkgID), source.ParseFull, analyzer)
+	act, err := s.actionHandle(ctx, packageID(pkgID), analyzer)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index d8de7d2..eceb84d 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -11,7 +11,6 @@
 	"sync"
 	"sync/atomic"
 
-	"golang.org/x/tools/internal/lsp/debug"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/trace"
@@ -19,8 +18,8 @@
 	errors "golang.org/x/xerrors"
 )
 
-type session struct {
-	cache *cache
+type Session struct {
+	cache *Cache
 	id    string
 
 	options source.Options
@@ -33,15 +32,44 @@
 	overlays  map[span.URI]*overlay
 }
 
-func (s *session) Options() source.Options {
+type overlay struct {
+	session *Session
+	uri     span.URI
+	text    []byte
+	hash    string
+	version float64
+	kind    source.FileKind
+
+	// saved is true if a file has been saved on disk,
+	// and therefore does not need to be part of the overlay sent to go/packages.
+	saved bool
+}
+
+func (o *overlay) FileSystem() source.FileSystem {
+	return o.session
+}
+
+func (o *overlay) Identity() source.FileIdentity {
+	return source.FileIdentity{
+		URI:        o.uri,
+		Identifier: o.hash,
+		Version:    o.version,
+		Kind:       o.kind,
+	}
+}
+func (o *overlay) Read(ctx context.Context) ([]byte, string, error) {
+	return o.text, o.hash, nil
+}
+
+func (s *Session) Options() source.Options {
 	return s.options
 }
 
-func (s *session) SetOptions(options source.Options) {
+func (s *Session) SetOptions(options source.Options) {
 	s.options = options
 }
 
-func (s *session) Shutdown(ctx context.Context) {
+func (s *Session) Shutdown(ctx context.Context) {
 	s.viewMu.Lock()
 	defer s.viewMu.Unlock()
 	for _, view := range s.views {
@@ -49,14 +77,14 @@
 	}
 	s.views = nil
 	s.viewMap = nil
-	debug.DropSession(debugSession{s})
+	s.cache.debug.DropSession(DebugSession{s})
 }
 
-func (s *session) Cache() source.Cache {
+func (s *Session) Cache() source.Cache {
 	return s.cache
 }
 
-func (s *session) NewView(ctx context.Context, name string, folder span.URI, options source.Options) (source.View, source.Snapshot, error) {
+func (s *Session) NewView(ctx context.Context, name string, folder span.URI, options source.Options) (source.View, source.Snapshot, error) {
 	s.viewMu.Lock()
 	defer s.viewMu.Unlock()
 	v, snapshot, err := s.createView(ctx, name, folder, options)
@@ -69,7 +97,7 @@
 	return v, snapshot, nil
 }
 
-func (s *session) createView(ctx context.Context, name string, folder span.URI, options source.Options) (*view, *snapshot, error) {
+func (s *Session) createView(ctx context.Context, name string, folder span.URI, options source.Options) (*view, *snapshot, error) {
 	index := atomic.AddInt64(&viewIndex, 1)
 	// We want a true background context and not a detached context here
 	// the spans need to be unrelated and no tag values should pollute it.
@@ -97,6 +125,7 @@
 			actions:           make(map[actionKey]*actionHandle),
 			workspacePackages: make(map[packageID]packagePath),
 			unloadableFiles:   make(map[span.URI]struct{}),
+			modHandles:        make(map[span.URI]*modHandle),
 		},
 		ignoredURIs: make(map[span.URI]struct{}),
 	}
@@ -113,12 +142,12 @@
 	// Initialize the view without blocking.
 	go v.initialize(xcontext.Detach(ctx), v.snapshot)
 
-	debug.AddView(debugView{v})
+	v.session.cache.debug.AddView(debugView{v})
 	return v, v.snapshot, nil
 }
 
 // View returns the view by name.
-func (s *session) View(name string) source.View {
+func (s *Session) View(name string) source.View {
 	s.viewMu.Lock()
 	defer s.viewMu.Unlock()
 	for _, view := range s.views {
@@ -131,11 +160,11 @@
 
 // ViewOf returns a view corresponding to the given URI.
 // If the file is not already associated with a view, pick one using some heuristics.
-func (s *session) ViewOf(uri span.URI) (source.View, error) {
+func (s *Session) ViewOf(uri span.URI) (source.View, error) {
 	return s.viewOf(uri)
 }
 
-func (s *session) viewOf(uri span.URI) (*view, error) {
+func (s *Session) viewOf(uri span.URI) (*view, error) {
 	s.viewMu.Lock()
 	defer s.viewMu.Unlock()
 
@@ -152,7 +181,7 @@
 	return v, nil
 }
 
-func (s *session) viewsOf(uri span.URI) []*view {
+func (s *Session) viewsOf(uri span.URI) []*view {
 	s.viewMu.Lock()
 	defer s.viewMu.Unlock()
 
@@ -165,7 +194,7 @@
 	return views
 }
 
-func (s *session) Views() []source.View {
+func (s *Session) Views() []source.View {
 	s.viewMu.Lock()
 	defer s.viewMu.Unlock()
 	result := make([]source.View, len(s.views))
@@ -177,7 +206,7 @@
 
 // bestView finds the best view to associate a given URI with.
 // viewMu must be held when calling this method.
-func (s *session) bestView(uri span.URI) (*view, error) {
+func (s *Session) bestView(uri span.URI) (*view, error) {
 	if len(s.views) == 0 {
 		return nil, errors.Errorf("no views in the session")
 	}
@@ -187,7 +216,7 @@
 		if longest != nil && len(longest.Folder()) > len(view.Folder()) {
 			continue
 		}
-		if strings.HasPrefix(string(uri), string(view.Folder())) {
+		if view.contains(uri) {
 			longest = view
 		}
 	}
@@ -198,7 +227,7 @@
 	return s.views[0], nil
 }
 
-func (s *session) removeView(ctx context.Context, view *view) error {
+func (s *Session) removeView(ctx context.Context, view *view) error {
 	s.viewMu.Lock()
 	defer s.viewMu.Unlock()
 	i, err := s.dropView(ctx, view)
@@ -213,7 +242,7 @@
 	return nil
 }
 
-func (s *session) updateView(ctx context.Context, view *view, options source.Options) (*view, *snapshot, error) {
+func (s *Session) updateView(ctx context.Context, view *view, options source.Options) (*view, *snapshot, error) {
 	s.viewMu.Lock()
 	defer s.viewMu.Unlock()
 	i, err := s.dropView(ctx, view)
@@ -234,7 +263,7 @@
 	return v, snapshot, nil
 }
 
-func (s *session) dropView(ctx context.Context, v *view) (int, error) {
+func (s *Session) dropView(ctx context.Context, v *view) (int, error) {
 	// we always need to drop the view map
 	s.viewMap = make(map[span.URI]*view)
 	for i := range s.views {
@@ -248,15 +277,18 @@
 	return -1, errors.Errorf("view %s for %v not found", v.Name(), v.Folder())
 }
 
-func (s *session) DidModifyFiles(ctx context.Context, changes []source.FileModification) ([]source.Snapshot, error) {
-	views := make(map[*view][]span.URI)
+func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModification) ([]source.Snapshot, error) {
+	views := make(map[*view]map[span.URI]source.FileHandle)
 
+	overlays, err := s.updateOverlays(ctx, changes)
+	if err != nil {
+		return nil, err
+	}
 	for _, c := range changes {
-		// Only update overlays for in-editor changes.
-		if !c.OnDisk {
-			if err := s.updateOverlay(ctx, c); err != nil {
-				return nil, err
-			}
+		// Do nothing if the file is open in the editor and we receive
+		// an on-disk action. The editor is the source of truth.
+		if s.isOpen(c.URI) && c.OnDisk {
+			continue
 		}
 		// Look through all of the session's views, invalidating the file for
 		// all of the views to which it is known.
@@ -273,7 +305,14 @@
 			if _, err := view.getFile(c.URI); err != nil {
 				return nil, err
 			}
-			views[view] = append(views[view], c.URI)
+			if _, ok := views[view]; !ok {
+				views[view] = make(map[span.URI]source.FileHandle)
+			}
+			if o, ok := overlays[c.URI]; ok {
+				views[view][c.URI] = o
+			} else {
+				views[view][c.URI] = s.cache.GetFile(c.URI)
+			}
 		}
 	}
 	var snapshots []source.Snapshot
@@ -283,7 +322,7 @@
 	return snapshots, nil
 }
 
-func (s *session) IsOpen(uri span.URI) bool {
+func (s *Session) isOpen(uri span.URI) bool {
 	s.overlayMu.Lock()
 	defer s.overlayMu.Unlock()
 
@@ -291,10 +330,96 @@
 	return open
 }
 
-func (s *session) GetFile(uri span.URI) source.FileHandle {
+func (s *Session) updateOverlays(ctx context.Context, changes []source.FileModification) (map[span.URI]*overlay, error) {
+	s.overlayMu.Lock()
+	defer s.overlayMu.Unlock()
+
+	for _, c := range changes {
+		// Don't update overlays for on-disk changes.
+		if c.OnDisk {
+			continue
+		}
+
+		o, ok := s.overlays[c.URI]
+
+		// Determine the file kind on open, otherwise, assume it has been cached.
+		var kind source.FileKind
+		switch c.Action {
+		case source.Open:
+			kind = source.DetectLanguage(c.LanguageID, c.URI.Filename())
+		default:
+			if !ok {
+				return nil, errors.Errorf("updateOverlays: modifying unopened overlay %v", c.URI)
+			}
+			kind = o.kind
+		}
+		if kind == source.UnknownKind {
+			return nil, errors.Errorf("updateOverlays: unknown file kind for %s", c.URI)
+		}
+
+		// Closing a file just deletes its overlay.
+		if c.Action == source.Close {
+			delete(s.overlays, c.URI)
+			continue
+		}
+
+		// If the file is on disk, check if its content is the same as the overlay.
+		text := c.Text
+		if text == nil {
+			text = o.text
+		}
+		hash := hashContents(text)
+		var sameContentOnDisk bool
+		switch c.Action {
+		case source.Open:
+			_, h, err := s.cache.GetFile(c.URI).Read(ctx)
+			sameContentOnDisk = (err == nil && h == hash)
+		case source.Save:
+			// Make sure the version and content (if present) is the same.
+			if o.version != c.Version {
+				return nil, errors.Errorf("updateOverlays: saving %s at version %v, currently at %v", c.URI, c.Version, o.version)
+			}
+			if c.Text != nil && o.hash != hash {
+				return nil, errors.Errorf("updateOverlays: overlay %s changed on save", c.URI)
+			}
+			sameContentOnDisk = true
+		}
+		o = &overlay{
+			session: s,
+			uri:     c.URI,
+			version: c.Version,
+			text:    text,
+			kind:    kind,
+			hash:    hash,
+			saved:   sameContentOnDisk,
+		}
+		s.overlays[c.URI] = o
+	}
+
+	// Get the overlays for each change while the session's overlay map is locked.
+	overlays := make(map[span.URI]*overlay)
+	for _, c := range changes {
+		if o, ok := s.overlays[c.URI]; ok {
+			overlays[c.URI] = o
+		}
+	}
+	return overlays, nil
+}
+
+func (s *Session) GetFile(uri span.URI) source.FileHandle {
 	if overlay := s.readOverlay(uri); overlay != nil {
 		return overlay
 	}
 	// Fall back to the cache-level file system.
 	return s.cache.GetFile(uri)
 }
+
+func (s *Session) readOverlay(uri span.URI) *overlay {
+	s.overlayMu.Lock()
+	defer s.overlayMu.Unlock()
+
+	if overlay, ok := s.overlays[uri]; ok {
+		return overlay
+	}
+	return nil
+}
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index ce3641f..4391584 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -6,14 +6,19 @@
 
 import (
 	"context"
+	"fmt"
+	"go/ast"
+	"go/token"
 	"os"
 	"path/filepath"
 	"sync"
 
 	"golang.org/x/tools/go/analysis"
+	"golang.org/x/tools/go/packages"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/telemetry"
 	"golang.org/x/tools/internal/span"
+	"golang.org/x/tools/internal/telemetry/log"
 	errors "golang.org/x/xerrors"
 )
 
@@ -52,6 +57,9 @@
 
 	// unloadableFiles keeps track of files that we've failed to load.
 	unloadableFiles map[span.URI]struct{}
+
+	// modHandles keeps track of any ParseModHandles for this snapshot.
+	modHandles map[span.URI]*modHandle
 }
 
 type packageKey struct {
@@ -64,145 +72,119 @@
 	analyzer *analysis.Analyzer
 }
 
+func (s *snapshot) ID() uint64 {
+	return s.id
+}
+
 func (s *snapshot) View() source.View {
 	return s.view
 }
 
+// Config returns the configuration used for the snapshot's interaction with the
+// go/packages API.
+func (s *snapshot) Config(ctx context.Context) *packages.Config {
+	env, buildFlags := s.view.env()
+	cfg := &packages.Config{
+		Env:        env,
+		Dir:        s.view.folder.Filename(),
+		Context:    ctx,
+		BuildFlags: buildFlags,
+		Mode: packages.NeedName |
+			packages.NeedFiles |
+			packages.NeedCompiledGoFiles |
+			packages.NeedImports |
+			packages.NeedDeps |
+			packages.NeedTypesSizes,
+		Fset:    s.view.session.cache.fset,
+		Overlay: s.buildOverlay(),
+		ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) {
+			panic("go/packages must not be used to parse files")
+		},
+		Logf: func(format string, args ...interface{}) {
+			if s.view.options.VerboseOutput {
+				log.Print(ctx, fmt.Sprintf(format, args...))
+			}
+		},
+		Tests: true,
+	}
+	return cfg
+}
+
+func (s *snapshot) buildOverlay() map[string][]byte {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	overlays := make(map[string][]byte)
+	for uri, fh := range s.files {
+		overlay, ok := fh.(*overlay)
+		if !ok {
+			continue
+		}
+		if overlay.saved {
+			continue
+		}
+		// TODO(rstambler): Make sure not to send overlays outside of the current view.
+		overlays[uri.Filename()] = overlay.text
+	}
+	return overlays
+}
+
 func (s *snapshot) PackageHandles(ctx context.Context, fh source.FileHandle) ([]source.PackageHandle, error) {
-	// If the file is a go.mod file, go.Packages.Load will always return 0 packages.
-	if fh.Identity().Kind == source.Mod {
-		return nil, errors.Errorf("attempting to get PackageHandles of .mod file %s", fh.Identity().URI)
+	if fh.Identity().Kind != source.Go {
+		panic("called PackageHandles on a non-Go FileHandle")
 	}
 
 	ctx = telemetry.File.With(ctx, fh.Identity().URI)
-	meta := s.getMetadataForURI(fh.Identity().URI)
 
-	phs, err := s.packageHandles(ctx, fileURI(fh.Identity().URI), meta)
-	if err != nil {
-		return nil, err
+	// Check if we should reload metadata for the file. We don't invalidate IDs
+	// (though we should), so the IDs will be a better source of truth than the
+	// metadata. If there are no IDs for the file, then we should also reload.
+	ids := s.getIDsForURI(fh.Identity().URI)
+	reload := len(ids) == 0
+	for _, id := range ids {
+		// Reload package metadata if any of the metadata has missing
+		// dependencies, in case something has changed since the last time we
+		// reloaded it.
+		if m := s.getMetadata(id); m == nil {
+			reload = true
+			break
+		}
+		// TODO(golang/go#36918): Previously, we would reload any package with
+		// missing dependencies. This is expensive and results in too many
+		// calls to packages.Load. Determine what we should do instead.
 	}
-	var results []source.PackageHandle
-	for _, ph := range phs {
-		results = append(results, ph)
-	}
-	return results, nil
-}
-
-func (s *snapshot) packageHandle(ctx context.Context, id packageID) (*packageHandle, error) {
-	m := s.getMetadata(id)
-
-	// Don't reload metadata in this function.
-	// Callers of this function must reload metadata themselves.
-	if m == nil {
-		return nil, errors.Errorf("%s has no metadata", id)
-	}
-	phs, load, check := s.shouldCheck([]*metadata{m})
-	if load {
-		return nil, errors.Errorf("%s needs loading", id)
-	}
-	if check {
-		return s.buildPackageHandle(ctx, m.id, source.ParseFull)
-	}
-	var result *packageHandle
-	for _, ph := range phs {
-		if ph.m.id == id {
-			if result != nil {
-				return nil, errors.Errorf("multiple package handles for the same ID: %s", id)
-			}
-			result = ph
+	if reload {
+		if err := s.load(ctx, fileURI(fh.Identity().URI)); err != nil {
+			return nil, err
 		}
 	}
-	if result == nil {
-		return nil, errors.Errorf("no PackageHandle for %s", id)
-	}
-	return result, nil
-}
-
-func (s *snapshot) packageHandles(ctx context.Context, scope interface{}, meta []*metadata) ([]*packageHandle, error) {
-	// First, determine if we need to reload or recheck the package.
-	phs, load, check := s.shouldCheck(meta)
-	if load {
-		newMeta, err := s.load(ctx, scope)
+	// Get the list of IDs from the snapshot again, in case it has changed.
+	var phs []source.PackageHandle
+	for _, id := range s.getIDsForURI(fh.Identity().URI) {
+		ph, err := s.packageHandle(ctx, id, source.ParseFull)
 		if err != nil {
 			return nil, err
 		}
-		newMissing := missingImports(newMeta)
-		if len(newMissing) != 0 {
-			// Type checking a package with the same missing imports over and over
-			// is futile. Don't re-check unless something has changed.
-			check = check && !sameSet(missingImports(meta), newMissing)
-		}
-		meta = newMeta
-	}
-	var results []*packageHandle
-	if check {
-		for _, m := range meta {
-			ph, err := s.buildPackageHandle(ctx, m.id, source.ParseFull)
-			if err != nil {
-				return nil, err
-			}
-			results = append(results, ph)
-		}
-	} else {
-		results = phs
-	}
-	if len(results) == 0 {
-		return nil, errors.Errorf("packageHandles: no package handles for %v", scope)
-	}
-	return results, nil
-}
-
-func missingImports(metadata []*metadata) map[packagePath]struct{} {
-	result := map[packagePath]struct{}{}
-	for _, m := range metadata {
-		for path := range m.missingDeps {
-			result[path] = struct{}{}
-		}
-	}
-	return result
-}
-
-func sameSet(x, y map[packagePath]struct{}) bool {
-	if len(x) != len(y) {
-		return false
-	}
-	for k := range x {
-		if _, ok := y[k]; !ok {
-			return false
-		}
-	}
-	return true
-}
-
-// shouldCheck determines if the packages provided by the metadata
-// need to be re-loaded or re-type-checked.
-func (s *snapshot) shouldCheck(m []*metadata) (phs []*packageHandle, load, check bool) {
-	// No metadata. Re-load and re-check.
-	if len(m) == 0 {
-		return nil, true, true
-	}
-	// We expect to see a checked package for each package ID,
-	// and it should be parsed in full mode.
-	// If a single PackageHandle is missing, re-check all of them.
-	// TODO: Optimize this by only checking the necessary packages.
-	for _, metadata := range m {
-		ph := s.getPackage(metadata.id, source.ParseFull)
-		if ph == nil {
-			return nil, false, true
-		}
 		phs = append(phs, ph)
 	}
-	// If the metadata for the package had missing dependencies,
-	// we _may_ need to re-check. If the missing dependencies haven't changed
-	// since previous load, we will not check again.
-	if len(phs) < len(m) {
-		for _, m := range m {
-			if len(m.missingDeps) != 0 {
-				return nil, true, true
-			}
-		}
+	return phs, nil
+}
+
+// packageHandle returns a PackageHandle for the given ID. It assumes that
+// the metadata for the given ID has already been loaded, but if the
+// PackageHandle has not been constructed, it will rebuild it.
+func (s *snapshot) packageHandle(ctx context.Context, id packageID, mode source.ParseMode) (*packageHandle, error) {
+	ph := s.getPackage(id, mode)
+	if ph != nil {
+		return ph, nil
 	}
-	return phs, false, false
+	// Don't reload metadata in this function.
+	// Callers of this function must reload metadata themselves.
+	m := s.getMetadata(id)
+	if m == nil {
+		return nil, errors.Errorf("%s has no metadata", id)
+	}
+	return s.buildPackageHandle(ctx, m.id, mode)
 }
 
 func (s *snapshot) GetReverseDependencies(ctx context.Context, id string) ([]source.PackageHandle, error) {
@@ -217,7 +199,7 @@
 
 	var results []source.PackageHandle
 	for id := range ids {
-		ph, err := s.packageHandle(ctx, id)
+		ph, err := s.packageHandle(ctx, id, source.ParseFull)
 		if err != nil {
 			return nil, err
 		}
@@ -242,6 +224,12 @@
 	}
 }
 
+func (s *snapshot) getModHandle(uri span.URI) *modHandle {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	return s.modHandles[uri]
+}
+
 func (s *snapshot) getImportedBy(id packageID) []packageID {
 	s.mu.Lock()
 	defer s.mu.Unlock()
@@ -302,7 +290,7 @@
 	}
 	var results []source.PackageHandle
 	for _, pkgID := range s.workspacePackageIDs() {
-		ph, err := s.packageHandle(ctx, pkgID)
+		ph, err := s.packageHandle(ctx, pkgID, source.ParseFull)
 		if err != nil {
 			return nil, err
 		}
@@ -326,7 +314,7 @@
 
 	var results []source.PackageHandle
 	for pkgID := range wsPackages {
-		ph, err := s.packageHandle(ctx, pkgID)
+		ph, err := s.packageHandle(ctx, pkgID, source.ParseFull)
 		if err != nil {
 			return nil, err
 		}
@@ -384,13 +372,13 @@
 	return results, nil
 }
 
-func (s *snapshot) getPackage(id packageID, m source.ParseMode) *packageHandle {
+func (s *snapshot) getPackage(id packageID, mode source.ParseMode) *packageHandle {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
 	key := packageKey{
 		id:   id,
-		mode: m,
+		mode: mode,
 	}
 	return s.packages[key]
 }
@@ -426,11 +414,11 @@
 	s.actions[key] = ah
 }
 
-func (s *snapshot) getMetadataForURI(uri span.URI) []*metadata {
+func (s *snapshot) getIDsForURI(uri span.URI) []packageID {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
-	return s.getMetadataForURILocked(uri)
+	return s.ids[uri]
 }
 
 func (s *snapshot) getMetadataForURILocked(uri span.URI) (metadata []*metadata) {
@@ -445,19 +433,6 @@
 	return metadata
 }
 
-func (s *snapshot) setMetadata(m *metadata) {
-	s.mu.Lock()
-	defer s.mu.Unlock()
-
-	// TODO: We should make sure not to set duplicate metadata,
-	// and instead panic here. This can be done by making sure not to
-	// reset metadata information for packages we've already seen.
-	if _, ok := s.metadata[m.id]; ok {
-		return
-	}
-	s.metadata[m.id] = m
-}
-
 func (s *snapshot) getMetadata(id packageID) *metadata {
 	s.mu.Lock()
 	defer s.mu.Unlock()
@@ -469,24 +444,23 @@
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
-	for _, existingID := range s.ids[uri] {
+	for i, existingID := range s.ids[uri] {
+		// TODO: We should make sure not to set duplicate IDs,
+		// and instead panic here. This can be done by making sure not to
+		// reset metadata information for packages we've already seen.
 		if existingID == id {
-			// TODO: We should make sure not to set duplicate IDs,
-			// and instead panic here. This can be done by making sure not to
-			// reset metadata information for packages we've already seen.
+			return
+		}
+		// If we are setting a real ID, when the package had only previously
+		// had a command-line-arguments ID, we should just replace it.
+		if existingID == "command-line-arguments" {
+			s.ids[uri][i] = id
 			return
 		}
 	}
 	s.ids[uri] = append(s.ids[uri], id)
 }
 
-func (s *snapshot) getIDs(uri span.URI) []packageID {
-	s.mu.Lock()
-	defer s.mu.Unlock()
-
-	return s.ids[uri]
-}
-
 func (s *snapshot) isWorkspacePackage(id packageID) (packagePath, bool) {
 	s.mu.Lock()
 	defer s.mu.Unlock()
@@ -495,17 +469,6 @@
 	return scope, ok
 }
 
-func (s *snapshot) getFileURIs() []span.URI {
-	s.mu.Lock()
-	defer s.mu.Unlock()
-
-	var uris []span.URI
-	for uri := range s.files {
-		uris = append(uris, uri)
-	}
-	return uris
-}
-
 // GetFile returns a File for the given URI. It will always succeed because it
 // adds the file to the managed set if needed.
 func (s *snapshot) GetFile(uri span.URI) (source.FileHandle, error) {
@@ -513,51 +476,44 @@
 	if err != nil {
 		return nil, err
 	}
-	return s.getFileHandle(f), nil
-}
 
-func (s *snapshot) getFileHandle(f *fileBase) source.FileHandle {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
 	if _, ok := s.files[f.URI()]; !ok {
-		s.files[f.URI()] = s.view.session.GetFile(f.URI())
+		s.files[f.URI()] = s.view.session.cache.GetFile(uri)
 	}
-	return s.files[f.URI()]
+	return s.files[f.URI()], nil
 }
 
-func (s *snapshot) findFileHandle(f *fileBase) source.FileHandle {
+func (s *snapshot) IsOpen(uri span.URI) bool {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
-	return s.files[f.URI()]
+	_, open := s.files[uri].(*overlay)
+	return open
+}
+
+func (s *snapshot) IsSaved(uri span.URI) bool {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	ovl, open := s.files[uri].(*overlay)
+	return !open || ovl.saved
 }
 
 func (s *snapshot) awaitLoaded(ctx context.Context) error {
 	// Do not return results until the snapshot's view has been initialized.
 	s.view.awaitInitialized(ctx)
 
-	m, err := s.reloadWorkspace(ctx)
-	if err != nil {
+	if err := s.reloadWorkspace(ctx); err != nil {
 		return err
 	}
-	for _, m := range m {
-		s.setWorkspacePackage(ctx, m)
-	}
-	if err := s.reloadOrphanedFiles(ctx); err != nil {
-		return err
-	}
-	// Create package handles for all of the workspace packages.
-	for _, id := range s.workspacePackageIDs() {
-		if _, err := s.packageHandle(ctx, id); err != nil {
-			return err
-		}
-	}
-	return nil
+	return s.reloadOrphanedFiles(ctx)
 }
 
 // reloadWorkspace reloads the metadata for all invalidated workspace packages.
-func (s *snapshot) reloadWorkspace(ctx context.Context) ([]*metadata, error) {
+func (s *snapshot) reloadWorkspace(ctx context.Context) error {
 	// If the view's build configuration is invalid, we cannot reload by package path.
 	// Just reload the directory instead.
 	if !s.view.hasValidBuildConfiguration {
@@ -575,9 +531,8 @@
 	s.mu.Unlock()
 
 	if len(pkgPaths) == 0 {
-		return nil, nil
+		return nil
 	}
-
 	return s.load(ctx, pkgPaths...)
 }
 
@@ -591,7 +546,7 @@
 		return nil
 	}
 
-	m, err := s.load(ctx, scopes...)
+	err := s.load(ctx, scopes...)
 
 	// If we failed to load some files, i.e. they have no metadata,
 	// mark the failures so we don't bother retrying until the file's
@@ -612,9 +567,6 @@
 		}
 		s.mu.Unlock()
 	}
-	for _, m := range m {
-		s.setWorkspacePackage(ctx, m)
-	}
 	return nil
 }
 
@@ -657,20 +609,7 @@
 	return false
 }
 
-func (s *snapshot) setWorkspacePackage(ctx context.Context, m *metadata) {
-	s.mu.Lock()
-	defer s.mu.Unlock()
-
-	// A test variant of a package can only be loaded directly by loading
-	// the non-test variant with -test. Track the import path of the non-test variant.
-	pkgPath := m.pkgPath
-	if m.forTest != "" {
-		pkgPath = m.forTest
-	}
-	s.workspacePackages[m.id] = pkgPath
-}
-
-func (s *snapshot) clone(ctx context.Context, withoutURIs []span.URI) *snapshot {
+func (s *snapshot) clone(ctx context.Context, withoutURIs map[span.URI]source.FileHandle) *snapshot {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
@@ -685,6 +624,7 @@
 		files:             make(map[span.URI]source.FileHandle),
 		workspacePackages: make(map[packageID]packagePath),
 		unloadableFiles:   make(map[span.URI]struct{}),
+		modHandles:        make(map[span.URI]*modHandle),
 	}
 
 	// Copy all of the FileHandles.
@@ -695,13 +635,17 @@
 	for k, v := range s.unloadableFiles {
 		result.unloadableFiles[k] = v
 	}
+	// Copy all of the modHandles.
+	for k, v := range s.modHandles {
+		result.modHandles[k] = v
+	}
 
 	// transitiveIDs keeps track of transitive reverse dependencies.
 	// If an ID is present in the map, invalidate its types.
 	// If an ID's value is true, invalidate its metadata too.
 	transitiveIDs := make(map[packageID]bool)
 
-	for _, withoutURI := range withoutURIs {
+	for withoutURI, currentFH := range withoutURIs {
 		directIDs := map[packageID]struct{}{}
 
 		// Collect all of the package IDs that correspond to the given file.
@@ -709,13 +653,12 @@
 		for _, id := range s.ids[withoutURI] {
 			directIDs[id] = struct{}{}
 		}
-		// Get the current and original FileHandles for this URI.
-		currentFH := s.view.session.GetFile(withoutURI)
+		// The original FileHandle for this URI is cached on the snapshot.
 		originalFH := s.files[withoutURI]
 
 		// Check if the file's package name or imports have changed,
 		// and if so, invalidate this file's packages' metadata.
-		invalidateMetadata := s.view.session.cache.shouldLoad(ctx, s, originalFH, currentFH)
+		invalidateMetadata := s.shouldInvalidateMetadata(ctx, originalFH, currentFH)
 
 		// If a go.mod file's contents have changed, invalidate the metadata
 		// for all of the packages in the workspace.
@@ -723,6 +666,7 @@
 			for id := range s.workspacePackages {
 				directIDs[id] = struct{}{}
 			}
+			delete(result.modHandles, withoutURI)
 		}
 
 		// If this is a file we don't yet know about,
@@ -770,10 +714,6 @@
 		delete(result.unloadableFiles, withoutURI)
 	}
 
-	// Collect the IDs for the packages associated with the excluded URIs.
-	for k, ids := range s.ids {
-		result.ids[k] = ids
-	}
 	// Copy the set of initally loaded packages.
 	for k, v := range s.workspacePackages {
 		result.workspacePackages[k] = v
@@ -800,12 +740,63 @@
 		}
 		result.metadata[k] = v
 	}
+	// Copy the URI to package ID mappings, skipping only those URIs whose
+	// metadata will be reloaded in future calls to load.
+outer:
+	for k, ids := range s.ids {
+		for _, id := range ids {
+			if invalidateMetadata, ok := transitiveIDs[id]; invalidateMetadata && ok {
+				continue outer
+			}
+		}
+		result.ids[k] = ids
+	}
 	// Don't bother copying the importedBy graph,
 	// as it changes each time we update metadata.
 
 	return result
 }
 
-func (s *snapshot) ID() uint64 {
-	return s.id
+// shouldInvalidateMetadata reparses a file's package and import declarations to
+// determine if the file requires a metadata reload.
+func (s *snapshot) shouldInvalidateMetadata(ctx context.Context, originalFH, currentFH source.FileHandle) bool {
+	if originalFH == nil {
+		return currentFH.Identity().Kind == source.Go
+	}
+	// If the file hasn't changed, there's no need to reload.
+	if originalFH.Identity().String() == currentFH.Identity().String() {
+		return false
+	}
+	// If a go.mod file's contents have changed, always invalidate metadata.
+	if kind := originalFH.Identity().Kind; kind == source.Mod {
+		modfile, _ := s.view.ModFiles()
+		return originalFH.Identity().URI == modfile
+	}
+	// Get the original and current parsed files in order to check package name and imports.
+	original, _, _, _, originalErr := s.view.session.cache.ParseGoHandle(originalFH, source.ParseHeader).Parse(ctx)
+	current, _, _, _, currentErr := s.view.session.cache.ParseGoHandle(currentFH, source.ParseHeader).Parse(ctx)
+	if originalErr != nil || currentErr != nil {
+		return (originalErr == nil) != (currentErr == nil)
+	}
+	// Check if the package's metadata has changed. The cases handled are:
+	//    1. A package's name has changed
+	//    2. A file's imports have changed
+	if original.Name.Name != current.Name.Name {
+		return true
+	}
+	// If the package's imports have increased, definitely re-run `go list`.
+	if len(original.Imports) < len(current.Imports) {
+		return true
+	}
+	importSet := make(map[string]struct{})
+	for _, importSpec := range original.Imports {
+		importSet[importSpec.Path.Value] = struct{}{}
+	}
+	// If any of the current imports were not in the original imports.
+	for _, importSpec := range current.Imports {
+		if _, ok := importSet[importSpec.Path.Value]; !ok {
+			return true
+		}
+	}
+	return false
 }
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 9dbbdf1..2922460 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -10,10 +10,8 @@
 	"encoding/json"
 	"fmt"
 	"go/ast"
-	"go/token"
 	"io"
 	"io/ioutil"
-	stdlog "log"
 	"os"
 	"path/filepath"
 	"reflect"
@@ -23,19 +21,20 @@
 	"time"
 
 	"golang.org/x/tools/go/packages"
+	"golang.org/x/tools/internal/gocommand"
 	"golang.org/x/tools/internal/imports"
-	"golang.org/x/tools/internal/lsp/debug"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/telemetry"
 	"golang.org/x/tools/internal/memoize"
 	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/log"
+	"golang.org/x/tools/internal/telemetry/tag"
 	"golang.org/x/tools/internal/xcontext"
 	errors "golang.org/x/xerrors"
 )
 
 type view struct {
-	session *session
+	session *Session
 	id      string
 
 	options source.Options
@@ -233,19 +232,20 @@
 	return astObj, nil
 }
 
-func (v *view) buildBuiltinPackage(ctx context.Context, m *metadata) error {
-	if len(m.goFiles) != 1 {
-		return errors.Errorf("only expected 1 file, got %v", len(m.goFiles))
+func (v *view) buildBuiltinPackage(ctx context.Context, goFiles []string) error {
+	if len(goFiles) != 1 {
+		return errors.Errorf("only expected 1 file, got %v", len(goFiles))
 	}
-	uri := m.goFiles[0]
+	uri := span.URIFromPath(goFiles[0])
 	v.addIgnoredFile(uri) // to avoid showing diagnostics for builtin.go
 
-	// Get the FileHandle through the session to avoid adding it to the snapshot.
-	pgh := v.session.cache.ParseGoHandle(v.session.GetFile(uri), source.ParseFull)
+	// Get the FileHandle through the cache to avoid adding it to the snapshot
+	// and to get the file content from disk.
+	pgh := v.session.cache.ParseGoHandle(v.session.cache.GetFile(uri), source.ParseFull)
 	fset := v.session.cache.fset
 	h := v.session.cache.store.Bind(pgh.File().Identity(), func(ctx context.Context) interface{} {
 		data := &builtinPackageData{}
-		file, _, _, err := pgh.Parse(ctx)
+		file, _, _, _, err := pgh.Parse(ctx)
 		if err != nil {
 			data.err = err
 			return data
@@ -262,44 +262,6 @@
 	return nil
 }
 
-// Config returns the configuration used for the view's interaction with the
-// go/packages API. It is shared across all views.
-func (v *view) Config(ctx context.Context) *packages.Config {
-	// TODO: Should we cache the config and/or overlay somewhere?
-
-	// We want to run the go commands with the -modfile flag if the version of go
-	// that we are using supports it.
-	buildFlags := v.options.BuildFlags
-	if v.tempMod != "" {
-		buildFlags = append(buildFlags, fmt.Sprintf("-modfile=%s", v.tempMod.Filename()))
-	}
-	cfg := &packages.Config{
-		Dir:        v.folder.Filename(),
-		Context:    ctx,
-		BuildFlags: buildFlags,
-		Mode: packages.NeedName |
-			packages.NeedFiles |
-			packages.NeedCompiledGoFiles |
-			packages.NeedImports |
-			packages.NeedDeps |
-			packages.NeedTypesSizes,
-		Fset:    v.session.cache.fset,
-		Overlay: v.session.buildOverlay(),
-		ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) {
-			panic("go/packages must not be used to parse files")
-		},
-		Logf: func(format string, args ...interface{}) {
-			if v.options.VerboseOutput {
-				log.Print(ctx, fmt.Sprintf(format, args...))
-			}
-		},
-		Tests: true,
-	}
-	cfg.Env = append(cfg.Env, fmt.Sprintf("GOPATH=%s", v.gopath))
-	cfg.Env = append(cfg.Env, v.options.Env...)
-	return cfg
-}
-
 func (v *view) RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error) error {
 	v.importsMu.Lock()
 	defer v.importsMu.Unlock()
@@ -313,8 +275,7 @@
 
 	// In module mode, check if the mod file has changed.
 	if v.realMod != "" {
-		mod, err := v.Snapshot().GetFile(v.realMod)
-		if err == nil && mod.Identity() != v.cachedModFileVersion {
+		if mod := v.session.cache.GetFile(v.realMod); mod.Identity() != v.cachedModFileVersion {
 			v.processEnv.GetResolver().(*imports.ModuleResolver).ClearForNewMod()
 			v.cachedModFileVersion = mod.Identity()
 		}
@@ -358,9 +319,9 @@
 	v.importsMu.Unlock()
 
 	// We don't have a context handy to use for logging, so use the stdlib for now.
-	stdlog.Printf("background imports cache refresh starting")
+	log.Print(v.baseCtx, "background imports cache refresh starting")
 	err := imports.PrimeCache(context.Background(), env)
-	stdlog.Printf("background refresh finished after %v with err: %v", time.Since(start), err)
+	log.Print(v.baseCtx, fmt.Sprintf("background refresh finished after %v", time.Since(start)), tag.Of("Error", err))
 
 	v.importsMu.Lock()
 	v.cacheRefreshDuration = time.Since(start)
@@ -369,17 +330,17 @@
 }
 
 func (v *view) buildProcessEnv(ctx context.Context) (*imports.ProcessEnv, error) {
-	cfg := v.Config(ctx)
+	env, buildFlags := v.env()
 	processEnv := &imports.ProcessEnv{
-		WorkingDir: cfg.Dir,
-		BuildFlags: cfg.BuildFlags,
+		WorkingDir: v.folder.Filename(),
+		BuildFlags: buildFlags,
 		Logf: func(format string, args ...interface{}) {
 			log.Print(ctx, fmt.Sprintf(format, args...))
 		},
 		LocalPrefix: v.options.LocalPrefix,
 		Debug:       v.options.VerboseOutput,
 	}
-	for _, kv := range cfg.Env {
+	for _, kv := range env {
 		split := strings.Split(kv, "=")
 		if len(split) < 2 {
 			continue
@@ -402,10 +363,16 @@
 	return processEnv, nil
 }
 
-func (v *view) fileVersion(filename string) string {
-	uri := span.FileURI(filename)
-	fh := v.session.GetFile(uri)
-	return fh.Identity().String()
+func (v *view) env() ([]string, []string) {
+	// We want to run the go commands with the -modfile flag if the version of go
+	// that we are using supports it.
+	buildFlags := v.options.BuildFlags
+	if v.tempMod != "" {
+		buildFlags = append(buildFlags, fmt.Sprintf("-modfile=%s", v.tempMod.Filename()))
+	}
+	env := []string{fmt.Sprintf("GOPATH=%s", v.gopath)}
+	env = append(env, v.options.Env...)
+	return env, buildFlags
 }
 
 func (v *view) contains(uri span.URI) bool {
@@ -516,7 +483,7 @@
 		os.Remove(v.tempMod.Filename())
 		os.Remove(tempSumFile(v.tempMod.Filename()))
 	}
-	debug.DropView(debugView{v})
+	v.session.cache.debug.DropView(debugView{v})
 }
 
 // Ignore checks if the given URI is a URI we ignore.
@@ -565,30 +532,7 @@
 	v.initializeOnce.Do(func() {
 		defer close(v.initialized)
 
-		err := func() error {
-			// Do not cancel the call to go/packages.Load for the entire workspace.
-			meta, err := s.load(ctx, viewLoadScope("LOAD_VIEW"), packagePath("builtin"))
-			if err != nil {
-				return err
-			}
-			// Keep track of the workspace packages.
-			for _, m := range meta {
-				// Make sure to handle the builtin package separately
-				// Don't set it as a workspace package.
-				if m.pkgPath == "builtin" {
-					if err := s.view.buildBuiltinPackage(ctx, m); err != nil {
-						return err
-					}
-					continue
-				}
-				s.setWorkspacePackage(ctx, m)
-				if _, err := s.packageHandle(ctx, m.id); err != nil {
-					return err
-				}
-			}
-			return nil
-		}()
-		if err != nil {
+		if err := s.load(ctx, viewLoadScope("LOAD_VIEW"), packagePath("builtin")); err != nil {
 			log.Error(ctx, "initial workspace load failed", err)
 		}
 	})
@@ -604,7 +548,7 @@
 // invalidateContent invalidates the content of a Go file,
 // including any position and type information that depends on it.
 // It returns true if we were already tracking the given file, false otherwise.
-func (v *view) invalidateContent(ctx context.Context, uris []span.URI) source.Snapshot {
+func (v *view) invalidateContent(ctx context.Context, uris map[span.URI]source.FileHandle) source.Snapshot {
 	// Detach the context so that content invalidation cannot be canceled.
 	ctx = xcontext.Detach(ctx)
 
@@ -641,14 +585,14 @@
 	if modFile == os.DevNull {
 		return nil
 	}
-	v.realMod = span.FileURI(modFile)
+	v.realMod = span.URIFromPath(modFile)
 
 	// Now that we have set all required fields,
 	// check if the view has a valid build configuration.
 	v.hasValidBuildConfiguration = checkBuildConfiguration(v.goCommand, v.realMod, v.folder, v.gopath)
 
-	// The user has disabled the use of the -modfile flag.
-	if !modfileFlagEnabled {
+	// The user has disabled the use of the -modfile flag or has no go.mod file.
+	if !modfileFlagEnabled || v.realMod == "" {
 		return nil
 	}
 	if modfileFlag, err := v.modfileFlagExists(ctx, v.Options().Env); err != nil {
@@ -657,9 +601,10 @@
 		return nil
 	}
 	// Copy the current go.mod file into the temporary go.mod file.
-	// The file's name will be of the format go.1234.mod.
-	// It's temporary go.sum file should have the corresponding format of go.1234.sum.
-	tempModFile, err := ioutil.TempFile("", "go.*.mod")
+	// The file's name will be of the format go.directory.1234.mod.
+	// It's temporary go.sum file should have the corresponding format of go.directory.1234.sum.
+	tmpPattern := fmt.Sprintf("go.%s.*.mod", filepath.Base(folder.Filename()))
+	tempModFile, err := ioutil.TempFile("", tmpPattern)
 	if err != nil {
 		return err
 	}
@@ -674,7 +619,7 @@
 	if _, err := io.Copy(tempModFile, origFile); err != nil {
 		return err
 	}
-	v.tempMod = span.FileURI(tempModFile.Name())
+	v.tempMod = span.URIFromPath(tempModFile.Name())
 
 	// Copy go.sum file as well (if there is one).
 	sumFile := filepath.Join(filepath.Dir(modFile), "go.sum")
@@ -741,12 +686,18 @@
 			gopackagesdriver = true
 		}
 	}
-	b, err := source.InvokeGo(ctx, v.folder.Filename(), env, "env", "-json")
+	inv := gocommand.Invocation{
+		Verb:       "env",
+		Args:       []string{"-json"},
+		Env:        env,
+		WorkingDir: v.Folder().Filename(),
+	}
+	stdout, err := inv.Run(ctx)
 	if err != nil {
 		return "", err
 	}
 	envMap := make(map[string]string)
-	decoder := json.NewDecoder(b)
+	decoder := json.NewDecoder(stdout)
 	if err := decoder.Decode(&envMap); err != nil {
 		return "", err
 	}
@@ -825,7 +776,13 @@
 	// Borrowed from internal/imports/mod.go:620.
 	const format = `{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}`
 	folder := v.folder.Filename()
-	stdout, err := source.InvokeGo(ctx, folder, append(env, "GO111MODULE=off"), "list", "-e", "-f", format)
+	inv := gocommand.Invocation{
+		Verb:       "list",
+		Args:       []string{"-e", "-f", format},
+		Env:        append(env, "GO111MODULE=off"),
+		WorkingDir: v.Folder().Filename(),
+	}
+	stdout, err := inv.Run(ctx)
 	if err != nil {
 		return false, err
 	}
diff --git a/internal/lsp/cmd/capabilities_test.go b/internal/lsp/cmd/capabilities_test.go
index 4d15a2e..e966166 100644
--- a/internal/lsp/cmd/capabilities_test.go
+++ b/internal/lsp/cmd/capabilities_test.go
@@ -10,7 +10,6 @@
 	"golang.org/x/tools/internal/lsp"
 	"golang.org/x/tools/internal/lsp/cache"
 	"golang.org/x/tools/internal/lsp/protocol"
-	"golang.org/x/tools/internal/span"
 	errors "golang.org/x/xerrors"
 )
 
@@ -36,11 +35,11 @@
 	defer c.terminate(ctx)
 
 	params := &protocol.ParamInitialize{}
-	params.RootURI = string(span.FileURI(c.Client.app.wd))
+	params.RootURI = protocol.URIFromPath(c.Client.app.wd)
 	params.Capabilities.Workspace.Configuration = true
 
 	// Send an initialize request to the server.
-	ctx, c.Server = lsp.NewClientServer(ctx, cache.New(app.options).NewSession(), c.Client)
+	c.Server = lsp.NewServer(cache.New(app.options, nil).NewSession(), c.Client)
 	result, err := c.Server.Initialize(ctx, params)
 	if err != nil {
 		t.Fatal(err)
@@ -55,7 +54,7 @@
 	}
 
 	// Open the file on the server side.
-	uri := protocol.NewURI(span.FileURI(tmpFile))
+	uri := protocol.URIFromPath(tmpFile)
 	if err := c.Server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
 		TextDocument: protocol.TextDocumentItem{
 			URI:        uri,
diff --git a/internal/lsp/cmd/check.go b/internal/lsp/cmd/check.go
index 3331053..7d22db8 100644
--- a/internal/lsp/cmd/check.go
+++ b/internal/lsp/cmd/check.go
@@ -48,7 +48,7 @@
 	}
 	defer conn.terminate(ctx)
 	for _, arg := range args {
-		uri := span.FileURI(arg)
+		uri := span.URIFromPath(arg)
 		uris = append(uris, uri)
 		file := conn.AddFile(ctx, uri)
 		if file.err != nil {
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index e0eefaf..aeb8bd0 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -22,11 +22,10 @@
 	"golang.org/x/tools/internal/jsonrpc2"
 	"golang.org/x/tools/internal/lsp"
 	"golang.org/x/tools/internal/lsp/cache"
+	"golang.org/x/tools/internal/lsp/debug"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/span"
-	"golang.org/x/tools/internal/telemetry/export"
-	"golang.org/x/tools/internal/telemetry/export/ocagent"
 	"golang.org/x/tools/internal/tool"
 	"golang.org/x/tools/internal/xcontext"
 	errors "golang.org/x/xerrors"
@@ -58,7 +57,7 @@
 	env []string
 
 	// Support for remote lsp server
-	Remote string `flag:"remote" help:"*EXPERIMENTAL* - forward all commands to a remote lsp"`
+	Remote string `flag:"remote" help:"*EXPERIMENTAL* - forward all commands to a remote lsp specified by this flag. If prefixed by 'unix;', the subsequent address is assumed to be a unix domain socket. If 'auto', or prefixed by 'auto', the remote address is automatically resolved based on the executing environment. Otherwise, TCP is used."`
 
 	// Enable verbose logging
 	Verbose bool `flag:"v" help:"verbose output"`
@@ -69,6 +68,8 @@
 	// PrepareOptions is called to update the options when a new view is built.
 	// It is primarily to allow the behavior of gopls to be modified by hooks.
 	PrepareOptions func(*source.Options)
+
+	debug *debug.Instance
 }
 
 // New returns a new Application ready to run.
@@ -130,10 +131,7 @@
 // If no arguments are passed it will invoke the server sub command, as a
 // temporary measure for compatibility.
 func (app *Application) Run(ctx context.Context, args ...string) error {
-	ocConfig := ocagent.Discover()
-	//TODO: we should not need to adjust the discovered configuration
-	ocConfig.Address = app.OCAgent
-	export.AddExporters(ocagent.Connect(ocConfig))
+	app.debug = debug.NewInstance(app.wd, app.OCAgent)
 	app.Serve.app = app
 	if len(args) == 0 {
 		return tool.Run(ctx, &app.Serve, args)
@@ -174,6 +172,7 @@
 		&implementation{app: app},
 		&imports{app: app},
 		&links{app: app},
+		&prepareRename{app: app},
 		&query{app: app},
 		&references{app: app},
 		&rename{app: app},
@@ -189,54 +188,54 @@
 )
 
 func (app *Application) connect(ctx context.Context) (*connection, error) {
-	switch app.Remote {
-	case "":
+	switch {
+	case app.Remote == "":
 		connection := newConnection(app)
-		ctx, connection.Server = lsp.NewClientServer(ctx, cache.New(app.options).NewSession(), connection.Client)
+		connection.Server = lsp.NewServer(cache.New(app.options, nil).NewSession(), connection.Client)
+		ctx = protocol.WithClient(ctx, connection.Client)
 		return connection, connection.initialize(ctx, app.options)
-	case "internal":
+	case strings.HasPrefix(app.Remote, "internal@"):
 		internalMu.Lock()
 		defer internalMu.Unlock()
 		if c := internalConnections[app.wd]; c != nil {
 			return c, nil
 		}
-		connection := newConnection(app)
+		remote := app.Remote[len("internal@"):]
 		ctx := xcontext.Detach(ctx) //TODO:a way of shutting down the internal server
-		cr, sw, _ := os.Pipe()
-		sr, cw, _ := os.Pipe()
-		var jc *jsonrpc2.Conn
-		ctx, jc, connection.Server = protocol.NewClient(ctx, jsonrpc2.NewHeaderStream(cr, cw), connection.Client)
-		go jc.Run(ctx)
-		go func() {
-			ctx, srv := lsp.NewServer(ctx, cache.New(app.options).NewSession(), jsonrpc2.NewHeaderStream(sr, sw))
-			srv.Run(ctx)
-		}()
-		if err := connection.initialize(ctx, app.options); err != nil {
+		connection, err := app.connectRemote(ctx, remote)
+		if err != nil {
 			return nil, err
 		}
 		internalConnections[app.wd] = connection
 		return connection, nil
 	default:
-		connection := newConnection(app)
-		conn, err := net.Dial("tcp", app.Remote)
-		if err != nil {
-			return nil, err
-		}
-		stream := jsonrpc2.NewHeaderStream(conn, conn)
-		var jc *jsonrpc2.Conn
-		ctx, jc, connection.Server = protocol.NewClient(ctx, stream, connection.Client)
-		go jc.Run(ctx)
-		return connection, connection.initialize(ctx, app.options)
+		return app.connectRemote(ctx, app.Remote)
 	}
 }
 
+func (app *Application) connectRemote(ctx context.Context, remote string) (*connection, error) {
+	connection := newConnection(app)
+	conn, err := net.Dial("tcp", remote)
+	if err != nil {
+		return nil, err
+	}
+	stream := jsonrpc2.NewHeaderStream(conn, conn)
+	cc := jsonrpc2.NewConn(stream)
+	connection.Server = protocol.ServerDispatcher(cc)
+	cc.AddHandler(protocol.ClientHandler(connection.Client))
+	cc.AddHandler(protocol.Canceller{})
+	ctx = protocol.WithClient(ctx, connection.Client)
+	go cc.Run(ctx)
+	return connection, connection.initialize(ctx, app.options)
+}
+
 func (c *connection) initialize(ctx context.Context, options func(*source.Options)) error {
 	params := &protocol.ParamInitialize{}
-	params.RootURI = string(span.FileURI(c.Client.app.wd))
+	params.RootURI = protocol.URIFromPath(c.Client.app.wd)
 	params.Capabilities.Workspace.Configuration = true
 
 	// Make sure to respect configured options when sending initialize request.
-	opts := source.DefaultOptions
+	opts := source.DefaultOptions()
 	if options != nil {
 		options(&opts)
 	}
@@ -288,6 +287,15 @@
 	}
 }
 
+// fileURI converts a DocumentURI to a file:// span.URI, panicking if it's not a file.
+func fileURI(uri protocol.DocumentURI) span.URI {
+	sURI := uri.SpanURI()
+	if !sURI.IsFile() {
+		panic(fmt.Sprintf("%q is not a file URI", uri))
+	}
+	return sURI
+}
+
 func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { return nil }
 
 func (c *cmdClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) {
@@ -368,8 +376,7 @@
 	c.filesMu.Lock()
 	defer c.filesMu.Unlock()
 
-	uri := span.URI(p.URI)
-	file := c.getFile(ctx, uri)
+	file := c.getFile(ctx, fileURI(p.URI))
 	file.diagnostics = p.Diagnostics
 	return nil
 }
@@ -419,7 +426,7 @@
 	file.added = true
 	p := &protocol.DidOpenTextDocumentParams{
 		TextDocument: protocol.TextDocumentItem{
-			URI:        protocol.NewURI(uri),
+			URI:        protocol.URIFromSpanURI(uri),
 			LanguageID: source.DetectLanguage("", file.uri.Filename()).String(),
 			Version:    1,
 			Text:       string(file.mapper.Content),
@@ -446,7 +453,7 @@
 }
 
 func (c *connection) terminate(ctx context.Context) {
-	if c.Client.app.Remote == "internal" {
+	if strings.HasPrefix(c.Client.app.Remote, "internal@") {
 		// internal connections need to be left alive for the next test
 		return
 	}
diff --git a/internal/lsp/cmd/cmd_test.go b/internal/lsp/cmd/cmd_test.go
index 0e417c6..bc56a3a 100644
--- a/internal/lsp/cmd/cmd_test.go
+++ b/internal/lsp/cmd/cmd_test.go
@@ -5,6 +5,7 @@
 package cmd_test
 
 import (
+	"context"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -13,8 +14,12 @@
 	"testing"
 
 	"golang.org/x/tools/go/packages/packagestest"
+	"golang.org/x/tools/internal/jsonrpc2/servertest"
+	"golang.org/x/tools/internal/lsp/cache"
 	"golang.org/x/tools/internal/lsp/cmd"
 	cmdtest "golang.org/x/tools/internal/lsp/cmd/test"
+	"golang.org/x/tools/internal/lsp/debug"
+	"golang.org/x/tools/internal/lsp/lsprpc"
 	"golang.org/x/tools/internal/lsp/tests"
 	"golang.org/x/tools/internal/testenv"
 )
@@ -29,9 +34,23 @@
 }
 
 func testCommandLine(t *testing.T, exporter packagestest.Exporter) {
+	ctx := tests.Context(t)
+	ts := testServer(ctx)
 	data := tests.Load(t, exporter, "../testdata")
-	defer data.Exported.Cleanup()
-	tests.Run(t, cmdtest.NewRunner(exporter, data, tests.Context(t), nil), data)
+	for _, datum := range data {
+		defer datum.Exported.Cleanup()
+		t.Run(datum.Folder, func(t *testing.T) {
+			t.Helper()
+			tests.Run(t, cmdtest.NewRunner(exporter, datum, ctx, ts.Addr, nil), datum)
+		})
+	}
+}
+
+func testServer(ctx context.Context) *servertest.TCPServer {
+	di := debug.NewInstance("", "")
+	cache := cache.New(nil, di.State)
+	ss := lsprpc.NewStreamServer(cache, false, di)
+	return servertest.NewTCPServer(ctx, ss)
 }
 
 func TestDefinitionHelpExample(t *testing.T) {
@@ -45,6 +64,8 @@
 		t.Errorf("could not get wd: %v", err)
 		return
 	}
+	ctx := tests.Context(t)
+	ts := testServer(ctx)
 	thisFile := filepath.Join(dir, "definition.go")
 	baseArgs := []string{"query", "definition"}
 	expect := regexp.MustCompile(`(?s)^[\w/\\:_-]+flag[/\\]flag.go:\d+:\d+-\d+: defined here as FlagSet struct {.*}$`)
@@ -52,7 +73,7 @@
 		fmt.Sprintf("%v:%v:%v", thisFile, cmd.ExampleLine, cmd.ExampleColumn),
 		fmt.Sprintf("%v:#%v", thisFile, cmd.ExampleOffset)} {
 		args := append(baseArgs, query)
-		r := cmdtest.NewRunner(nil, nil, tests.Context(t), nil)
+		r := cmdtest.NewRunner(nil, nil, ctx, ts.Addr, nil)
 		got, _ := r.NormalizeGoplsCmd(t, args...)
 		if !expect.MatchString(got) {
 			t.Errorf("test with %v\nexpected:\n%s\ngot:\n%s", args, expect, got)
diff --git a/internal/lsp/cmd/definition.go b/internal/lsp/cmd/definition.go
index 5cabac2..2b2e3c0 100644
--- a/internal/lsp/cmd/definition.go
+++ b/internal/lsp/cmd/definition.go
@@ -109,7 +109,7 @@
 	if hover == nil {
 		return errors.Errorf("%v: not an identifier", from)
 	}
-	file = conn.AddFile(ctx, span.NewURI(locs[0].URI))
+	file = conn.AddFile(ctx, fileURI(locs[0].URI))
 	if file.err != nil {
 		return errors.Errorf("%v: %v", from, file.err)
 	}
@@ -134,9 +134,6 @@
 	default:
 		return errors.Errorf("unknown emulation for definition: %s", d.query.Emulate)
 	}
-	if err != nil {
-		return err
-	}
 	if d.query.JSON {
 		enc := json.NewEncoder(os.Stdout)
 		enc.SetIndent("", "\t")
diff --git a/internal/lsp/cmd/folding_range.go b/internal/lsp/cmd/folding_range.go
index d6e3b73..f655f30 100644
--- a/internal/lsp/cmd/folding_range.go
+++ b/internal/lsp/cmd/folding_range.go
@@ -50,7 +50,7 @@
 
 	p := protocol.FoldingRangeParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(from.URI()),
+			URI: protocol.URIFromSpanURI(from.URI()),
 		},
 	}
 
diff --git a/internal/lsp/cmd/implementation.go b/internal/lsp/cmd/implementation.go
index 98a6b8a..e498372 100644
--- a/internal/lsp/cmd/implementation.go
+++ b/internal/lsp/cmd/implementation.go
@@ -72,7 +72,7 @@
 
 	var spans []string
 	for _, impl := range implementations {
-		f := conn.AddFile(ctx, span.NewURI(impl.URI))
+		f := conn.AddFile(ctx, fileURI(impl.URI))
 		span, err := f.mapper.Span(impl)
 		if err != nil {
 			return err
diff --git a/internal/lsp/cmd/imports.go b/internal/lsp/cmd/imports.go
index 2127e25..a6d00e9 100644
--- a/internal/lsp/cmd/imports.go
+++ b/internal/lsp/cmd/imports.go
@@ -62,7 +62,7 @@
 	}
 	actions, err := conn.CodeAction(ctx, &protocol.CodeActionParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	})
 	if err != nil {
@@ -74,7 +74,7 @@
 			continue
 		}
 		for _, c := range a.Edit.DocumentChanges {
-			if c.TextDocument.URI == string(uri) {
+			if fileURI(c.TextDocument.URI) == uri {
 				edits = append(edits, c.Edits...)
 			}
 		}
diff --git a/internal/lsp/cmd/links.go b/internal/lsp/cmd/links.go
index a93ae8f..1d5a669 100644
--- a/internal/lsp/cmd/links.go
+++ b/internal/lsp/cmd/links.go
@@ -59,7 +59,7 @@
 	}
 	results, err := conn.DocumentLink(ctx, &protocol.DocumentLinkParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	})
 	if err != nil {
diff --git a/internal/lsp/cmd/prepare_rename.go b/internal/lsp/cmd/prepare_rename.go
new file mode 100644
index 0000000..40ee201
--- /dev/null
+++ b/internal/lsp/cmd/prepare_rename.go
@@ -0,0 +1,80 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cmd
+
+import (
+	"context"
+	"flag"
+	"fmt"
+
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/span"
+	"golang.org/x/tools/internal/tool"
+)
+
+// prepareRename implements the prepare_rename verb for gopls.
+type prepareRename struct {
+	app *Application
+}
+
+func (r *prepareRename) Name() string      { return "prepare_rename" }
+func (r *prepareRename) Usage() string     { return "<position>" }
+func (r *prepareRename) ShortHelp() string { return "test validity of a rename operation at location" }
+func (r *prepareRename) DetailedHelp(f *flag.FlagSet) {
+	fmt.Fprint(f.Output(), `
+Example:
+
+	$ # 1-indexed location (:line:column or :#offset) of the target identifier
+	$ gopls prepare_rename helper/helper.go:8:6
+	$ gopls prepare_rename helper/helper.go:#53
+
+	gopls prepare_rename flags are:
+`)
+	f.PrintDefaults()
+}
+
+func (r *prepareRename) Run(ctx context.Context, args ...string) error {
+	if len(args) != 1 {
+		return tool.CommandLineErrorf("prepare_rename expects 1 argument (file)")
+	}
+
+	conn, err := r.app.connect(ctx)
+	if err != nil {
+		return err
+	}
+	defer conn.terminate(ctx)
+
+	from := span.Parse(args[0])
+	file := conn.AddFile(ctx, from.URI())
+	if file.err != nil {
+		return file.err
+	}
+	loc, err := file.mapper.Location(from)
+	if err != nil {
+		return err
+	}
+	p := protocol.PrepareRenameParams{
+		TextDocumentPositionParams: protocol.TextDocumentPositionParams{
+			TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
+			Position:     loc.Range.Start,
+		},
+	}
+	result, err := conn.PrepareRename(ctx, &p)
+	if err != nil {
+		return fmt.Errorf("prepare_rename failed: %v", err)
+	}
+	if result == nil {
+		return fmt.Errorf("request is not valid at the given position")
+	}
+
+	l := protocol.Location{Range: *result}
+	s, err := file.mapper.Span(l)
+	if err != nil {
+		return err
+	}
+
+	fmt.Println(s)
+	return nil
+}
diff --git a/internal/lsp/cmd/references.go b/internal/lsp/cmd/references.go
index cc85e79..5626019 100644
--- a/internal/lsp/cmd/references.go
+++ b/internal/lsp/cmd/references.go
@@ -73,7 +73,7 @@
 	}
 	var spans []string
 	for _, l := range locations {
-		f := conn.AddFile(ctx, span.NewURI(l.URI))
+		f := conn.AddFile(ctx, fileURI(l.URI))
 		// convert location to span for user-friendly 1-indexed line
 		// and column numbers
 		span, err := f.mapper.Span(l)
diff --git a/internal/lsp/cmd/rename.go b/internal/lsp/cmd/rename.go
index 42eeeaa..57cf846 100644
--- a/internal/lsp/cmd/rename.go
+++ b/internal/lsp/cmd/rename.go
@@ -81,15 +81,15 @@
 	var orderedURIs []string
 	edits := map[span.URI][]protocol.TextEdit{}
 	for _, c := range edit.DocumentChanges {
-		uri := span.URI(c.TextDocument.URI)
+		uri := fileURI(c.TextDocument.URI)
 		edits[uri] = append(edits[uri], c.Edits...)
-		orderedURIs = append(orderedURIs, c.TextDocument.URI)
+		orderedURIs = append(orderedURIs, string(uri))
 	}
 	sort.Strings(orderedURIs)
 	changeCount := len(orderedURIs)
 
 	for _, u := range orderedURIs {
-		uri := span.URI(u)
+		uri := span.URIFromURI(u)
 		cmdFile := conn.AddFile(ctx, uri)
 		filename := cmdFile.uri.Filename()
 
diff --git a/internal/lsp/cmd/serve.go b/internal/lsp/cmd/serve.go
index 02b71f0..17ee9e3 100644
--- a/internal/lsp/cmd/serve.go
+++ b/internal/lsp/cmd/serve.go
@@ -6,36 +6,29 @@
 
 import (
 	"context"
-	"encoding/json"
 	"flag"
 	"fmt"
-	"io"
-	"log"
-	"net"
 	"os"
-	"path/filepath"
+	"strings"
 	"time"
 
 	"golang.org/x/tools/internal/jsonrpc2"
-	"golang.org/x/tools/internal/lsp"
 	"golang.org/x/tools/internal/lsp/cache"
-	"golang.org/x/tools/internal/lsp/debug"
+	"golang.org/x/tools/internal/lsp/lsprpc"
 	"golang.org/x/tools/internal/lsp/protocol"
-	"golang.org/x/tools/internal/lsp/telemetry"
-	"golang.org/x/tools/internal/telemetry/trace"
 	"golang.org/x/tools/internal/tool"
-	errors "golang.org/x/xerrors"
 )
 
 // Serve is a struct that exposes the configurable parts of the LSP server as
 // flags, in the right form for tool.Main to consume.
 type Serve struct {
-	Logfile string `flag:"logfile" help:"filename to log to. if value is \"auto\", then logging to a default output file is enabled"`
-	Mode    string `flag:"mode" help:"no effect"`
-	Port    int    `flag:"port" help:"port on which to run gopls for debugging purposes"`
-	Address string `flag:"listen" help:"address on which to listen for remote connections"`
-	Trace   bool   `flag:"rpc.trace" help:"print the full rpc trace in lsp inspector format"`
-	Debug   string `flag:"debug" help:"serve debug information on the supplied address"`
+	Logfile     string        `flag:"logfile" help:"filename to log to. if value is \"auto\", then logging to a default output file is enabled"`
+	Mode        string        `flag:"mode" help:"no effect"`
+	Port        int           `flag:"port" help:"port on which to run gopls for debugging purposes"`
+	Address     string        `flag:"listen" help:"address on which to listen for remote connections. If prefixed by 'unix;', the subsequent address is assumed to be a unix domain socket. Otherwise, TCP is used."`
+	IdleTimeout time.Duration `flag:"listen.timeout" help:"when used with -listen, shut down the server when there are no connected clients for this duration"`
+	Trace       bool          `flag:"rpc.trace" help:"print the full rpc trace in lsp inspector format"`
+	Debug       string        `flag:"debug" help:"serve debug information on the supplied address"`
 
 	app *Application
 }
@@ -61,176 +54,49 @@
 	if len(args) > 0 {
 		return tool.CommandLineErrorf("server does not take arguments, got %v", args)
 	}
-	out := os.Stderr
-	logfile := s.Logfile
-	if logfile != "" {
-		if logfile == "auto" {
-			logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.log", os.Getpid()))
-		}
-		f, err := os.Create(logfile)
-		if err != nil {
-			return errors.Errorf("Unable to create log file: %v", err)
-		}
-		defer f.Close()
-		log.SetOutput(io.MultiWriter(os.Stderr, f))
-		out = f
-	}
 
-	debug.Serve(ctx, s.Debug, debugServe{s: s, logfile: logfile, start: time.Now()})
-
-	if s.app.Remote != "" {
-		return s.forward()
-	}
-
-	prepare := func(ctx context.Context, srv *lsp.Server) *lsp.Server {
-		srv.Conn.AddHandler(&handler{})
-		return srv
-	}
-	run := func(ctx context.Context, srv *lsp.Server) { go prepare(ctx, srv).Run(ctx) }
-	if s.Address != "" {
-		return lsp.RunServerOnAddress(ctx, cache.New(s.app.options), s.Address, run)
-	}
-	if s.Port != 0 {
-		return lsp.RunServerOnPort(ctx, cache.New(s.app.options), s.Port, run)
-	}
-	stream := jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout)
-	if s.Trace {
-		stream = protocol.LoggingStream(stream, out)
-	}
-	ctx, srv := lsp.NewServer(ctx, cache.New(s.app.options).NewSession(), stream)
-	return prepare(ctx, srv).Run(ctx)
-}
-
-func (s *Serve) forward() error {
-	conn, err := net.Dial("tcp", s.app.Remote)
+	closeLog, err := s.app.debug.SetLogFile(s.Logfile)
 	if err != nil {
 		return err
 	}
-	errc := make(chan error)
+	defer closeLog()
+	s.app.debug.ServerAddress = s.Address
+	s.app.debug.DebugAddress = s.Debug
+	s.app.debug.Serve(ctx)
+	s.app.debug.MonitorMemory(ctx)
 
-	go func(conn net.Conn) {
-		_, err := io.Copy(conn, os.Stdin)
-		errc <- err
-	}(conn)
-
-	go func(conn net.Conn) {
-		_, err := io.Copy(os.Stdout, conn)
-		errc <- err
-	}(conn)
-
-	return <-errc
-}
-
-// debugServe implements the debug.Instance interface.
-type debugServe struct {
-	s       *Serve
-	logfile string
-	start   time.Time
-}
-
-func (d debugServe) Logfile() string      { return d.logfile }
-func (d debugServe) StartTime() time.Time { return d.start }
-func (d debugServe) Port() int            { return d.s.Port }
-func (d debugServe) Address() string      { return d.s.Address }
-func (d debugServe) Debug() string        { return d.s.Debug }
-func (d debugServe) Workdir() string      { return d.s.app.wd }
-
-type handler struct{}
-
-type rpcStats struct {
-	method     string
-	direction  jsonrpc2.Direction
-	id         *jsonrpc2.ID
-	payload    *json.RawMessage
-	start      time.Time
-	delivering func()
-	close      func()
-}
-
-type statsKeyType int
-
-const statsKey = statsKeyType(0)
-
-func (h *handler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
-	stats := h.getStats(ctx)
-	if stats != nil {
-		stats.delivering()
-	}
-	return false
-}
-
-func (h *handler) Cancel(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID, cancelled bool) bool {
-	return false
-}
-
-func (h *handler) Request(ctx context.Context, conn *jsonrpc2.Conn, direction jsonrpc2.Direction, r *jsonrpc2.WireRequest) context.Context {
-	if r.Method == "" {
-		panic("no method in rpc stats")
-	}
-	stats := &rpcStats{
-		method:    r.Method,
-		start:     time.Now(),
-		direction: direction,
-		payload:   r.Params,
-	}
-	ctx = context.WithValue(ctx, statsKey, stats)
-	mode := telemetry.Outbound
-	if direction == jsonrpc2.Receive {
-		mode = telemetry.Inbound
-	}
-	ctx, stats.close = trace.StartSpan(ctx, r.Method,
-		telemetry.Method.Of(r.Method),
-		telemetry.RPCDirection.Of(mode),
-		telemetry.RPCID.Of(r.ID),
-	)
-	telemetry.Started.Record(ctx, 1)
-	_, stats.delivering = trace.StartSpan(ctx, "queued")
-	return ctx
-}
-
-func (h *handler) Response(ctx context.Context, conn *jsonrpc2.Conn, direction jsonrpc2.Direction, r *jsonrpc2.WireResponse) context.Context {
-	return ctx
-}
-
-func (h *handler) Done(ctx context.Context, err error) {
-	stats := h.getStats(ctx)
-	if err != nil {
-		ctx = telemetry.StatusCode.With(ctx, "ERROR")
+	var ss jsonrpc2.StreamServer
+	if s.app.Remote != "" {
+		network, addr := parseAddr(s.app.Remote)
+		ss = lsprpc.NewForwarder(network, addr, true, s.app.debug)
 	} else {
-		ctx = telemetry.StatusCode.With(ctx, "OK")
+		ss = lsprpc.NewStreamServer(cache.New(s.app.options, s.app.debug.State), true, s.app.debug)
 	}
-	elapsedTime := time.Since(stats.start)
-	latencyMillis := float64(elapsedTime) / float64(time.Millisecond)
-	telemetry.Latency.Record(ctx, latencyMillis)
-	stats.close()
-}
 
-func (h *handler) Read(ctx context.Context, bytes int64) context.Context {
-	telemetry.SentBytes.Record(ctx, bytes)
-	return ctx
-}
-
-func (h *handler) Wrote(ctx context.Context, bytes int64) context.Context {
-	telemetry.ReceivedBytes.Record(ctx, bytes)
-	return ctx
-}
-
-const eol = "\r\n\r\n\r\n"
-
-func (h *handler) Error(ctx context.Context, err error) {
-}
-
-func (h *handler) getStats(ctx context.Context) *rpcStats {
-	stats, ok := ctx.Value(statsKey).(*rpcStats)
-	if !ok || stats == nil {
-		method, ok := ctx.Value(telemetry.Method).(string)
-		if !ok {
-			method = "???"
-		}
-		stats = &rpcStats{
-			method: method,
-			close:  func() {},
-		}
+	if s.Address != "" {
+		network, addr := parseAddr(s.Address)
+		return jsonrpc2.ListenAndServe(ctx, network, addr, ss, s.IdleTimeout)
 	}
-	return stats
+	if s.Port != 0 {
+		addr := fmt.Sprintf(":%v", s.Port)
+		return jsonrpc2.ListenAndServe(ctx, "tcp", addr, ss, s.IdleTimeout)
+	}
+	stream := jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout)
+	if s.Trace {
+		stream = protocol.LoggingStream(stream, s.app.debug.LogWriter)
+	}
+	return ss.ServeStream(ctx, stream)
+}
+
+// parseAddr parses the -listen flag in to a network, and address.
+func parseAddr(listen string) (network string, address string) {
+	// Allow passing just -remote=auto, as a shorthand for using automatic remote
+	// resolution.
+	if listen == lsprpc.AutoNetwork {
+		return lsprpc.AutoNetwork, ""
+	}
+	if parts := strings.SplitN(listen, ";", 2); len(parts) == 2 {
+		return parts[0], parts[1]
+	}
+	return "tcp", listen
 }
diff --git a/internal/lsp/cmd/serve_test.go b/internal/lsp/cmd/serve_test.go
index ad3f8b2..7b3bc9a 100644
--- a/internal/lsp/cmd/serve_test.go
+++ b/internal/lsp/cmd/serve_test.go
@@ -1,86 +1,28 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
 package cmd
 
-import (
-	"context"
-	"io"
-	"regexp"
-	"testing"
-	"time"
+import "testing"
 
-	"golang.org/x/tools/internal/jsonrpc2"
-	"golang.org/x/tools/internal/lsp/protocol"
-	"golang.org/x/tools/internal/telemetry/log"
-)
-
-type fakeServer struct {
-	protocol.Server
-	client protocol.Client
-}
-
-func (s *fakeServer) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
-	// Our instrumentation should cause this message to be logged back to the LSP
-	// client.
-	log.Print(ctx, "ping")
-	return nil
-}
-
-type fakeClient struct {
-	protocol.Client
-
-	logs chan string
-}
-
-func (c *fakeClient) LogMessage(ctx context.Context, params *protocol.LogMessageParams) error {
-	c.logs <- params.Message
-	return nil
-}
-
-func TestClientLogging(t *testing.T) {
-	server := &fakeServer{}
-	client := &fakeClient{logs: make(chan string)}
-	ctx, cancel := context.WithCancel(context.Background())
-	defer cancel()
-
-	// Bind our fake client and server.
-	// sReader and sWriter read from and write to the server. cReader and cWriter
-	// read from and write to the client.
-	sReader, sWriter := io.Pipe()
-	cReader, cWriter := io.Pipe()
-	close := func() {
-		failOnErr := func(err error) {
-			if err != nil {
-				t.Fatal(err)
-			}
-		}
-		failOnErr(sReader.Close())
-		failOnErr(cReader.Close())
-		failOnErr(sWriter.Close())
-		failOnErr(cWriter.Close())
+func TestListenParsing(t *testing.T) {
+	tests := []struct {
+		input, wantNetwork, wantAddr string
+	}{
+		{"127.0.0.1:0", "tcp", "127.0.0.1:0"},
+		{"unix;/tmp/sock", "unix", "/tmp/sock"},
+		{"auto", "auto", ""},
+		{"auto;foo", "auto", "foo"},
 	}
-	defer close()
-	serverStream := jsonrpc2.NewStream(sReader, cWriter)
-	// The returned client dispatches to the client, but it is already stored
-	// in the context by NewServer, so we can ignore it.
-	serverCtx, serverConn, _ := protocol.NewServer(ctx, serverStream, server)
-	serverConn.AddHandler(&handler{})
-	clientStream := jsonrpc2.NewStream(cReader, sWriter)
-	clientCtx, clientConn, serverDispatch := protocol.NewClient(ctx, clientStream, client)
 
-	go clientConn.Run(clientCtx)
-	go serverConn.Run(serverCtx)
-	serverDispatch.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{})
-
-	select {
-	case got := <-client.logs:
-		want := "ping"
-		matched, err := regexp.MatchString(want, got)
-		if err != nil {
-			t.Fatal(err)
+	for _, test := range tests {
+		gotNetwork, gotAddr := parseAddr(test.input)
+		if gotNetwork != test.wantNetwork {
+			t.Errorf("network = %q, want %q", gotNetwork, test.wantNetwork)
 		}
-		if !matched {
-			t.Errorf("got log %q, want a log containing %q", got, want)
+		if gotAddr != test.wantAddr {
+			t.Errorf("addr = %q, want %q", gotAddr, test.wantAddr)
 		}
-	case <-time.After(1 * time.Second):
-		t.Error("timeout waiting for client log")
 	}
 }
diff --git a/internal/lsp/cmd/signature.go b/internal/lsp/cmd/signature.go
index 7cc91cd..2cec976 100644
--- a/internal/lsp/cmd/signature.go
+++ b/internal/lsp/cmd/signature.go
@@ -59,7 +59,7 @@
 
 	tdpp := protocol.TextDocumentPositionParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(from.URI()),
+			URI: protocol.URIFromSpanURI(from.URI()),
 		},
 		Position: loc.Range.Start,
 	}
diff --git a/internal/lsp/cmd/suggested_fix.go b/internal/lsp/cmd/suggested_fix.go
index 19a53dc..5e8b1fa 100644
--- a/internal/lsp/cmd/suggested_fix.go
+++ b/internal/lsp/cmd/suggested_fix.go
@@ -70,7 +70,7 @@
 
 	p := protocol.CodeActionParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 		Context: protocol.CodeActionContext{
 			Only:        []protocol.CodeActionKind{protocol.QuickFix},
@@ -87,7 +87,7 @@
 			continue
 		}
 		for _, c := range a.Edit.DocumentChanges {
-			if c.TextDocument.URI == string(uri) {
+			if fileURI(c.TextDocument.URI) == uri {
 				edits = append(edits, c.Edits...)
 			}
 		}
diff --git a/internal/lsp/cmd/symbols.go b/internal/lsp/cmd/symbols.go
index 41cc0f7..eb3aa02 100644
--- a/internal/lsp/cmd/symbols.go
+++ b/internal/lsp/cmd/symbols.go
@@ -44,7 +44,7 @@
 	from := span.Parse(args[0])
 	p := protocol.DocumentSymbolParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: string(from.URI()),
+			URI: protocol.URIFromSpanURI(from.URI()),
 		},
 	}
 
diff --git a/internal/lsp/cmd/test/check.go b/internal/lsp/cmd/test/check.go
index db277fa..f21194f 100644
--- a/internal/lsp/cmd/test/check.go
+++ b/internal/lsp/cmd/test/check.go
@@ -22,7 +22,7 @@
 		t.Skip("skipping circular diagnostics tests due to golang/go#36265")
 	}
 	fname := uri.Filename()
-	out, _ := r.RunGoplsCmd(t, "check", fname)
+	out, _ := r.runGoplsCmd(t, "check", fname)
 	// parse got into a collection of reports
 	got := map[string]struct{}{}
 	for _, l := range strings.Split(out, "\n") {
diff --git a/internal/lsp/cmd/test/cmdtest.go b/internal/lsp/cmd/test/cmdtest.go
index f0aab4b..7754610 100644
--- a/internal/lsp/cmd/test/cmdtest.go
+++ b/internal/lsp/cmd/test/cmdtest.go
@@ -9,15 +9,17 @@
 	"bytes"
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"os"
 	"path/filepath"
 	"strconv"
 	"strings"
+	"sync"
 	"testing"
 
 	"golang.org/x/tools/go/packages/packagestest"
 	"golang.org/x/tools/internal/lsp/cmd"
+	"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"
@@ -30,6 +32,7 @@
 	ctx         context.Context
 	options     func(*source.Options)
 	normalizers []normalizer
+	remote      string
 }
 
 type normalizer struct {
@@ -39,13 +42,14 @@
 	fragment string
 }
 
-func NewRunner(exporter packagestest.Exporter, data *tests.Data, ctx context.Context, options func(*source.Options)) *runner {
+func NewRunner(exporter packagestest.Exporter, data *tests.Data, ctx context.Context, remote string, options func(*source.Options)) *runner {
 	r := &runner{
 		exporter:    exporter,
 		data:        data,
 		ctx:         ctx,
 		options:     options,
 		normalizers: make([]normalizer, 0, len(data.Exported.Modules)),
+		remote:      remote,
 	}
 	// build the path normalizing patterns
 	for _, m := range data.Exported.Modules {
@@ -67,6 +71,10 @@
 	return r
 }
 
+func (r *runner) CodeLens(t *testing.T, spn span.Span, want []protocol.CodeLens) {
+	//TODO: add command line completions tests when it works
+}
+
 func (r *runner) Completion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
 	//TODO: add command line completions tests when it works
 }
@@ -95,11 +103,19 @@
 	//TODO: add command line completions tests when it works
 }
 
-func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.PrepareItem) {
-	//TODO: add command line prepare rename tests when it works
+func (r *runner) WorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{}) {
+	//TODO: add command line workspace symbol tests when it works
 }
 
-func (r *runner) RunGoplsCmd(t testing.TB, args ...string) (string, string) {
+func (r *runner) FuzzyWorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{}) {
+	//TODO: add command line workspace symbol tests when it works
+}
+
+func (r *runner) CaseSensitiveWorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{}) {
+	//TODO: add command line workspace symbol tests when it works
+}
+
+func (r *runner) runGoplsCmd(t testing.TB, args ...string) (string, string) {
 	rStdout, wStdout, err := os.Pipe()
 	if err != nil {
 		t.Fatal(err)
@@ -110,39 +126,38 @@
 		t.Fatal(err)
 	}
 	oldStderr := os.Stderr
-	defer func() {
-		os.Stdout = oldStdout
-		os.Stderr = oldStderr
-		wStdout.Close()
-		rStdout.Close()
-		wStderr.Close()
-		rStderr.Close()
+	stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
+	var wg sync.WaitGroup
+	wg.Add(2)
+	go func() {
+		io.Copy(stdout, rStdout)
+		wg.Done()
 	}()
-	os.Stdout = wStdout
-	os.Stderr = wStderr
+	go func() {
+		io.Copy(stderr, rStderr)
+		wg.Done()
+	}()
+	os.Stdout, os.Stderr = wStdout, wStderr
 	app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Exported.Config.Env, r.options)
+	remote := r.remote
 	err = tool.Run(tests.Context(t),
 		app,
-		append([]string{"-remote=internal"}, args...))
+		append([]string{fmt.Sprintf("-remote=internal@%s", remote)}, args...))
 	if err != nil {
 		fmt.Fprint(os.Stderr, err)
 	}
 	wStdout.Close()
 	wStderr.Close()
-	stdout, err := ioutil.ReadAll(rStdout)
-	if err != nil {
-		t.Fatal(err)
-	}
-	stderr, err := ioutil.ReadAll(rStderr)
-	if err != nil {
-		t.Fatal(err)
-	}
-	return string(stdout), string(stderr)
+	wg.Wait()
+	os.Stdout, os.Stderr = oldStdout, oldStderr
+	rStdout.Close()
+	rStderr.Close()
+	return stdout.String(), stderr.String()
 }
 
 // NormalizeGoplsCmd runs the gopls command and normalizes its output.
 func (r *runner) NormalizeGoplsCmd(t testing.TB, args ...string) (string, string) {
-	stdout, stderr := r.RunGoplsCmd(t, args...)
+	stdout, stderr := r.runGoplsCmd(t, args...)
 	return r.Normalize(stdout), r.Normalize(stderr)
 }
 
diff --git a/internal/lsp/cmd/test/prepare_rename.go b/internal/lsp/cmd/test/prepare_rename.go
new file mode 100644
index 0000000..3d69edd
--- /dev/null
+++ b/internal/lsp/cmd/test/prepare_rename.go
@@ -0,0 +1,45 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cmdtest
+
+import (
+	"fmt"
+	"testing"
+
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
+	"golang.org/x/tools/internal/span"
+)
+
+func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.PrepareItem) {
+	m, err := r.data.Mapper(src.URI())
+	if err != nil {
+		t.Errorf("prepare_rename failed: %v", err)
+	}
+
+	var (
+		target         = fmt.Sprintf("%v", src)
+		args           = []string{"prepare_rename", target}
+		stdOut, stdErr = r.NormalizeGoplsCmd(t, args...)
+		expect         string
+	)
+
+	if want.Text == "" {
+		if stdErr != "" {
+			t.Errorf("prepare_rename failed for %s,\nexpected:\n`%v`\ngot:\n`%v`", target, expect, stdErr)
+		}
+		return
+	}
+
+	ws, err := m.Span(protocol.Location{Range: want.Range})
+	if err != nil {
+		t.Errorf("prepare_rename failed: %v", err)
+	}
+
+	expect = r.Normalize(fmt.Sprintln(ws))
+	if expect != stdOut {
+		t.Errorf("prepare_rename failed for %s expected:\n`%s`\ngot:\n`%s`\n", target, expect, stdOut)
+	}
+}
diff --git a/internal/lsp/cmd/test/signature.go b/internal/lsp/cmd/test/signature.go
index 4e2726c..0c77da1 100644
--- a/internal/lsp/cmd/test/signature.go
+++ b/internal/lsp/cmd/test/signature.go
@@ -8,20 +8,22 @@
 	"fmt"
 	"testing"
 
-	"golang.org/x/tools/internal/lsp/source"
-
+	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/span"
 )
 
-func (r *runner) SignatureHelp(t *testing.T, spn span.Span, expectedSignature *source.SignatureInformation) {
-	goldenTag := "-signature"
-	if expectedSignature != nil {
-		goldenTag = expectedSignature.Label + goldenTag
-	}
+func (r *runner) SignatureHelp(t *testing.T, spn span.Span, want *protocol.SignatureHelp) {
 	uri := spn.URI()
 	filename := uri.Filename()
 	target := filename + fmt.Sprintf(":%v:%v", spn.Start().Line(), spn.Start().Column())
 	got, _ := r.NormalizeGoplsCmd(t, "signature", target)
+	if want == nil {
+		if got != "" {
+			t.Fatalf("want nil, but got %s", got)
+		}
+		return
+	}
+	goldenTag := want.Signatures[0].Label + "-signature"
 	expect := string(r.data.Golden(goldenTag, filename, func() ([]byte, error) {
 		return []byte(got), nil
 	}))
diff --git a/internal/lsp/cmd/test/suggested_fix.go b/internal/lsp/cmd/test/suggested_fix.go
index f88131b..1963fdb 100644
--- a/internal/lsp/cmd/test/suggested_fix.go
+++ b/internal/lsp/cmd/test/suggested_fix.go
@@ -7,6 +7,7 @@
 import (
 	"testing"
 
+	"golang.org/x/tools/internal/lsp/tests"
 	"golang.org/x/tools/internal/span"
 )
 
@@ -14,7 +15,7 @@
 	uri := spn.URI()
 	filename := uri.Filename()
 	got, _ := r.NormalizeGoplsCmd(t, "fix", "-a", filename)
-	want := string(r.data.Golden("suggestedfix", filename, func() ([]byte, error) {
+	want := string(r.data.Golden("suggestedfix_"+tests.SpanName(spn), filename, func() ([]byte, error) {
 		return []byte(got), nil
 	}))
 	if want != got {
diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go
index c7f99ce..c9f85fd 100644
--- a/internal/lsp/code_action.go
+++ b/internal/lsp/code_action.go
@@ -15,25 +15,19 @@
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/telemetry"
-	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/log"
 	errors "golang.org/x/xerrors"
 )
 
 func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.UnknownKind)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
+	uri := fh.Identity().URI
 
 	// Determine the supported actions for this file kind.
-	supportedCodeActions, ok := view.Options().SupportedCodeActions[fh.Identity().Kind]
+	supportedCodeActions, ok := snapshot.View().Options().SupportedCodeActions[fh.Identity().Kind]
 	if !ok {
 		return nil, fmt.Errorf("no supported code actions for %v file kind", fh.Identity().Kind)
 	}
@@ -56,6 +50,9 @@
 	var codeActions []protocol.CodeAction
 	switch fh.Identity().Kind {
 	case source.Mod:
+		if diagnostics := params.Context.Diagnostics; len(diagnostics) > 0 {
+			codeActions = append(codeActions, mod.SuggestedFixes(ctx, snapshot, fh, diagnostics)...)
+		}
 		if !wanted[protocol.SourceOrganizeImports] {
 			codeActions = append(codeActions, protocol.CodeAction{
 				Title: "Tidy",
@@ -67,9 +64,6 @@
 				},
 			})
 		}
-		if diagnostics := params.Context.Diagnostics; len(diagnostics) > 0 {
-			codeActions = append(codeActions, mod.SuggestedFixes(ctx, snapshot, fh, diagnostics)...)
-		}
 	case source.Go:
 		edits, editsPerFix, err := source.AllImportsFixes(ctx, snapshot, fh)
 		if err != nil {
@@ -101,6 +95,13 @@
 					}
 				}
 			}
+			actions, err := mod.SuggestedGoFixes(ctx, snapshot, fh, diagnostics)
+			if err != nil {
+				log.Error(ctx, "quick fixes failed", err, telemetry.File.Of(uri))
+			}
+			if len(actions) > 0 {
+				codeActions = append(codeActions, actions...)
+			}
 		}
 		if wanted[protocol.SourceOrganizeImports] && len(edits) > 0 {
 			codeActions = append(codeActions, protocol.CodeAction{
@@ -250,7 +251,7 @@
 			TextDocument: protocol.VersionedTextDocumentIdentifier{
 				Version: fh.Identity().Version,
 				TextDocumentIdentifier: protocol.TextDocumentIdentifier{
-					URI: protocol.NewURI(fh.Identity().URI),
+					URI: protocol.URIFromSpanURI(fh.Identity().URI),
 				},
 			},
 			Edits: edits,
diff --git a/internal/lsp/command.go b/internal/lsp/command.go
index 6e94a58..4c0ad87 100644
--- a/internal/lsp/command.go
+++ b/internal/lsp/command.go
@@ -3,9 +3,9 @@
 import (
 	"context"
 
+	"golang.org/x/tools/internal/gocommand"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 	errors "golang.org/x/xerrors"
 )
 
@@ -15,23 +15,39 @@
 		if len(params.Arguments) == 0 || len(params.Arguments) > 1 {
 			return nil, errors.Errorf("expected one file URI for call to `go mod tidy`, got %v", params.Arguments)
 		}
-		// Confirm that this action is being taken on a go.mod file.
-		uri := span.NewURI(params.Arguments[0].(string))
-		view, err := s.session.ViewOf(uri)
-		if err != nil {
+		uri := protocol.DocumentURI(params.Arguments[0].(string))
+		snapshot, _, ok, err := s.beginFileRequest(uri, source.Mod)
+		if !ok {
 			return nil, err
 		}
-		fh, err := view.Snapshot().GetFile(uri)
-		if err != nil {
-			return nil, err
-		}
-		if fh.Identity().Kind != source.Mod {
-			return nil, errors.Errorf("%s is not a mod file", uri)
-		}
 		// Run go.mod tidy on the view.
-		// TODO: This should go through the ModTidyHandle on the view.
-		// That will also allow us to move source.InvokeGo into internal/lsp/cache.
-		if _, err := source.InvokeGo(ctx, view.Folder().Filename(), view.Config(ctx).Env, "mod", "tidy"); err != nil {
+		inv := gocommand.Invocation{
+			Verb:       "mod",
+			Args:       []string{"tidy"},
+			Env:        snapshot.Config(ctx).Env,
+			WorkingDir: snapshot.View().Folder().Filename(),
+		}
+		if _, err := inv.Run(ctx); err != nil {
+			return nil, err
+		}
+	case "upgrade.dependency":
+		if len(params.Arguments) < 2 {
+			return nil, errors.Errorf("expected one file URI and one dependency for call to `go get`, got %v", params.Arguments)
+		}
+		uri := protocol.DocumentURI(params.Arguments[0].(string))
+		snapshot, _, ok, err := s.beginFileRequest(uri, source.UnknownKind)
+		if !ok {
+			return nil, err
+		}
+		dep := params.Arguments[1].(string)
+		// Run "go get" on the dependency to upgrade it to the latest version.
+		inv := gocommand.Invocation{
+			Verb:       "get",
+			Args:       []string{dep},
+			Env:        snapshot.Config(ctx).Env,
+			WorkingDir: snapshot.View().Folder().Filename(),
+		}
+		if _, err := inv.Run(ctx); err != nil {
 			return nil, err
 		}
 	}
diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go
index 4ea01b6..ea6af87 100644
--- a/internal/lsp/completion.go
+++ b/internal/lsp/completion.go
@@ -11,20 +11,13 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/log"
 	"golang.org/x/tools/internal/telemetry/tag"
 )
 
 func (s *Server) completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) {
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
-		return nil, err
-	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.UnknownKind)
+	if !ok {
 		return nil, err
 	}
 	var candidates []source.CompletionItem
@@ -52,7 +45,7 @@
 
 	// When using deep completions/fuzzy matching, report results as incomplete so
 	// client fetches updated completions after every key stroke.
-	options := view.Options()
+	options := snapshot.View().Options()
 	incompleteResults := options.DeepCompletion || options.Matcher == source.Fuzzy
 
 	items := toProtocolCompletionItems(candidates, rng, options)
diff --git a/internal/lsp/completion_test.go b/internal/lsp/completion_test.go
index cde7461..93ecb27 100644
--- a/internal/lsp/completion_test.go
+++ b/internal/lsp/completion_test.go
@@ -23,9 +23,7 @@
 		}
 
 	})
-	if !strings.Contains(string(src.URI()), "builtins") {
-		got = tests.FilterBuiltins(got)
-	}
+	got = tests.FilterBuiltins(src, got)
 	want := expected(t, test, items)
 	if diff := tests.DiffCompletionItems(want, got); diff != "" {
 		t.Errorf("%s: %s", src, diff)
@@ -51,9 +49,7 @@
 
 func (r *runner) UnimportedCompletion(t *testing.T, src span.Span, test tests.Completion, items tests.CompletionItems) {
 	got := r.callCompletion(t, src, func(opts *source.Options) {})
-	if !strings.Contains(string(src.URI()), "builtins") {
-		got = tests.FilterBuiltins(got)
-	}
+	got = tests.FilterBuiltins(src, got)
 	want := expected(t, test, items)
 	if diff := tests.CheckCompletionOrder(want, got, false); diff != "" {
 		t.Errorf("%s: %s", src, diff)
@@ -66,9 +62,7 @@
 		opts.Matcher = source.CaseInsensitive
 		opts.UnimportedCompletion = false
 	})
-	if !strings.Contains(string(src.URI()), "builtins") {
-		got = tests.FilterBuiltins(got)
-	}
+	got = tests.FilterBuiltins(src, got)
 	want := expected(t, test, items)
 	if msg := tests.DiffCompletionItems(want, got); msg != "" {
 		t.Errorf("%s: %s", src, msg)
@@ -81,9 +75,7 @@
 		opts.Matcher = source.Fuzzy
 		opts.UnimportedCompletion = false
 	})
-	if !strings.Contains(string(src.URI()), "builtins") {
-		got = tests.FilterBuiltins(got)
-	}
+	got = tests.FilterBuiltins(src, got)
 	want := expected(t, test, items)
 	if msg := tests.DiffCompletionItems(want, got); msg != "" {
 		t.Errorf("%s: %s", src, msg)
@@ -95,9 +87,7 @@
 		opts.Matcher = source.CaseSensitive
 		opts.UnimportedCompletion = false
 	})
-	if !strings.Contains(string(src.URI()), "builtins") {
-		got = tests.FilterBuiltins(got)
-	}
+	got = tests.FilterBuiltins(src, got)
 	want := expected(t, test, items)
 	if msg := tests.DiffCompletionItems(want, got); msg != "" {
 		t.Errorf("%s: %s", src, msg)
@@ -147,7 +137,7 @@
 	list, err := r.server.Completion(r.ctx, &protocol.CompletionParams{
 		TextDocumentPositionParams: protocol.TextDocumentPositionParams{
 			TextDocument: protocol.TextDocumentIdentifier{
-				URI: protocol.NewURI(src.URI()),
+				URI: protocol.URIFromSpanURI(src.URI()),
 			},
 			Position: protocol.Position{
 				Line:      float64(src.Start().Line() - 1),
diff --git a/internal/lsp/debug/info.go b/internal/lsp/debug/info.go
index 84f365f..b3cecb5 100644
--- a/internal/lsp/debug/info.go
+++ b/internal/lsp/debug/info.go
@@ -23,14 +23,14 @@
 // Version is a manually-updated mechanism for tracking versions.
 var Version = "v0.3.2"
 
-// PrintServerInfo writes HTML debug info to w for the Instance s.
-func PrintServerInfo(w io.Writer, s Instance) {
+// PrintServerInfo writes HTML debug info to w for the Instance.
+func (i *Instance) PrintServerInfo(w io.Writer) {
 	section(w, HTML, "Server Instance", func() {
-		fmt.Fprintf(w, "Start time: %v\n", s.StartTime())
-		fmt.Fprintf(w, "LogFile: %s\n", s.Logfile())
-		fmt.Fprintf(w, "Working directory: %s\n", s.Workdir())
-		fmt.Fprintf(w, "Address: %s\n", s.Address())
-		fmt.Fprintf(w, "Debug address: %s\n", s.Debug())
+		fmt.Fprintf(w, "Start time: %v\n", i.StartTime)
+		fmt.Fprintf(w, "LogFile: %s\n", i.Logfile)
+		fmt.Fprintf(w, "Working directory: %s\n", i.Workdir)
+		fmt.Fprintf(w, "Address: %s\n", i.ServerAddress)
+		fmt.Fprintf(w, "Debug address: %s\n", i.DebugAddress)
 	})
 	PrintVersionInfo(w, true, HTML)
 }
diff --git a/internal/lsp/debug/rpc.go b/internal/lsp/debug/rpc.go
index fe31427..39574c1 100644
--- a/internal/lsp/debug/rpc.go
+++ b/internal/lsp/debug/rpc.go
@@ -17,7 +17,7 @@
 	"golang.org/x/tools/internal/telemetry/metric"
 )
 
-var rpcTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
+var rpcTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
 {{define "title"}}RPC Information{{end}}
 {{define "body"}}
 	<H2>Inbound</H2>
@@ -90,11 +90,6 @@
 	Count int64
 }
 
-func (r *rpcs) StartSpan(ctx context.Context, span *telemetry.Span)  {}
-func (r *rpcs) FinishSpan(ctx context.Context, span *telemetry.Span) {}
-func (r *rpcs) Log(ctx context.Context, event telemetry.Event)       {}
-func (r *rpcs) Flush()                                               {}
-
 func (r *rpcs) Metric(ctx context.Context, data telemetry.MetricData) {
 	for i, group := range data.Groups() {
 		set := &r.Inbound
diff --git a/internal/lsp/debug/serve.go b/internal/lsp/debug/serve.go
index 40058ea..c1fb380 100644
--- a/internal/lsp/debug/serve.go
+++ b/internal/lsp/debug/serve.go
@@ -7,40 +7,176 @@
 import (
 	"bytes"
 	"context"
+	"fmt"
 	"go/token"
 	"html/template"
+	"io"
 	stdlog "log"
 	"net"
 	"net/http"
 	"net/http/pprof"
 	_ "net/http/pprof" // pull in the standard pprof handlers
+	"os"
 	"path"
+	"path/filepath"
+	"reflect"
 	"runtime"
+	rpprof "runtime/pprof"
 	"strconv"
 	"strings"
 	"sync"
 	"time"
 
+	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/span"
+	"golang.org/x/tools/internal/telemetry"
 	"golang.org/x/tools/internal/telemetry/export"
+	"golang.org/x/tools/internal/telemetry/export/ocagent"
 	"golang.org/x/tools/internal/telemetry/export/prometheus"
 	"golang.org/x/tools/internal/telemetry/log"
 	"golang.org/x/tools/internal/telemetry/tag"
 )
 
-type Instance interface {
-	Logfile() string
-	StartTime() time.Time
-	Address() string
-	Debug() string
-	Workdir() string
+// An Instance holds all debug information associated with a gopls instance.
+type Instance struct {
+	Logfile              string
+	StartTime            time.Time
+	ServerAddress        string
+	DebugAddress         string
+	ListenedDebugAddress string
+	Workdir              string
+	OCAgentConfig        string
+
+	LogWriter io.Writer
+
+	ocagent    export.Exporter
+	prometheus *prometheus.Exporter
+	rpcs       *rpcs
+	traces     *traces
+	State      *State
 }
 
+// State holds debugging information related to the server state.
+type State struct {
+	mu       sync.Mutex
+	caches   objset
+	sessions objset
+	views    objset
+	clients  objset
+	servers  objset
+}
+
+type ider interface {
+	ID() string
+}
+
+type objset struct {
+	objs []ider
+}
+
+func (s *objset) add(elem ider) {
+	s.objs = append(s.objs, elem)
+}
+
+func (s *objset) drop(elem ider) {
+	var newobjs []ider
+	for _, obj := range s.objs {
+		if obj.ID() != elem.ID() {
+			newobjs = append(newobjs, obj)
+		}
+	}
+	s.objs = newobjs
+}
+
+func (s *objset) find(id string) ider {
+	for _, e := range s.objs {
+		if e.ID() == id {
+			return e
+		}
+	}
+	return nil
+}
+
+// Caches returns the set of Cache objects currently being served.
+func (st *State) Caches() []Cache {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	caches := make([]Cache, len(st.caches.objs))
+	for i, c := range st.caches.objs {
+		caches[i] = c.(Cache)
+	}
+	return caches
+}
+
+// Sessions returns the set of Session objects currently being served.
+func (st *State) Sessions() []Session {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	sessions := make([]Session, len(st.sessions.objs))
+	for i, s := range st.sessions.objs {
+		sessions[i] = s.(Session)
+	}
+	return sessions
+}
+
+// Views returns the set of View objects currently being served.
+func (st *State) Views() []View {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	views := make([]View, len(st.views.objs))
+	for i, v := range st.views.objs {
+		views[i] = v.(View)
+	}
+	return views
+}
+
+// Clients returns the set of Clients currently being served.
+func (st *State) Clients() []Client {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	clients := make([]Client, len(st.clients.objs))
+	for i, c := range st.clients.objs {
+		clients[i] = c.(Client)
+	}
+	return clients
+}
+
+// Servers returns the set of Servers the instance is currently connected to.
+func (st *State) Servers() []Server {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	servers := make([]Server, len(st.servers.objs))
+	for i, s := range st.servers.objs {
+		servers[i] = s.(Server)
+	}
+	return servers
+}
+
+// A Client is an incoming connection from a remote client.
+type Client interface {
+	ID() string
+	Session() Session
+	DebugAddress() string
+	Logfile() string
+	ServerID() string
+}
+
+// A Server is an outgoing connection to a remote LSP server.
+type Server interface {
+	ID() string
+	DebugAddress() string
+	Logfile() string
+	ClientID() string
+}
+
+// A Cache is an in-memory cache.
 type Cache interface {
 	ID() string
 	FileSet() *token.FileSet
+	MemStats() map[reflect.Type]int
 }
 
+// A Session is an LSP serving session.
 type Session interface {
 	ID() string
 	Cache() Cache
@@ -48,6 +184,7 @@
 	File(hash string) *File
 }
 
+// A View is a root directory within a Session.
 type View interface {
 	ID() string
 	Name() string
@@ -55,6 +192,7 @@
 	Session() Session
 }
 
+// A File is is a file within a session.
 type File struct {
 	Session Session
 	URI     span.URI
@@ -63,55 +201,91 @@
 	Hash    string
 }
 
-var (
-	mu   sync.Mutex
-	data = struct {
-		Caches   []Cache
-		Sessions []Session
-		Views    []View
-	}{}
-)
-
-// AddCache adds a cache to the set being served
-func AddCache(cache Cache) {
-	mu.Lock()
-	defer mu.Unlock()
-	data.Caches = append(data.Caches, cache)
+// AddCache adds a cache to the set being served.
+func (st *State) AddCache(cache Cache) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	st.caches.add(cache)
 }
 
-// DropCache drops a cache from the set being served
-func DropCache(cache Cache) {
-	mu.Lock()
-	defer mu.Unlock()
-	//find and remove the cache
-	if i, _ := findCache(cache.ID()); i >= 0 {
-		copy(data.Caches[i:], data.Caches[i+1:])
-		data.Caches[len(data.Caches)-1] = nil
-		data.Caches = data.Caches[:len(data.Caches)-1]
-	}
+// DropCache drops a cache from the set being served.
+func (st *State) DropCache(cache Cache) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	st.caches.drop(cache)
 }
 
-func findCache(id string) (int, Cache) {
-	for i, c := range data.Caches {
-		if c.ID() == id {
-			return i, c
-		}
-	}
-	return -1, nil
+// AddSession adds a session to the set being served.
+func (st *State) AddSession(session Session) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	st.sessions.add(session)
 }
 
-func getCache(r *http.Request) interface{} {
-	mu.Lock()
-	defer mu.Unlock()
+// DropSession drops a session from the set being served.
+func (st *State) DropSession(session Session) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	st.sessions.drop(session)
+}
+
+// AddView adds a view to the set being served.
+func (st *State) AddView(view View) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	st.views.add(view)
+}
+
+// DropView drops a view from the set being served.
+func (st *State) DropView(view View) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	st.views.drop(view)
+}
+
+// AddClient adds a client to the set being served.
+func (st *State) AddClient(client Client) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	st.clients.add(client)
+}
+
+// DropClient adds a client to the set being served.
+func (st *State) DropClient(client Client) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	st.clients.drop(client)
+}
+
+// AddServer adds a server to the set being queried. In practice, there should
+// be at most one remote server.
+func (st *State) AddServer(server Server) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	st.servers.add(server)
+}
+
+// DropServer drops a server to the set being queried.
+func (st *State) DropServer(server Server) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	st.servers.drop(server)
+}
+
+func (i *Instance) getCache(r *http.Request) interface{} {
+	i.State.mu.Lock()
+	defer i.State.mu.Unlock()
 	id := path.Base(r.URL.Path)
 	result := struct {
 		Cache
 		Sessions []Session
-	}{}
-	_, result.Cache = findCache(id)
+	}{
+		Cache: i.State.caches.find(id).(Cache),
+	}
 
 	// now find all the views that belong to this session
-	for _, v := range data.Sessions {
+	for _, vd := range i.State.sessions.objs {
+		v := vd.(Session)
 		if v.Cache().ID() == id {
 			result.Sessions = append(result.Sessions, v)
 		}
@@ -119,27 +293,19 @@
 	return result
 }
 
-func findSession(id string) Session {
-	for _, c := range data.Sessions {
-		if c.ID() == id {
-			return c
-		}
-	}
-	return nil
-}
-
-func getSession(r *http.Request) interface{} {
-	mu.Lock()
-	defer mu.Unlock()
+func (i *Instance) getSession(r *http.Request) interface{} {
+	i.State.mu.Lock()
+	defer i.State.mu.Unlock()
 	id := path.Base(r.URL.Path)
 	result := struct {
 		Session
 		Views []View
 	}{
-		Session: findSession(id),
+		Session: i.State.sessions.find(id).(Session),
 	}
 	// now find all the views that belong to this session
-	for _, v := range data.Views {
+	for _, vd := range i.State.views.objs {
+		v := vd.(View)
 		if v.Session().ID() == id {
 			result.Views = append(result.Views, v)
 		}
@@ -147,37 +313,39 @@
 	return result
 }
 
-func findView(id string) View {
-	for _, c := range data.Views {
-		if c.ID() == id {
-			return c
-		}
-	}
-	return nil
-}
-
-func getView(r *http.Request) interface{} {
-	mu.Lock()
-	defer mu.Unlock()
+func (i Instance) getClient(r *http.Request) interface{} {
+	i.State.mu.Lock()
+	defer i.State.mu.Unlock()
 	id := path.Base(r.URL.Path)
-	return findView(id)
+	return i.State.clients.find(id).(Client)
 }
 
-func getFile(r *http.Request) interface{} {
-	mu.Lock()
-	defer mu.Unlock()
+func (i Instance) getServer(r *http.Request) interface{} {
+	i.State.mu.Lock()
+	defer i.State.mu.Unlock()
+	id := path.Base(r.URL.Path)
+	return i.State.servers.find(id).(Server)
+}
+
+func (i Instance) getView(r *http.Request) interface{} {
+	i.State.mu.Lock()
+	defer i.State.mu.Unlock()
+	id := path.Base(r.URL.Path)
+	return i.State.views.find(id).(View)
+}
+
+func (i *Instance) getFile(r *http.Request) interface{} {
+	i.State.mu.Lock()
+	defer i.State.mu.Unlock()
 	hash := path.Base(r.URL.Path)
 	sid := path.Base(path.Dir(r.URL.Path))
-	session := findSession(sid)
-	return session.File(hash)
+	return i.State.sessions.find(sid).(Session).File(hash)
 }
 
-func getInfo(s Instance) dataFunc {
-	return func(r *http.Request) interface{} {
-		buf := &bytes.Buffer{}
-		PrintServerInfo(buf, s)
-		return template.HTML(buf.String())
-	}
+func (i *Instance) getInfo(r *http.Request) interface{} {
+	buf := &bytes.Buffer{}
+	i.PrintServerInfo(buf)
+	return template.HTML(buf.String())
 }
 
 func getMemory(r *http.Request) interface{} {
@@ -186,75 +354,94 @@
 	return m
 }
 
-// AddSession adds a session to the set being served
-func AddSession(session Session) {
-	mu.Lock()
-	defer mu.Unlock()
-	data.Sessions = append(data.Sessions, session)
+// NewInstance creates debug instance ready for use using the supplied configuration.
+func NewInstance(workdir, agent string) *Instance {
+	i := &Instance{
+		StartTime:     time.Now(),
+		Workdir:       workdir,
+		OCAgentConfig: agent,
+	}
+	i.LogWriter = os.Stderr
+	ocConfig := ocagent.Discover()
+	//TODO: we should not need to adjust the discovered configuration
+	ocConfig.Address = i.OCAgentConfig
+	i.ocagent = ocagent.Connect(ocConfig)
+	i.prometheus = prometheus.New()
+	i.rpcs = &rpcs{}
+	i.traces = &traces{}
+	i.State = &State{}
+	export.SetExporter(i)
+	return i
 }
 
-// DropSession drops a session from the set being served
-func DropSession(session Session) {
-	mu.Lock()
-	defer mu.Unlock()
-	//find and remove the session
-}
-
-// AddView adds a view to the set being served
-func AddView(view View) {
-	mu.Lock()
-	defer mu.Unlock()
-	data.Views = append(data.Views, view)
-}
-
-// DropView drops a view from the set being served
-func DropView(view View) {
-	mu.Lock()
-	defer mu.Unlock()
-	//find and remove the view
+// SetLogFile sets the logfile for use with this instance.
+func (i *Instance) SetLogFile(logfile string) (func(), error) {
+	// TODO: probably a better solution for deferring closure to the caller would
+	// be for the debug instance to itself be closed, but this fixes the
+	// immediate bug of logs not being captured.
+	closeLog := func() {}
+	if logfile != "" {
+		if logfile == "auto" {
+			logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.log", os.Getpid()))
+		}
+		f, err := os.Create(logfile)
+		if err != nil {
+			return nil, fmt.Errorf("unable to create log file: %v", err)
+		}
+		closeLog = func() {
+			defer f.Close()
+		}
+		stdlog.SetOutput(io.MultiWriter(os.Stderr, f))
+		i.LogWriter = f
+	}
+	i.Logfile = logfile
+	return closeLog, nil
 }
 
 // Serve starts and runs a debug server in the background.
 // It also logs the port the server starts on, to allow for :0 auto assigned
 // ports.
-func Serve(ctx context.Context, addr string, instance Instance) error {
-	mu.Lock()
-	defer mu.Unlock()
-	if addr == "" {
+func (i *Instance) Serve(ctx context.Context) error {
+	if i.DebugAddress == "" {
 		return nil
 	}
-	listener, err := net.Listen("tcp", addr)
+	listener, err := net.Listen("tcp", i.DebugAddress)
 	if err != nil {
 		return err
 	}
+	i.ListenedDebugAddress = listener.Addr().String()
 
 	port := listener.Addr().(*net.TCPAddr).Port
-	if strings.HasSuffix(addr, ":0") {
+	if strings.HasSuffix(i.DebugAddress, ":0") {
 		stdlog.Printf("debug server listening on port %d", port)
 	}
 	log.Print(ctx, "Debug serving", tag.Of("Port", port))
-	prometheus := prometheus.New()
-	rpcs := &rpcs{}
-	traces := &traces{}
-	export.AddExporters(prometheus, rpcs, traces)
 	go func() {
 		mux := http.NewServeMux()
-		mux.HandleFunc("/", Render(mainTmpl, func(*http.Request) interface{} { return data }))
-		mux.HandleFunc("/debug/", Render(debugTmpl, nil))
+		mux.HandleFunc("/", render(mainTmpl, func(*http.Request) interface{} { return i }))
+		mux.HandleFunc("/debug/", render(debugTmpl, nil))
 		mux.HandleFunc("/debug/pprof/", pprof.Index)
 		mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
 		mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
 		mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
 		mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
-		mux.HandleFunc("/metrics/", prometheus.Serve)
-		mux.HandleFunc("/rpc/", Render(rpcTmpl, rpcs.getData))
-		mux.HandleFunc("/trace/", Render(traceTmpl, traces.getData))
-		mux.HandleFunc("/cache/", Render(cacheTmpl, getCache))
-		mux.HandleFunc("/session/", Render(sessionTmpl, getSession))
-		mux.HandleFunc("/view/", Render(viewTmpl, getView))
-		mux.HandleFunc("/file/", Render(fileTmpl, getFile))
-		mux.HandleFunc("/info", Render(infoTmpl, getInfo(instance)))
-		mux.HandleFunc("/memory", Render(memoryTmpl, getMemory))
+		if i.prometheus != nil {
+			mux.HandleFunc("/metrics/", i.prometheus.Serve)
+		}
+		if i.rpcs != nil {
+			mux.HandleFunc("/rpc/", render(rpcTmpl, i.rpcs.getData))
+		}
+		if i.traces != nil {
+			mux.HandleFunc("/trace/", render(traceTmpl, i.traces.getData))
+		}
+		mux.HandleFunc("/cache/", render(cacheTmpl, i.getCache))
+		mux.HandleFunc("/session/", render(sessionTmpl, i.getSession))
+		mux.HandleFunc("/view/", render(viewTmpl, i.getView))
+		mux.HandleFunc("/client/", render(clientTmpl, i.getClient))
+		mux.HandleFunc("/server/", render(serverTmpl, i.getServer))
+		mux.HandleFunc("/file/", render(fileTmpl, i.getFile))
+		mux.HandleFunc("/info", render(infoTmpl, i.getInfo))
+		mux.HandleFunc("/memory", render(memoryTmpl, getMemory))
 		if err := http.Serve(listener, mux); err != nil {
 			log.Error(ctx, "Debug server failed", err)
 			return
@@ -264,9 +451,104 @@
 	return nil
 }
 
+// MonitorMemory starts recording memory statistics each second.
+func (i *Instance) MonitorMemory(ctx context.Context) {
+	tick := time.NewTicker(time.Second)
+	nextThresholdGiB := uint64(1)
+	go func() {
+		for {
+			<-tick.C
+			var mem runtime.MemStats
+			runtime.ReadMemStats(&mem)
+			if mem.HeapAlloc < nextThresholdGiB*1<<30 {
+				continue
+			}
+			i.writeMemoryDebug(nextThresholdGiB)
+			log.Print(ctx, fmt.Sprintf("Wrote memory usage debug info to %v", os.TempDir()))
+			nextThresholdGiB++
+		}
+	}()
+}
+
+func (i *Instance) writeMemoryDebug(threshold uint64) error {
+	fname := func(t string) string {
+		return fmt.Sprintf("gopls.%d-%dGiB-%s", os.Getpid(), threshold, t)
+	}
+
+	f, err := os.Create(filepath.Join(os.TempDir(), fname("heap.pb.gz")))
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	if err := rpprof.Lookup("heap").WriteTo(f, 0); err != nil {
+		return err
+	}
+
+	f, err = os.Create(filepath.Join(os.TempDir(), fname("goroutines.txt")))
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	if err := rpprof.Lookup("goroutine").WriteTo(f, 1); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (i *Instance) StartSpan(ctx context.Context, spn *telemetry.Span) {
+	if i.ocagent != nil {
+		i.ocagent.StartSpan(ctx, spn)
+	}
+	if i.traces != nil {
+		i.traces.StartSpan(ctx, spn)
+	}
+}
+
+func (i *Instance) FinishSpan(ctx context.Context, spn *telemetry.Span) {
+	if i.ocagent != nil {
+		i.ocagent.FinishSpan(ctx, spn)
+	}
+	if i.traces != nil {
+		i.traces.FinishSpan(ctx, spn)
+	}
+}
+
+//TODO: remove this hack
+// capture stderr at startup because it gets modified in a way that this
+// logger should not respect
+var stderr = os.Stderr
+
+func (i *Instance) Log(ctx context.Context, event telemetry.Event) {
+	if event.Error != nil {
+		fmt.Fprintf(stderr, "%v\n", event)
+	}
+	protocol.LogEvent(ctx, event)
+	if i.ocagent != nil {
+		i.ocagent.Log(ctx, event)
+	}
+}
+
+func (i *Instance) Metric(ctx context.Context, data telemetry.MetricData) {
+	if i.ocagent != nil {
+		i.ocagent.Metric(ctx, data)
+	}
+	if i.traces != nil {
+		i.prometheus.Metric(ctx, data)
+	}
+	if i.rpcs != nil {
+		i.rpcs.Metric(ctx, data)
+	}
+}
+
+func (i *Instance) Flush() {
+	if i.ocagent != nil {
+		i.ocagent.Flush()
+	}
+}
+
 type dataFunc func(*http.Request) interface{}
 
-func Render(tmpl *template.Template, fun dataFunc) func(http.ResponseWriter, *http.Request) {
+func render(tmpl *template.Template, fun dataFunc) func(http.ResponseWriter, *http.Request) {
 	return func(w http.ResponseWriter, r *http.Request) {
 		var data interface{}
 		if fun != nil {
@@ -274,6 +556,7 @@
 		}
 		if err := tmpl.Execute(w, data); err != nil {
 			log.Error(context.Background(), "", err)
+			http.Error(w, err.Error(), http.StatusInternalServerError)
 		}
 	}
 }
@@ -294,7 +577,7 @@
 	return commas(strconv.FormatUint(uint64(v), 10))
 }
 
-var BaseTemplate = template.Must(template.New("").Parse(`
+var baseTemplate = template.Must(template.New("").Parse(`
 <html>
 <head>
 <title>{{template "title" .}}</title>
@@ -329,34 +612,60 @@
 </html>
 
 {{define "cachelink"}}<a href="/cache/{{.}}">Cache {{.}}</a>{{end}}
+{{define "clientlink"}}<a href="/client/{{.}}">Client {{.}}</a>{{end}}
+{{define "serverlink"}}<a href="/server/{{.}}">Server {{.}}</a>{{end}}
 {{define "sessionlink"}}<a href="/session/{{.}}">Session {{.}}</a>{{end}}
 {{define "viewlink"}}<a href="/view/{{.}}">View {{.}}</a>{{end}}
 {{define "filelink"}}<a href="/file/{{.Session.ID}}/{{.Hash}}">{{.URI}}</a>{{end}}
 `)).Funcs(template.FuncMap{
 	"fuint64": fuint64,
 	"fuint32": fuint32,
+	"localAddress": func(s string) string {
+		// Try to translate loopback addresses to localhost, both for cosmetics and
+		// because unspecified ipv6 addresses can break links on Windows.
+		//
+		// TODO(rfindley): In the future, it would be better not to assume the
+		// server is running on localhost, and instead construct this address using
+		// the remote host.
+		host, port, err := net.SplitHostPort(s)
+		if err != nil {
+			return s
+		}
+		ip := net.ParseIP(host)
+		if ip == nil {
+			return s
+		}
+		if ip.IsLoopback() || ip.IsUnspecified() {
+			return "localhost:" + port
+		}
+		return s
+	},
 })
 
-var mainTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
+var mainTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
 {{define "title"}}GoPls server information{{end}}
 {{define "body"}}
 <h2>Caches</h2>
-<ul>{{range .Caches}}<li>{{template "cachelink" .ID}}</li>{{end}}</ul>
+<ul>{{range .State.Caches}}<li>{{template "cachelink" .ID}}</li>{{end}}</ul>
 <h2>Sessions</h2>
-<ul>{{range .Sessions}}<li>{{template "sessionlink" .ID}} from {{template "cachelink" .Cache.ID}}</li>{{end}}</ul>
+<ul>{{range .State.Sessions}}<li>{{template "sessionlink" .ID}} from {{template "cachelink" .Cache.ID}}</li>{{end}}</ul>
 <h2>Views</h2>
-<ul>{{range .Views}}<li>{{.Name}} is {{template "viewlink" .ID}} from {{template "sessionlink" .Session.ID}} in {{.Folder}}</li>{{end}}</ul>
+<ul>{{range .State.Views}}<li>{{.Name}} is {{template "viewlink" .ID}} from {{template "sessionlink" .Session.ID}} in {{.Folder}}</li>{{end}}</ul>
+<h2>Clients</h2>
+<ul>{{range .State.Clients}}<li>{{template "clientlink" .ID}}</li>{{end}}</ul>
+<h2>Servers</h2>
+<ul>{{range .State.Servers}}<li>{{template "serverlink" .ID}}</li>{{end}}</ul>
 {{end}}
 `))
 
-var infoTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
+var infoTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
 {{define "title"}}GoPls version information{{end}}
 {{define "body"}}
 {{.}}
 {{end}}
 `))
 
-var memoryTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
+var memoryTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
 {{define "title"}}GoPls memory usage{{end}}
 {{define "head"}}<meta http-equiv="refresh" content="5">{{end}}
 {{define "body"}}
@@ -386,22 +695,43 @@
 {{end}}
 `))
 
-var debugTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
+var debugTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
 {{define "title"}}GoPls Debug pages{{end}}
 {{define "body"}}
 <a href="/debug/pprof">Profiling</a>
 {{end}}
 `))
 
-var cacheTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
+var cacheTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
 {{define "title"}}Cache {{.ID}}{{end}}
 {{define "body"}}
 <h2>Sessions</h2>
 <ul>{{range .Sessions}}<li>{{template "sessionlink" .ID}}</li>{{end}}</ul>
+<h2>memoize.Store entries</h2>
+<ul>{{range $k,$v := .MemStats}}<li>{{$k}} - {{$v}}</li>{{end}}</ul>
 {{end}}
 `))
 
-var sessionTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
+var clientTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
+{{define "title"}}Client {{.ID}}{{end}}
+{{define "body"}}
+Using session: <b>{{template "sessionlink" .Session.ID}}</b><br>
+Debug this client at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>
+Logfile: {{.Logfile}}<br>
+Gopls Path: {{.GoplsPath}}<br>
+{{end}}
+`))
+
+var serverTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
+{{define "title"}}Server {{.ID}}{{end}}
+{{define "body"}}
+Debug this server at: <a href="http://{{localAddress .DebugAddress}}">{{localAddress .DebugAddress}}</a><br>
+Logfile: {{.Logfile}}<br>
+Gopls Path: {{.GoplsPath}}<br>
+{{end}}
+`))
+
+var sessionTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
 {{define "title"}}Session {{.ID}}{{end}}
 {{define "body"}}
 From: <b>{{template "cachelink" .Cache.ID}}</b><br>
@@ -412,7 +742,7 @@
 {{end}}
 `))
 
-var viewTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
+var viewTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
 {{define "title"}}View {{.ID}}{{end}}
 {{define "body"}}
 Name: <b>{{.Name}}</b><br>
@@ -423,7 +753,7 @@
 {{end}}
 `))
 
-var fileTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
+var fileTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
 {{define "title"}}File {{.Hash}}{{end}}
 {{define "body"}}
 From: <b>{{template "sessionlink" .Session.ID}}</b><br>
diff --git a/internal/lsp/debug/serve_test.go b/internal/lsp/debug/serve_test.go
new file mode 100644
index 0000000..ff981e9
--- /dev/null
+++ b/internal/lsp/debug/serve_test.go
@@ -0,0 +1,54 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package debug
+
+import "testing"
+
+type fakeCache struct {
+	Cache
+
+	id string
+}
+
+func (c fakeCache) ID() string {
+	return c.id
+}
+
+func TestState(t *testing.T) {
+	c1 := fakeCache{id: "1"}
+	c2 := fakeCache{id: "2"}
+	c3 := fakeCache{id: "3"}
+
+	var s State
+	s.AddCache(c1)
+	s.AddCache(c2)
+	s.AddCache(c3)
+
+	compareCaches := func(desc string, want []fakeCache) {
+		t.Run(desc, func(t *testing.T) {
+			caches := s.Caches()
+			if gotLen, wantLen := len(caches), len(want); gotLen != wantLen {
+				t.Fatalf("len(Caches) = %d, want %d", gotLen, wantLen)
+			}
+			for i, got := range caches {
+				if got != want[i] {
+					t.Errorf("Caches[%d] = %v, want %v", i, got, want[i])
+				}
+			}
+		})
+	}
+
+	compareCaches("initial load", []fakeCache{c1, c2, c3})
+	s.DropCache(c2)
+	compareCaches("dropped cache 2", []fakeCache{c1, c3})
+	s.DropCache(c2)
+	compareCaches("duplicate drop", []fakeCache{c1, c3})
+	s.AddCache(c2)
+	compareCaches("re-add cache 2", []fakeCache{c1, c3, c2})
+	s.DropCache(c1)
+	s.DropCache(c2)
+	s.DropCache(c3)
+	compareCaches("drop all", []fakeCache{})
+}
diff --git a/internal/lsp/debug/trace.go b/internal/lsp/debug/trace.go
index 4fd3de4..f71b5a6 100644
--- a/internal/lsp/debug/trace.go
+++ b/internal/lsp/debug/trace.go
@@ -17,7 +17,7 @@
 	"golang.org/x/tools/internal/telemetry"
 )
 
-var traceTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
+var traceTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
 {{define "title"}}Trace Information{{end}}
 {{define "body"}}
 	{{range .Traces}}<a href="/trace/{{.Name}}">{{.Name}}</a> last: {{.Last.Duration}}, longest: {{.Longest.Duration}}<br>{{end}}
@@ -130,12 +130,6 @@
 	}
 }
 
-func (t *traces) Log(ctx context.Context, event telemetry.Event) {}
-
-func (t *traces) Metric(ctx context.Context, data telemetry.MetricData) {}
-
-func (t *traces) Flush() {}
-
 func (t *traces) getData(req *http.Request) interface{} {
 	if len(t.sets) == 0 {
 		return nil
diff --git a/internal/lsp/definition.go b/internal/lsp/definition.go
index e8b8b54..440a481 100644
--- a/internal/lsp/definition.go
+++ b/internal/lsp/definition.go
@@ -9,24 +9,14 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 func (s *Server) definition(ctx context.Context, params *protocol.DefinitionParams) ([]protocol.Location, error) {
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
-	ident, err := source.Identifier(ctx, snapshot, fh, params.Position, source.WidestPackageHandle)
+	ident, err := source.Identifier(ctx, snapshot, fh, params.Position)
 	if err != nil {
 		return nil, err
 	}
@@ -36,27 +26,18 @@
 	}
 	return []protocol.Location{
 		{
-			URI:   protocol.NewURI(ident.Declaration.URI()),
+			URI:   protocol.URIFromSpanURI(ident.Declaration.URI()),
 			Range: decRange,
 		},
 	}, nil
 }
 
 func (s *Server) typeDefinition(ctx context.Context, params *protocol.TypeDefinitionParams) ([]protocol.Location, error) {
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
-	ident, err := source.Identifier(ctx, snapshot, fh, params.Position, source.WidestPackageHandle)
+	ident, err := source.Identifier(ctx, snapshot, fh, params.Position)
 	if err != nil {
 		return nil, err
 	}
@@ -66,7 +47,7 @@
 	}
 	return []protocol.Location{
 		{
-			URI:   protocol.NewURI(ident.Type.URI()),
+			URI:   protocol.URIFromSpanURI(ident.Type.URI()),
 			Range: identRange,
 		},
 	}, nil
diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go
index 2cfa8ea..0319c08 100644
--- a/internal/lsp/diagnostics.go
+++ b/internal/lsp/diagnostics.go
@@ -6,7 +6,9 @@
 
 import (
 	"context"
+	"fmt"
 	"strings"
+	"sync"
 
 	"golang.org/x/tools/internal/lsp/mod"
 	"golang.org/x/tools/internal/lsp/protocol"
@@ -17,80 +19,132 @@
 	"golang.org/x/tools/internal/xcontext"
 )
 
+type diagnosticKey struct {
+	id           source.FileIdentity
+	withAnalysis bool
+}
+
 func (s *Server) diagnoseDetached(snapshot source.Snapshot) {
 	ctx := snapshot.View().BackgroundContext()
 	ctx = xcontext.Detach(ctx)
 
-	s.diagnose(ctx, snapshot)
+	reports := s.diagnose(ctx, snapshot, false)
+	s.publishReports(ctx, snapshot, reports)
 }
 
 func (s *Server) diagnoseSnapshot(snapshot source.Snapshot) {
 	ctx := snapshot.View().BackgroundContext()
 
-	s.diagnose(ctx, snapshot)
+	reports := s.diagnose(ctx, snapshot, false)
+	s.publishReports(ctx, snapshot, reports)
 }
 
 // diagnose is a helper function for running diagnostics with a given context.
 // Do not call it directly.
-func (s *Server) diagnose(ctx context.Context, snapshot source.Snapshot) {
+func (s *Server) diagnose(ctx context.Context, snapshot source.Snapshot, alwaysAnalyze bool) map[diagnosticKey][]source.Diagnostic {
 	ctx, done := trace.StartSpan(ctx, "lsp:background-worker")
 	defer done()
 
-	// Diagnose all of the packages in the workspace.
-	go func() {
-		wsPackages, err := snapshot.WorkspacePackages(ctx)
-		if ctx.Err() != nil {
-			return
-		}
-		if err != nil {
-			log.Error(ctx, "diagnose: no workspace packages", err, telemetry.Snapshot.Of(snapshot.ID()), telemetry.Directory.Of(snapshot.View().Folder))
-			return
-		}
-		for _, ph := range wsPackages {
-			go func(ph source.PackageHandle) {
-				// Only run analyses for packages with open files.
-				var withAnalyses bool
-				for _, fh := range ph.CompiledGoFiles() {
-					if s.session.IsOpen(fh.File().Identity().URI) {
-						withAnalyses = true
-					}
-				}
-				reports, warn, err := source.Diagnostics(ctx, snapshot, ph, withAnalyses)
-				// Check if might want to warn the user about their build configuration.
-				if warn && !snapshot.View().ValidBuildConfiguration() {
-					s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
-						Type: protocol.Warning,
-						// TODO(rstambler): We should really be able to point to a link on the website.
-						Message: `You are neither in a module nor in your GOPATH. Please see https://github.com/golang/go/wiki/Modules for information on how to set up your Go project.`,
-					})
-				}
-				if ctx.Err() != nil {
-					return
-				}
-				if err != nil {
-					log.Error(ctx, "diagnose: could not generate diagnostics for package", err, telemetry.Snapshot.Of(snapshot.ID()), telemetry.Package.Of(ph.ID()))
-					return
-				}
-				s.publishReports(ctx, snapshot, reports, withAnalyses)
-			}(ph)
-		}
-	}()
+	// Wait for a free diagnostics slot.
+	select {
+	case <-ctx.Done():
+		return nil
+	case s.diagnosticsSema <- struct{}{}:
+	}
+	defer func() { <-s.diagnosticsSema }()
+
+	allReports := make(map[diagnosticKey][]source.Diagnostic)
+	var reportsMu sync.Mutex
+	var wg sync.WaitGroup
 
 	// Diagnose the go.mod file.
-	go func() {
-		reports, err := mod.Diagnostics(ctx, snapshot)
-		if ctx.Err() != nil {
-			return
+	reports, missingModules, err := mod.Diagnostics(ctx, snapshot)
+	if ctx.Err() != nil {
+		return nil
+	}
+	if err != nil {
+		log.Error(ctx, "diagnose: could not generate diagnostics for go.mod file", err)
+	}
+	// Ensure that the reports returned from mod.Diagnostics are only related to the
+	// go.mod file for the module.
+	if len(reports) > 1 {
+		panic("unexpected reports from mod.Diagnostics")
+	}
+	modURI, _ := snapshot.View().ModFiles()
+	for id, diags := range reports {
+		if id.URI != modURI {
+			panic("unexpected reports from mod.Diagnostics")
 		}
-		if err != nil {
-			log.Error(ctx, "diagnose: could not generate diagnostics for go.mod file", err)
-			return
+		key := diagnosticKey{
+			id: id,
 		}
-		s.publishReports(ctx, snapshot, reports, false)
-	}()
+		allReports[key] = diags
+	}
+
+	// Diagnose all of the packages in the workspace.
+	wsPackages, err := snapshot.WorkspacePackages(ctx)
+	if ctx.Err() != nil {
+		return nil
+	}
+	if err != nil {
+		// If we encounter a genuine error when getting workspace packages,
+		// notify the user.
+		s.showedInitialErrorMu.Lock()
+		if !s.showedInitialError {
+			err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
+				Type:    protocol.Error,
+				Message: fmt.Sprintf("Your workspace is misconfigured: %s. Please see https://github.com/golang/tools/blob/master/gopls/doc/troubleshooting.md for more information or file an issue (https://github.com/golang/go/issues/new) if you believe this is a mistake.", err.Error()),
+			})
+			s.showedInitialError = err == nil
+		}
+		s.showedInitialErrorMu.Unlock()
+
+		log.Error(ctx, "diagnose: no workspace packages", err, telemetry.Snapshot.Of(snapshot.ID()), telemetry.Directory.Of(snapshot.View().Folder))
+		return nil
+	}
+	for _, ph := range wsPackages {
+		wg.Add(1)
+		go func(ph source.PackageHandle) {
+			defer wg.Done()
+			// Only run analyses for packages with open files.
+			withAnalyses := alwaysAnalyze
+			for _, fh := range ph.CompiledGoFiles() {
+				if snapshot.IsOpen(fh.File().Identity().URI) {
+					withAnalyses = true
+				}
+			}
+			reports, warn, err := source.Diagnostics(ctx, snapshot, ph, missingModules, withAnalyses)
+			// Check if might want to warn the user about their build configuration.
+			if warn && !snapshot.View().ValidBuildConfiguration() {
+				s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
+					Type: protocol.Warning,
+					// TODO(rstambler): We should really be able to point to a link on the website.
+					Message: `You are neither in a module nor in your GOPATH. Please see https://github.com/golang/go/wiki/Modules for information on how to set up your Go project.`,
+				})
+			}
+			if ctx.Err() != nil {
+				return
+			}
+			if err != nil {
+				log.Error(ctx, "diagnose: could not generate diagnostics for package", err, telemetry.Snapshot.Of(snapshot.ID()), telemetry.Package.Of(ph.ID()))
+				return
+			}
+			reportsMu.Lock()
+			for id, diags := range reports {
+				key := diagnosticKey{
+					id:           id,
+					withAnalysis: withAnalyses,
+				}
+				allReports[key] = diags
+			}
+			reportsMu.Unlock()
+		}(ph)
+	}
+	wg.Wait()
+	return allReports
 }
 
-func (s *Server) publishReports(ctx context.Context, snapshot source.Snapshot, reports map[source.FileIdentity][]source.Diagnostic, withAnalysis bool) {
+func (s *Server) publishReports(ctx context.Context, snapshot source.Snapshot, reports map[diagnosticKey][]source.Diagnostic) {
 	// Check for context cancellation before publishing diagnostics.
 	if ctx.Err() != nil {
 		return
@@ -99,7 +153,7 @@
 	s.deliveredMu.Lock()
 	defer s.deliveredMu.Unlock()
 
-	for fileID, diagnostics := range reports {
+	for key, diagnostics := range reports {
 		// Don't deliver diagnostics if the context has already been canceled.
 		if ctx.Err() != nil {
 			break
@@ -108,15 +162,15 @@
 		// Pre-sort diagnostics to avoid extra work when we compare them.
 		source.SortDiagnostics(diagnostics)
 		toSend := sentDiagnostics{
-			version:      fileID.Version,
-			identifier:   fileID.Identifier,
+			version:      key.id.Version,
+			identifier:   key.id.Identifier,
 			sorted:       diagnostics,
-			withAnalysis: withAnalysis,
+			withAnalysis: key.withAnalysis,
 			snapshotID:   snapshot.ID(),
 		}
 
 		// We use the zero values if this is an unknown file.
-		delivered := s.delivered[fileID.URI]
+		delivered := s.delivered[key.id.URI]
 
 		// Snapshot IDs are always increasing, so we use them instead of file
 		// versions to create the correct order for diagnostics.
@@ -131,7 +185,7 @@
 		// Check if we should reuse the cached diagnostics.
 		if equalDiagnostics(delivered.sorted, diagnostics) {
 			// Make sure to update the delivered map.
-			s.delivered[fileID.URI] = toSend
+			s.delivered[key.id.URI] = toSend
 			continue
 		}
 
@@ -145,8 +199,8 @@
 
 		if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
 			Diagnostics: toProtocolDiagnostics(diagnostics),
-			URI:         protocol.NewURI(fileID.URI),
-			Version:     fileID.Version,
+			URI:         protocol.URIFromSpanURI(key.id.URI),
+			Version:     key.id.Version,
 		}); err != nil {
 			if ctx.Err() == nil {
 				log.Error(ctx, "publishReports: failed to deliver diagnostic", err, telemetry.File)
@@ -154,7 +208,7 @@
 			continue
 		}
 		// Update the delivered map.
-		s.delivered[fileID.URI] = toSend
+		s.delivered[key.id.URI] = toSend
 	}
 }
 
@@ -179,7 +233,7 @@
 		for _, rel := range diag.Related {
 			related = append(related, protocol.DiagnosticRelatedInformation{
 				Location: protocol.Location{
-					URI:   protocol.NewURI(rel.URI),
+					URI:   protocol.URIFromSpanURI(rel.URI),
 					Range: rel.Range,
 				},
 				Message: rel.Message,
diff --git a/internal/lsp/diff/difftest/difftest.go b/internal/lsp/diff/difftest/difftest.go
index 297515f..513a925 100644
--- a/internal/lsp/diff/difftest/difftest.go
+++ b/internal/lsp/diff/difftest/difftest.go
@@ -222,7 +222,7 @@
 	for _, test := range TestCases {
 		t.Run(test.Name, func(t *testing.T) {
 			t.Helper()
-			edits := compute(span.FileURI("/"+test.Name), test.In, test.Out)
+			edits := compute(span.URIFromPath("/"+test.Name), test.In, test.Out)
 			got := diff.ApplyEdits(test.In, edits)
 			unified := fmt.Sprint(diff.ToUnified(FileA, FileB, test.In, edits))
 			if got != test.Out {
diff --git a/internal/lsp/fake/client.go b/internal/lsp/fake/client.go
new file mode 100644
index 0000000..b4ff1f8
--- /dev/null
+++ b/internal/lsp/fake/client.go
@@ -0,0 +1,108 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fake
+
+import (
+	"context"
+
+	"golang.org/x/tools/internal/lsp/protocol"
+)
+
+// Client is an adapter that converts a *Client into an LSP Client.
+type Client struct {
+	*Editor
+
+	// Hooks for testing. Add additional hooks here as needed for testing.
+	onLogMessage  func(context.Context, *protocol.LogMessageParams) error
+	onDiagnostics func(context.Context, *protocol.PublishDiagnosticsParams) error
+}
+
+// OnLogMessage sets the hook to run when the editor receives a log message.
+func (c *Client) OnLogMessage(hook func(context.Context, *protocol.LogMessageParams) error) {
+	c.mu.Lock()
+	c.onLogMessage = hook
+	c.mu.Unlock()
+}
+
+// OnDiagnostics sets the hook to run when the editor receives diagnostics
+// published from the language server.
+func (c *Client) OnDiagnostics(hook func(context.Context, *protocol.PublishDiagnosticsParams) error) {
+	c.mu.Lock()
+	c.onDiagnostics = hook
+	c.mu.Unlock()
+}
+
+func (c *Client) ShowMessage(ctx context.Context, params *protocol.ShowMessageParams) error {
+	c.mu.Lock()
+	c.lastMessage = params
+	c.mu.Unlock()
+	return nil
+}
+
+func (c *Client) ShowMessageRequest(ctx context.Context, params *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) {
+	return nil, nil
+}
+
+func (c *Client) LogMessage(ctx context.Context, params *protocol.LogMessageParams) error {
+	c.mu.Lock()
+	c.logs = append(c.logs, params)
+	onLogMessage := c.onLogMessage
+	c.mu.Unlock()
+	if onLogMessage != nil {
+		return onLogMessage(ctx, params)
+	}
+	return nil
+}
+
+func (c *Client) Event(ctx context.Context, event *interface{}) error {
+	c.mu.Lock()
+	c.events = append(c.events, event)
+	c.mu.Unlock()
+	return nil
+}
+
+func (c *Client) PublishDiagnostics(ctx context.Context, params *protocol.PublishDiagnosticsParams) error {
+	c.mu.Lock()
+	c.diagnostics = params
+	onPublishDiagnostics := c.onDiagnostics
+	c.mu.Unlock()
+	if onPublishDiagnostics != nil {
+		return onPublishDiagnostics(ctx, params)
+	}
+	return nil
+}
+
+func (c *Client) WorkspaceFolders(context.Context) ([]protocol.WorkspaceFolder, error) {
+	return []protocol.WorkspaceFolder{}, nil
+}
+
+func (c *Client) Configuration(context.Context, *protocol.ParamConfiguration) ([]interface{}, error) {
+	return []interface{}{c.configuration()}, nil
+}
+
+func (c *Client) RegisterCapability(context.Context, *protocol.RegistrationParams) error {
+	return nil
+}
+
+func (c *Client) UnregisterCapability(context.Context, *protocol.UnregistrationParams) error {
+	return nil
+}
+
+// ApplyEdit applies edits sent from the server. Note that as of writing gopls
+// doesn't use this feature, so it is untested.
+func (c *Client) ApplyEdit(ctx context.Context, params *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResponse, error) {
+	if len(params.Edit.Changes) != 0 {
+		return &protocol.ApplyWorkspaceEditResponse{FailureReason: "Edit.Changes is unsupported"}, nil
+	}
+	for _, change := range params.Edit.DocumentChanges {
+		path := c.ws.URIToPath(change.TextDocument.URI)
+		var edits []Edit
+		for _, lspEdit := range change.Edits {
+			edits = append(edits, fromProtocolTextEdit(lspEdit))
+		}
+		c.EditBuffer(ctx, path, edits)
+	}
+	return &protocol.ApplyWorkspaceEditResponse{Applied: true}, nil
+}
diff --git a/internal/lsp/fake/doc.go b/internal/lsp/fake/doc.go
new file mode 100644
index 0000000..69e4a49
--- /dev/null
+++ b/internal/lsp/fake/doc.go
@@ -0,0 +1,19 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package fake provides fake implementations of a text editor, LSP client
+// plugin, and workspace for use in tests.
+//
+// The Editor type provides a high level API for text editor operations
+// (open/modify/save/close a buffer, jump to definition, etc.), and the Client
+// type exposes an LSP client for the editor that can be connected to a
+// language server. By default, the Editor and Client should be compliant with
+// the LSP spec: their intended use is to verify server compliance with the
+// spec in a variety of environment. Possible future enhancements of these
+// types may allow them to misbehave in configurable ways, but that is not
+// their primary use.
+//
+// The Workspace type provides a facility for executing tests in a clean
+// workspace and GOPATH.
+package fake
diff --git a/internal/lsp/fake/edit.go b/internal/lsp/fake/edit.go
new file mode 100644
index 0000000..1eec597
--- /dev/null
+++ b/internal/lsp/fake/edit.go
@@ -0,0 +1,99 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fake
+
+import (
+	"fmt"
+	"strings"
+
+	"golang.org/x/tools/internal/lsp/protocol"
+)
+
+// Pos represents a 0-indexed position in a text buffer.
+type Pos struct {
+	Line, Column int
+}
+
+func (p Pos) toProtocolPosition() protocol.Position {
+	return protocol.Position{
+		Line:      float64(p.Line),
+		Character: float64(p.Column),
+	}
+}
+
+func fromProtocolPosition(pos protocol.Position) Pos {
+	return Pos{
+		Line:   int(pos.Line),
+		Column: int(pos.Character),
+	}
+}
+
+// Edit represents a single (contiguous) buffer edit.
+type Edit struct {
+	Start, End Pos
+	Text       string
+}
+
+// NewEdit creates an edit replacing all content between
+// (startLine, startColumn) and (endLine, endColumn) with text.
+func NewEdit(startLine, startColumn, endLine, endColumn int, text string) Edit {
+	return Edit{
+		Start: Pos{Line: startLine, Column: startColumn},
+		End:   Pos{Line: endLine, Column: endColumn},
+		Text:  text,
+	}
+}
+
+func (e Edit) toProtocolChangeEvent() protocol.TextDocumentContentChangeEvent {
+	return protocol.TextDocumentContentChangeEvent{
+		Range: &protocol.Range{
+			Start: e.Start.toProtocolPosition(),
+			End:   e.End.toProtocolPosition(),
+		},
+		Text: e.Text,
+	}
+}
+
+func fromProtocolTextEdit(textEdit protocol.TextEdit) Edit {
+	return Edit{
+		Start: fromProtocolPosition(textEdit.Range.Start),
+		End:   fromProtocolPosition(textEdit.Range.End),
+		Text:  textEdit.NewText,
+	}
+}
+
+// inText reports whether p is a valid position in the text buffer.
+func inText(p Pos, content []string) bool {
+	if p.Line < 0 || p.Line >= len(content) {
+		return false
+	}
+	// Note the strict right bound: the column indexes character _separators_,
+	// not characters.
+	if p.Column < 0 || p.Column > len(content[p.Line]) {
+		return false
+	}
+	return true
+}
+
+// editContent implements a simplistic, inefficient algorithm for applying text
+// edits to our buffer representation. It returns an error if the edit is
+// invalid for the current content.
+func editContent(content []string, edit Edit) ([]string, error) {
+	if edit.End.Line < edit.Start.Line || (edit.End.Line == edit.Start.Line && edit.End.Column < edit.Start.Column) {
+		return nil, fmt.Errorf("invalid edit: end %v before start %v", edit.End, edit.Start)
+	}
+	if !inText(edit.Start, content) {
+		return nil, fmt.Errorf("start position %v is out of bounds", edit.Start)
+	}
+	if !inText(edit.End, content) {
+		return nil, fmt.Errorf("end position %v is out of bounds", edit.End)
+	}
+	// Splice the edit text in between the first and last lines of the edit.
+	prefix := string([]rune(content[edit.Start.Line])[:edit.Start.Column])
+	suffix := string([]rune(content[edit.End.Line])[edit.End.Column:])
+	newLines := strings.Split(prefix+edit.Text+suffix, "\n")
+	newContent := append(content[:edit.Start.Line], newLines...)
+	return append(newContent, content[edit.End.Line+1:]...), nil
+}
diff --git a/internal/lsp/fake/edit_test.go b/internal/lsp/fake/edit_test.go
new file mode 100644
index 0000000..12789eb
--- /dev/null
+++ b/internal/lsp/fake/edit_test.go
@@ -0,0 +1,97 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fake
+
+import (
+	"strings"
+	"testing"
+)
+
+func TestApplyEdit(t *testing.T) {
+	tests := []struct {
+		label   string
+		content string
+		edit    Edit
+		want    string
+		wantErr bool
+	}{
+		{
+			label: "empty content",
+		},
+		{
+			label:   "empty edit",
+			content: "hello",
+			edit:    Edit{},
+			want:    "hello",
+		},
+		{
+			label:   "unicode edit",
+			content: "hello, 日本語",
+			edit: Edit{
+				Start: Pos{Line: 0, Column: 7},
+				End:   Pos{Line: 0, Column: 10},
+				Text:  "world",
+			},
+			want: "hello, world",
+		},
+		{
+			label:   "range edit",
+			content: "ABC\nDEF\nGHI\nJKL",
+			edit: Edit{
+				Start: Pos{Line: 1, Column: 1},
+				End:   Pos{Line: 2, Column: 3},
+				Text:  "12\n345",
+			},
+			want: "ABC\nD12\n345\nJKL",
+		},
+		{
+			label:   "end before start",
+			content: "ABC\nDEF\nGHI\nJKL",
+			edit: Edit{
+				End:   Pos{Line: 1, Column: 1},
+				Start: Pos{Line: 2, Column: 3},
+				Text:  "12\n345",
+			},
+			wantErr: true,
+		},
+		{
+			label:   "out of bounds line",
+			content: "ABC\nDEF\nGHI\nJKL",
+			edit: Edit{
+				Start: Pos{Line: 1, Column: 1},
+				End:   Pos{Line: 4, Column: 3},
+				Text:  "12\n345",
+			},
+			wantErr: true,
+		},
+		{
+			label:   "out of bounds column",
+			content: "ABC\nDEF\nGHI\nJKL",
+			edit: Edit{
+				Start: Pos{Line: 1, Column: 4},
+				End:   Pos{Line: 2, Column: 3},
+				Text:  "12\n345",
+			},
+			wantErr: true,
+		},
+	}
+
+	for _, test := range tests {
+		test := test
+		t.Run(test.label, func(t *testing.T) {
+			lines := strings.Split(test.content, "\n")
+			newLines, err := editContent(lines, test.edit)
+			if (err != nil) != test.wantErr {
+				t.Errorf("got err %v, want error: %t", err, test.wantErr)
+			}
+			if err != nil {
+				return
+			}
+			if got := strings.Join(newLines, "\n"); got != test.want {
+				t.Errorf("got %q, want %q", got, test.want)
+			}
+		})
+	}
+}
diff --git a/internal/lsp/fake/editor.go b/internal/lsp/fake/editor.go
new file mode 100644
index 0000000..5cb0a57
--- /dev/null
+++ b/internal/lsp/fake/editor.go
@@ -0,0 +1,371 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fake
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"sync"
+
+	"golang.org/x/tools/internal/jsonrpc2"
+	"golang.org/x/tools/internal/lsp/protocol"
+)
+
+// Editor is a fake editor client.  It keeps track of client state and can be
+// used for writing LSP tests.
+type Editor struct {
+	// server, client, and workspace are concurrency safe and written only at
+	// construction, so do not require synchronization.
+	server protocol.Server
+	client *Client
+	ws     *Workspace
+
+	// Since this editor is intended just for testing, we use very coarse
+	// locking.
+	mu sync.Mutex
+	// Editor state.
+	buffers     map[string]buffer
+	lastMessage *protocol.ShowMessageParams
+	logs        []*protocol.LogMessageParams
+	diagnostics *protocol.PublishDiagnosticsParams
+	events      []interface{}
+	// Capabilities / Options
+	serverCapabilities protocol.ServerCapabilities
+}
+
+type buffer struct {
+	version int
+	path    string
+	content []string
+}
+
+func (b buffer) text() string {
+	return strings.Join(b.content, "\n")
+}
+
+// NewConnectedEditor creates a new editor that dispatches the LSP across the
+// provided jsonrpc2 connection.
+//
+// The returned editor is initialized and ready to use.
+func NewConnectedEditor(ctx context.Context, ws *Workspace, conn *jsonrpc2.Conn) (*Editor, error) {
+	e := NewEditor(ws)
+	e.server = protocol.ServerDispatcher(conn)
+	e.client = &Client{Editor: e}
+	conn.AddHandler(protocol.ClientHandler(e.client))
+	if err := e.initialize(ctx); err != nil {
+		return nil, err
+	}
+	e.ws.AddWatcher(e.onFileChanges)
+	return e, nil
+}
+
+// NewEditor Creates a new Editor.
+func NewEditor(ws *Workspace) *Editor {
+	return &Editor{
+		buffers: make(map[string]buffer),
+		ws:      ws,
+	}
+}
+
+// Shutdown issues the 'shutdown' LSP notification.
+func (e *Editor) Shutdown(ctx context.Context) error {
+	if e.server != nil {
+		if err := e.server.Shutdown(ctx); err != nil {
+			return fmt.Errorf("Shutdown: %v", err)
+		}
+	}
+	return nil
+}
+
+// Exit issues the 'exit' LSP notification.
+func (e *Editor) Exit(ctx context.Context) error {
+	if e.server != nil {
+		// Not all LSP clients issue the exit RPC, but we do so here to ensure that
+		// we gracefully handle it on multi-session servers.
+		if err := e.server.Exit(ctx); err != nil {
+			return fmt.Errorf("Exit: %v", err)
+		}
+	}
+	return nil
+}
+
+// Client returns the LSP client for this editor.
+func (e *Editor) Client() *Client {
+	return e.client
+}
+
+func (e *Editor) configuration() map[string]interface{} {
+	return map[string]interface{}{
+		"env": map[string]interface{}{
+			"GOPATH":      e.ws.GOPATH(),
+			"GO111MODULE": "on",
+		},
+	}
+}
+
+func (e *Editor) initialize(ctx context.Context) error {
+	params := &protocol.ParamInitialize{}
+	params.ClientInfo.Name = "fakeclient"
+	params.ClientInfo.Version = "v1.0.0"
+	params.RootURI = e.ws.RootURI()
+
+	// TODO: set client capabilities.
+	params.Trace = "messages"
+	// TODO: support workspace folders.
+
+	if e.server != nil {
+		resp, err := e.server.Initialize(ctx, params)
+		if err != nil {
+			return fmt.Errorf("initialize: %v", err)
+		}
+		e.mu.Lock()
+		e.serverCapabilities = resp.Capabilities
+		e.mu.Unlock()
+
+		if err := e.server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
+			return fmt.Errorf("initialized: %v", err)
+		}
+	}
+	return nil
+}
+
+func (e *Editor) onFileChanges(ctx context.Context, evts []FileEvent) {
+	if e.server == nil {
+		return
+	}
+	var lspevts []protocol.FileEvent
+	for _, evt := range evts {
+		lspevts = append(lspevts, evt.ProtocolEvent)
+	}
+	e.server.DidChangeWatchedFiles(ctx, &protocol.DidChangeWatchedFilesParams{
+		Changes: lspevts,
+	})
+}
+
+// OpenFile creates a buffer for the given workspace-relative file.
+func (e *Editor) OpenFile(ctx context.Context, path string) error {
+	content, err := e.ws.ReadFile(path)
+	if err != nil {
+		return err
+	}
+	buf := newBuffer(path, content)
+	e.mu.Lock()
+	e.buffers[path] = buf
+	item := textDocumentItem(e.ws, buf)
+	e.mu.Unlock()
+
+	if e.server != nil {
+		if err := e.server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
+			TextDocument: item,
+		}); err != nil {
+			return fmt.Errorf("DidOpen: %v", err)
+		}
+	}
+	return nil
+}
+
+func newBuffer(path, content string) buffer {
+	return buffer{
+		version: 1,
+		path:    path,
+		content: strings.Split(content, "\n"),
+	}
+}
+
+func textDocumentItem(ws *Workspace, buf buffer) protocol.TextDocumentItem {
+	uri := ws.URI(buf.path)
+	languageID := ""
+	if strings.HasSuffix(buf.path, ".go") {
+		// TODO: what about go.mod files? What is their language ID?
+		languageID = "go"
+	}
+	return protocol.TextDocumentItem{
+		URI:        uri,
+		LanguageID: languageID,
+		Version:    float64(buf.version),
+		Text:       buf.text(),
+	}
+}
+
+// CreateBuffer creates a new unsaved buffer corresponding to the workspace
+// path, containing the given textual content.
+func (e *Editor) CreateBuffer(ctx context.Context, path, content string) error {
+	buf := newBuffer(path, content)
+	e.mu.Lock()
+	e.buffers[path] = buf
+	item := textDocumentItem(e.ws, buf)
+	e.mu.Unlock()
+
+	if e.server != nil {
+		if err := e.server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
+			TextDocument: item,
+		}); err != nil {
+			return fmt.Errorf("DidOpen: %v", err)
+		}
+	}
+	return nil
+}
+
+// CloseBuffer removes the current buffer (regardless of whether it is saved).
+func (e *Editor) CloseBuffer(ctx context.Context, path string) error {
+	e.mu.Lock()
+	_, ok := e.buffers[path]
+	if !ok {
+		e.mu.Unlock()
+		return fmt.Errorf("unknown path %q", path)
+	}
+	delete(e.buffers, path)
+	e.mu.Unlock()
+
+	if e.server != nil {
+		if err := e.server.DidClose(ctx, &protocol.DidCloseTextDocumentParams{
+			TextDocument: protocol.TextDocumentIdentifier{
+				URI: e.ws.URI(path),
+			},
+		}); err != nil {
+			return fmt.Errorf("DidClose: %v", err)
+		}
+	}
+	return nil
+}
+
+// WriteBuffer writes the content of the buffer specified by the given path to
+// the filesystem.
+func (e *Editor) WriteBuffer(ctx context.Context, path string) error {
+	e.mu.Lock()
+	buf, ok := e.buffers[path]
+	if !ok {
+		e.mu.Unlock()
+		return fmt.Errorf(fmt.Sprintf("unknown buffer: %q", path))
+	}
+	content := buf.text()
+	includeText := false
+	syncOptions, ok := e.serverCapabilities.TextDocumentSync.(protocol.TextDocumentSyncOptions)
+	if ok {
+		includeText = syncOptions.Save.IncludeText
+	}
+	e.mu.Unlock()
+
+	docID := protocol.TextDocumentIdentifier{
+		URI: e.ws.URI(buf.path),
+	}
+	if e.server != nil {
+		if err := e.server.WillSave(ctx, &protocol.WillSaveTextDocumentParams{
+			TextDocument: docID,
+			Reason:       protocol.Manual,
+		}); err != nil {
+			return fmt.Errorf("WillSave: %v", err)
+		}
+	}
+	if err := e.ws.WriteFile(ctx, path, content); err != nil {
+		return fmt.Errorf("writing %q: %v", path, err)
+	}
+	if e.server != nil {
+		params := &protocol.DidSaveTextDocumentParams{
+			TextDocument: protocol.VersionedTextDocumentIdentifier{
+				Version:                float64(buf.version),
+				TextDocumentIdentifier: docID,
+			},
+		}
+		if includeText {
+			params.Text = &content
+		}
+		if err := e.server.DidSave(ctx, params); err != nil {
+			return fmt.Errorf("DidSave: %v", err)
+		}
+	}
+	return nil
+}
+
+// EditBuffer applies the given test edits to the buffer identified by path.
+func (e *Editor) EditBuffer(ctx context.Context, path string, edits []Edit) error {
+	params, err := e.doEdits(ctx, path, edits)
+	if err != nil {
+		return err
+	}
+	if e.server != nil {
+		if err := e.server.DidChange(ctx, params); err != nil {
+			return fmt.Errorf("DidChange: %v", err)
+		}
+	}
+	return nil
+}
+
+func (e *Editor) doEdits(ctx context.Context, path string, edits []Edit) (*protocol.DidChangeTextDocumentParams, error) {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+	buf, ok := e.buffers[path]
+	if !ok {
+		return nil, fmt.Errorf("unknown buffer %q", path)
+	}
+	var (
+		content = make([]string, len(buf.content))
+		err     error
+		evts    []protocol.TextDocumentContentChangeEvent
+	)
+	copy(content, buf.content)
+	for _, edit := range edits {
+		content, err = editContent(content, edit)
+		if err != nil {
+			return nil, err
+		}
+		evts = append(evts, edit.toProtocolChangeEvent())
+	}
+	buf.content = content
+	buf.version++
+	e.buffers[path] = buf
+	params := &protocol.DidChangeTextDocumentParams{
+		TextDocument: protocol.VersionedTextDocumentIdentifier{
+			Version: float64(buf.version),
+			TextDocumentIdentifier: protocol.TextDocumentIdentifier{
+				URI: e.ws.URI(buf.path),
+			},
+		},
+		ContentChanges: evts,
+	}
+	return params, nil
+}
+
+// GoToDefinition jumps to the definition of the symbol at the given position
+// in an open buffer.
+func (e *Editor) GoToDefinition(ctx context.Context, path string, pos Pos) (string, Pos, error) {
+	if err := e.checkBufferPosition(path, pos); err != nil {
+		return "", Pos{}, err
+	}
+	params := &protocol.DefinitionParams{}
+	params.TextDocument.URI = e.ws.URI(path)
+	params.Position = pos.toProtocolPosition()
+
+	resp, err := e.server.Definition(ctx, params)
+	if err != nil {
+		return "", Pos{}, fmt.Errorf("definition: %v", err)
+	}
+	if len(resp) == 0 {
+		return "", Pos{}, nil
+	}
+	newPath := e.ws.URIToPath(resp[0].URI)
+	newPos := fromProtocolPosition(resp[0].Range.Start)
+	if err := e.OpenFile(ctx, newPath); err != nil {
+		return "", Pos{}, fmt.Errorf("OpenFile: %v", err)
+	}
+	return newPath, newPos, nil
+}
+
+func (e *Editor) checkBufferPosition(path string, pos Pos) error {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+	buf, ok := e.buffers[path]
+	if !ok {
+		return fmt.Errorf("buffer %q is not open", path)
+	}
+	if !inText(pos, buf.content) {
+		return fmt.Errorf("position %v is invalid in buffer %q", pos, path)
+	}
+	return nil
+}
+
+// TODO: expose more client functionality, for example Hover, CodeAction,
+// Rename, Completion, etc.  setting the content of an entire buffer, etc.
diff --git a/internal/lsp/fake/editor_test.go b/internal/lsp/fake/editor_test.go
new file mode 100644
index 0000000..544f809
--- /dev/null
+++ b/internal/lsp/fake/editor_test.go
@@ -0,0 +1,60 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fake
+
+import (
+	"context"
+	"testing"
+)
+
+const exampleProgram = `
+-- go.mod --
+go 1.12
+-- main.go --
+package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("Hello World.")
+}
+`
+
+func TestClientEditing(t *testing.T) {
+	ws, err := NewWorkspace("test", []byte(exampleProgram))
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer ws.Close()
+	ctx := context.Background()
+	client := NewEditor(ws)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := client.OpenFile(ctx, "main.go"); err != nil {
+		t.Fatal(err)
+	}
+	if err := client.EditBuffer(ctx, "main.go", []Edit{
+		{
+			Start: Pos{5, 14},
+			End:   Pos{5, 26},
+			Text:  "Hola, mundo.",
+		},
+	}); err != nil {
+		t.Fatal(err)
+	}
+	got := client.buffers["main.go"].text()
+	want := `package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("Hola, mundo.")
+}
+`
+	if got != want {
+		t.Errorf("got text %q, want %q", got, want)
+	}
+}
diff --git a/internal/lsp/fake/workspace.go b/internal/lsp/fake/workspace.go
new file mode 100644
index 0000000..b540163
--- /dev/null
+++ b/internal/lsp/fake/workspace.go
@@ -0,0 +1,207 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fake
+
+import (
+	"context"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/span"
+	"golang.org/x/tools/txtar"
+)
+
+// FileEvent wraps the protocol.FileEvent so that it can be associated with a
+// workspace-relative path.
+type FileEvent struct {
+	Path          string
+	ProtocolEvent protocol.FileEvent
+}
+
+// The Workspace type represents a temporary workspace to use for editing Go
+// files in tests.
+type Workspace struct {
+	name    string
+	gopath  string
+	workdir string
+
+	watcherMu sync.Mutex
+	watchers  []func(context.Context, []FileEvent)
+}
+
+// NewWorkspace creates a named workspace populated by the txtar-encoded
+// content given by txt. It creates temporary directories for the workspace
+// content and for GOPATH.
+func NewWorkspace(name string, txt []byte) (_ *Workspace, err error) {
+	w := &Workspace{name: name}
+	defer func() {
+		// Clean up if we fail at any point in this constructor.
+		if err != nil {
+			w.removeAll()
+		}
+	}()
+	dir, err := ioutil.TempDir("", fmt.Sprintf("goplstest-ws-%s-", name))
+	if err != nil {
+		return nil, fmt.Errorf("creating temporary workdir: %v", err)
+	}
+	w.workdir = dir
+	gopath, err := ioutil.TempDir("", fmt.Sprintf("goplstest-gopath-%s-", name))
+	if err != nil {
+		return nil, fmt.Errorf("creating temporary gopath: %v", err)
+	}
+	w.gopath = gopath
+	archive := txtar.Parse(txt)
+	for _, f := range archive.Files {
+		if err := w.writeFileData(f.Name, f.Data); err != nil {
+			return nil, err
+		}
+	}
+	return w, nil
+}
+
+// RootURI returns the root URI for this workspace.
+func (w *Workspace) RootURI() protocol.DocumentURI {
+	return toURI(w.workdir)
+}
+
+// GOPATH returns the value that GOPATH should be set to for this workspace.
+func (w *Workspace) GOPATH() string {
+	return w.gopath
+}
+
+// AddWatcher registers the given func to be called on any file change.
+func (w *Workspace) AddWatcher(watcher func(context.Context, []FileEvent)) {
+	w.watcherMu.Lock()
+	w.watchers = append(w.watchers, watcher)
+	w.watcherMu.Unlock()
+}
+
+// filePath returns the absolute filesystem path to a the workspace-relative
+// path.
+func (w *Workspace) filePath(path string) string {
+	fp := filepath.FromSlash(path)
+	if filepath.IsAbs(fp) {
+		return fp
+	}
+	return filepath.Join(w.workdir, filepath.FromSlash(path))
+}
+
+// URI returns the URI to a the workspace-relative path.
+func (w *Workspace) URI(path string) protocol.DocumentURI {
+	return toURI(w.filePath(path))
+}
+
+// URIToPath converts a uri to a workspace-relative path (or an absolute path,
+// if the uri is outside of the workspace).
+func (w *Workspace) URIToPath(uri protocol.DocumentURI) string {
+	root := w.RootURI().SpanURI().Filename()
+	path := uri.SpanURI().Filename()
+	if rel, err := filepath.Rel(root, path); err == nil && !strings.HasPrefix(rel, "..") {
+		return filepath.ToSlash(rel)
+	}
+	return filepath.ToSlash(path)
+}
+
+func toURI(fp string) protocol.DocumentURI {
+	return protocol.DocumentURI(span.URIFromPath(fp))
+}
+
+// ReadFile reads a text file specified by a workspace-relative path.
+func (w *Workspace) ReadFile(path string) (string, error) {
+	b, err := ioutil.ReadFile(w.filePath(path))
+	if err != nil {
+		return "", err
+	}
+	return string(b), nil
+}
+
+// RemoveFile removes a workspace-relative file path.
+func (w *Workspace) RemoveFile(ctx context.Context, path string) error {
+	fp := w.filePath(path)
+	if err := os.Remove(fp); err != nil {
+		return fmt.Errorf("removing %q: %v", path, err)
+	}
+	evts := []FileEvent{{
+		Path: path,
+		ProtocolEvent: protocol.FileEvent{
+			URI:  w.URI(path),
+			Type: protocol.Deleted,
+		},
+	}}
+	w.sendEvents(ctx, evts)
+	return nil
+}
+
+func (w *Workspace) sendEvents(ctx context.Context, evts []FileEvent) {
+	w.watcherMu.Lock()
+	watchers := make([]func(context.Context, []FileEvent), len(w.watchers))
+	copy(watchers, w.watchers)
+	w.watcherMu.Unlock()
+	for _, w := range watchers {
+		go w(ctx, evts)
+	}
+}
+
+// WriteFile writes text file content to a workspace-relative path.
+func (w *Workspace) WriteFile(ctx context.Context, path, content string) error {
+	fp := w.filePath(path)
+	_, err := os.Stat(fp)
+	if err != nil && !os.IsNotExist(err) {
+		return fmt.Errorf("checking if %q exists: %v", path, err)
+	}
+	var changeType protocol.FileChangeType
+	if os.IsNotExist(err) {
+		changeType = protocol.Created
+	} else {
+		changeType = protocol.Changed
+	}
+	if err := w.writeFileData(path, []byte(content)); err != nil {
+		return err
+	}
+	evts := []FileEvent{{
+		Path: path,
+		ProtocolEvent: protocol.FileEvent{
+			URI:  w.URI(path),
+			Type: changeType,
+		},
+	}}
+	w.sendEvents(ctx, evts)
+	return nil
+}
+
+func (w *Workspace) writeFileData(path string, data []byte) error {
+	fp := w.filePath(path)
+	if err := os.MkdirAll(filepath.Dir(fp), 0755); err != nil {
+		return fmt.Errorf("creating nested directory: %v", err)
+	}
+	if err := ioutil.WriteFile(fp, data, 0644); err != nil {
+		return fmt.Errorf("writing %q: %v", path, err)
+	}
+	return nil
+}
+
+func (w *Workspace) removeAll() error {
+	var werr, perr error
+	if w.workdir != "" {
+		werr = os.RemoveAll(w.workdir)
+	}
+	if w.gopath != "" {
+		perr = os.RemoveAll(w.gopath)
+	}
+	if werr != nil || perr != nil {
+		return fmt.Errorf("error(s) cleaning workspace: removing workdir: %v; removing gopath: %v", werr, perr)
+	}
+	return nil
+}
+
+// Close removes all state associated with the workspace.
+func (w *Workspace) Close() error {
+	return w.removeAll()
+}
diff --git a/internal/lsp/fake/workspace_test.go b/internal/lsp/fake/workspace_test.go
new file mode 100644
index 0000000..31b46d2
--- /dev/null
+++ b/internal/lsp/fake/workspace_test.go
@@ -0,0 +1,92 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fake
+
+import (
+	"context"
+	"testing"
+
+	"golang.org/x/tools/internal/lsp/protocol"
+)
+
+const data = `
+-- go.mod --
+go 1.12
+-- nested/README.md --
+Hello World!
+`
+
+func newWorkspace(t *testing.T) (*Workspace, <-chan []FileEvent, func()) {
+	t.Helper()
+
+	ws, err := NewWorkspace("default", []byte(data))
+	if err != nil {
+		t.Fatal(err)
+	}
+	cleanup := func() {
+		if err := ws.Close(); err != nil {
+			t.Fatal(err)
+		}
+	}
+
+	fileEvents := make(chan []FileEvent)
+	watch := func(_ context.Context, events []FileEvent) {
+		fileEvents <- events
+	}
+	ws.AddWatcher(watch)
+	return ws, fileEvents, cleanup
+}
+
+func TestWorkspace_ReadFile(t *testing.T) {
+	ws, _, cleanup := newWorkspace(t)
+	defer cleanup()
+
+	got, err := ws.ReadFile("nested/README.md")
+	if err != nil {
+		t.Fatal(err)
+	}
+	want := "Hello World!\n"
+	if got != want {
+		t.Errorf("reading workspace file, got %q, want %q", got, want)
+	}
+}
+
+func TestWorkspace_WriteFile(t *testing.T) {
+	ws, events, cleanup := newWorkspace(t)
+	defer cleanup()
+	ctx := context.Background()
+
+	tests := []struct {
+		path     string
+		wantType protocol.FileChangeType
+	}{
+		{"data.txt", protocol.Created},
+		{"nested/README.md", protocol.Changed},
+	}
+
+	for _, test := range tests {
+		if err := ws.WriteFile(ctx, test.path, "42"); err != nil {
+			t.Fatal(err)
+		}
+		es := <-events
+		if got := len(es); got != 1 {
+			t.Fatalf("len(events) = %d, want 1", got)
+		}
+		if es[0].Path != test.path {
+			t.Errorf("event.Path = %q, want %q", es[0].Path, test.path)
+		}
+		if es[0].ProtocolEvent.Type != test.wantType {
+			t.Errorf("event type = %v, want %v", es[0].ProtocolEvent.Type, test.wantType)
+		}
+		got, err := ws.ReadFile(test.path)
+		if err != nil {
+			t.Fatal(err)
+		}
+		want := "42"
+		if got != want {
+			t.Errorf("ws.ReadFile(%q) = %q, want %q", test.path, got, want)
+		}
+	}
+}
diff --git a/internal/lsp/folding_range.go b/internal/lsp/folding_range.go
index 19b28eb..5bae8f5 100644
--- a/internal/lsp/folding_range.go
+++ b/internal/lsp/folding_range.go
@@ -5,28 +5,15 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 func (s *Server) foldingRange(ctx context.Context, params *protocol.FoldingRangeParams) ([]protocol.FoldingRange, error) {
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	var ranges []*source.FoldingRangeInfo
-	switch fh.Identity().Kind {
-	case source.Go:
-		ranges, err = source.FoldingRange(ctx, snapshot, fh, view.Options().LineFoldingOnly)
-	case source.Mod:
-		ranges = nil
-	}
 
+	ranges, err := source.FoldingRange(ctx, snapshot, fh, snapshot.View().Options().LineFoldingOnly)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/lsp/format.go b/internal/lsp/format.go
index f3abeb1..33dd407 100644
--- a/internal/lsp/format.go
+++ b/internal/lsp/format.go
@@ -9,28 +9,14 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 func (s *Server) formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) {
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	var edits []protocol.TextEdit
-	switch fh.Identity().Kind {
-	case source.Go:
-		edits, err = source.Format(ctx, snapshot, fh)
-	case source.Mod:
-		return nil, nil
-	}
-
+	edits, err := source.Format(ctx, snapshot, fh)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index 065819d..9ea2f01 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -42,8 +42,8 @@
 	if len(s.pendingFolders) == 0 {
 		if params.RootURI != "" {
 			s.pendingFolders = []protocol.WorkspaceFolder{{
-				URI:  params.RootURI,
-				Name: path.Base(params.RootURI),
+				URI:  string(params.RootURI),
+				Name: path.Base(params.RootURI.SpanURI().Filename()),
 			}}
 		} else {
 			// No folders and no root--we are in single file mode.
@@ -79,6 +79,7 @@
 			ImplementationProvider:     true,
 			DocumentFormattingProvider: true,
 			DocumentSymbolProvider:     true,
+			WorkspaceSymbolProvider:    true,
 			ExecuteCommandProvider: protocol.ExecuteCommandOptions{
 				Commands: options.SupportedCommands,
 			},
@@ -164,8 +165,8 @@
 	viewErrors := make(map[span.URI]error)
 
 	for _, folder := range folders {
-		uri := span.NewURI(folder.URI)
-		_, snapshot, err := s.addView(ctx, folder.Name, span.NewURI(folder.URI))
+		uri := span.URIFromURI(folder.URI)
+		_, snapshot, err := s.addView(ctx, folder.Name, uri)
 		if err != nil {
 			viewErrors[uri] = err
 			continue
@@ -191,10 +192,10 @@
 	v := protocol.ParamConfiguration{
 		ConfigurationParams: protocol.ConfigurationParams{
 			Items: []protocol.ConfigurationItem{{
-				ScopeURI: protocol.NewURI(folder),
+				ScopeURI: string(folder),
 				Section:  "gopls",
 			}, {
-				ScopeURI: protocol.NewURI(folder),
+				ScopeURI: string(folder),
 				Section:  fmt.Sprintf("gopls-%s", name),
 			}},
 		},
@@ -233,25 +234,57 @@
 	return nil
 }
 
+// beginFileRequest checks preconditions for a file-oriented request and routes
+// it to a snapshot.
+// We don't want to return errors for benign conditions like wrong file type,
+// so callers should do if !ok { return err } rather than if err != nil.
+func (s *Server) beginFileRequest(pURI protocol.DocumentURI, expectKind source.FileKind) (source.Snapshot, source.FileHandle, bool, error) {
+	uri := pURI.SpanURI()
+	if !uri.IsFile() {
+		// Not a file URI. Stop processing the request, but don't return an error.
+		return nil, nil, false, nil
+	}
+	view, err := s.session.ViewOf(uri)
+	if err != nil {
+		return nil, nil, false, err
+	}
+	snapshot := view.Snapshot()
+	fh, err := snapshot.GetFile(uri)
+	if err != nil {
+		return nil, nil, false, err
+	}
+	if expectKind != source.UnknownKind && fh.Identity().Kind != expectKind {
+		// Wrong kind of file. Nothing to do.
+		return nil, nil, false, nil
+	}
+	return snapshot, fh, true, nil
+}
+
 func (s *Server) shutdown(ctx context.Context) error {
 	s.stateMu.Lock()
 	defer s.stateMu.Unlock()
 	if s.state < serverInitialized {
 		return jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server not initialized")
 	}
-	// drop all the active views
-	s.session.Shutdown(ctx)
-	s.state = serverShutDown
+	if s.state != serverShutDown {
+		// drop all the active views
+		s.session.Shutdown(ctx)
+		s.state = serverShutDown
+	}
 	return nil
 }
 
+// ServerExitFunc is used to exit when requested by the client. It is mutable
+// for testing purposes.
+var ServerExitFunc = os.Exit
+
 func (s *Server) exit(ctx context.Context) error {
 	s.stateMu.Lock()
 	defer s.stateMu.Unlock()
 	if s.state != serverShutDown {
-		os.Exit(1)
+		ServerExitFunc(1)
 	}
-	os.Exit(0)
+	ServerExitFunc(0)
 	return nil
 }
 
diff --git a/internal/lsp/helper/README.md b/internal/lsp/helper/README.md
index e27e769..3c51efe 100644
--- a/internal/lsp/helper/README.md
+++ b/internal/lsp/helper/README.md
@@ -3,7 +3,7 @@
 `helper` generates boilerplate code for server.go by processing the
 generated code in `protocol/tsserver.go`.
 
-First, build `helper` in this directore (`go build .`).
+First, build `helper` in this directory (`go build .`).
 
 In directory `lsp`, executing `go generate server.go` generates the stylized file
 `server_gen.go` that contains stubs for type `Server`.
diff --git a/internal/lsp/helper/helper.go b/internal/lsp/helper/helper.go
index e90f2f4..06b2457 100644
--- a/internal/lsp/helper/helper.go
+++ b/internal/lsp/helper/helper.go
@@ -7,6 +7,7 @@
 	"flag"
 	"fmt"
 	"go/ast"
+	"go/format"
 	"go/parser"
 	"go/token"
 	"log"
@@ -109,7 +110,10 @@
 	if err != nil {
 		log.Fatal(err)
 	}
-	ans := bytes.Replace(buf.Bytes(), []byte("\\\n"), []byte{}, -1)
+	ans, err := format.Source(bytes.Replace(buf.Bytes(), []byte("\\\n"), []byte{}, -1))
+	if err != nil {
+		log.Fatal(err)
+	}
 	fd.Write(ans)
 }
 
diff --git a/internal/lsp/highlight.go b/internal/lsp/highlight.go
index 45e374b..7386ddc 100644
--- a/internal/lsp/highlight.go
+++ b/internal/lsp/highlight.go
@@ -10,31 +10,17 @@
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/telemetry"
-	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/log"
 )
 
 func (s *Server) documentHighlight(ctx context.Context, params *protocol.DocumentHighlightParams) ([]protocol.DocumentHighlight, error) {
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
+	rngs, err := source.Highlight(ctx, snapshot, fh, params.Position)
 	if err != nil {
-		return nil, err
-	}
-	var rngs []protocol.Range
-	switch fh.Identity().Kind {
-	case source.Go:
-		rngs, err = source.Highlight(ctx, snapshot, fh, params.Position)
-	case source.Mod:
-		return nil, nil
-	}
-
-	if err != nil {
-		log.Error(ctx, "no highlight", err, telemetry.URI.Of(uri))
+		log.Error(ctx, "no highlight", err, telemetry.URI.Of(params.TextDocument.URI))
 	}
 	return toProtocolHighlight(rngs), nil
 }
diff --git a/internal/lsp/hover.go b/internal/lsp/hover.go
index 967907a..32af7e2 100644
--- a/internal/lsp/hover.go
+++ b/internal/lsp/hover.go
@@ -7,46 +7,21 @@
 import (
 	"context"
 
+	"golang.org/x/tools/internal/lsp/mod"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 func (s *Server) hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.UnknownKind)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
+	switch fh.Identity().Kind {
+	case source.Mod:
+		return mod.Hover(ctx, snapshot, fh, params.Position)
+	case source.Go:
+		return source.Hover(ctx, snapshot, fh, params.Position)
 	}
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
-	ident, err := source.Identifier(ctx, snapshot, fh, params.Position, source.WidestPackageHandle)
-	if err != nil {
-		return nil, nil
-	}
-	h, err := ident.Hover(ctx)
-	if err != nil {
-		return nil, err
-	}
-	rng, err := ident.Range()
-	if err != nil {
-		return nil, err
-	}
-	hover, err := source.FormatHover(h, view.Options())
-	if err != nil {
-		return nil, err
-	}
-	return &protocol.Hover{
-		Contents: protocol.MarkupContent{
-			Kind:  view.Options().PreferredContentFormat,
-			Value: hover,
-		},
-		Range: rng,
-	}, nil
+	return nil, nil
 }
diff --git a/internal/lsp/implementation.go b/internal/lsp/implementation.go
index bae2832..e4b3650 100644
--- a/internal/lsp/implementation.go
+++ b/internal/lsp/implementation.go
@@ -9,22 +9,12 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 func (s *Server) implementation(ctx context.Context, params *protocol.ImplementationParams) ([]protocol.Location, error) {
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
 	return source.Implementation(ctx, snapshot, fh, params.Position)
 }
diff --git a/internal/lsp/link.go b/internal/lsp/link.go
index ddcb334..ae7a0a5 100644
--- a/internal/lsp/link.go
+++ b/internal/lsp/link.go
@@ -5,6 +5,7 @@
 package lsp
 
 import (
+	"bytes"
 	"context"
 	"fmt"
 	"go/ast"
@@ -12,6 +13,7 @@
 	"net/url"
 	"regexp"
 	"strconv"
+	"strings"
 	"sync"
 
 	"golang.org/x/tools/internal/lsp/protocol"
@@ -21,20 +23,78 @@
 )
 
 func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) {
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
-		return nil, err
-	}
-	fh, err := view.Snapshot().GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
 	// TODO(golang/go#36501): Support document links for go.mod files.
-	if fh.Identity().Kind == source.Mod {
-		return nil, nil
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.UnknownKind)
+	if !ok {
+		return nil, err
 	}
-	file, m, _, err := view.Session().Cache().ParseGoHandle(fh, source.ParseFull).Parse(ctx)
+	switch fh.Identity().Kind {
+	case source.Mod:
+		return modLinks(ctx, snapshot, fh)
+	case source.Go:
+		return goLinks(ctx, snapshot.View(), fh)
+	}
+	return nil, nil
+}
+
+func modLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.DocumentLink, error) {
+	view := snapshot.View()
+
+	file, m, err := snapshot.ModHandle(ctx, fh).Parse(ctx)
+	if err != nil {
+		return nil, err
+	}
+	var links []protocol.DocumentLink
+	for _, req := range file.Require {
+		dep := []byte(req.Mod.Path)
+		s, e := req.Syntax.Start.Byte, req.Syntax.End.Byte
+		i := bytes.Index(m.Content[s:e], dep)
+		if i == -1 {
+			continue
+		}
+		// Shift the start position to the location of the
+		// dependency within the require statement.
+		start, end := token.Pos(s+i), token.Pos(s+i+len(dep))
+		target := fmt.Sprintf("https://%s/mod/%s", view.Options().LinkTarget, req.Mod.String())
+		if l, err := toProtocolLink(view, m, target, start, end, source.Mod); err == nil {
+			links = append(links, l)
+		} else {
+			log.Error(ctx, "failed to create protocol link", err)
+		}
+	}
+	// TODO(ridersofrohan): handle links for replace and exclude directives
+	if syntax := file.Syntax; syntax == nil {
+		return links, nil
+	}
+	// Get all the links that are contained in the comments of the file.
+	for _, expr := range file.Syntax.Stmt {
+		comments := expr.Comment()
+		if comments == nil {
+			continue
+		}
+		for _, cmt := range comments.Before {
+			links = append(links, findLinksInString(ctx, view, cmt.Token, token.Pos(cmt.Start.Byte), m, source.Mod)...)
+		}
+		for _, cmt := range comments.Suffix {
+			links = append(links, findLinksInString(ctx, view, cmt.Token, token.Pos(cmt.Start.Byte), m, source.Mod)...)
+		}
+		for _, cmt := range comments.After {
+			links = append(links, findLinksInString(ctx, view, cmt.Token, token.Pos(cmt.Start.Byte), m, source.Mod)...)
+		}
+	}
+	return links, nil
+}
+
+func goLinks(ctx context.Context, view source.View, fh source.FileHandle) ([]protocol.DocumentLink, error) {
+	phs, err := view.Snapshot().PackageHandles(ctx, fh)
+	if err != nil {
+		return nil, err
+	}
+	ph, err := source.WidestPackageHandle(phs)
+	if err != nil {
+		return nil, err
+	}
+	file, _, m, _, err := view.Session().Cache().ParseGoHandle(fh, source.ParseFull).Parse(ctx)
 	if err != nil {
 		return nil, err
 	}
@@ -44,11 +104,13 @@
 		case *ast.ImportSpec:
 			// For import specs, provide a link to a documentation website, like https://pkg.go.dev.
 			if target, err := strconv.Unquote(n.Path.Value); err == nil {
+				if mod, version, ok := moduleAtVersion(ctx, target, ph); ok && strings.ToLower(view.Options().LinkTarget) == "pkg.go.dev" {
+					target = strings.Replace(target, mod, mod+"@"+version, 1)
+				}
 				target = fmt.Sprintf("https://%s/%s", view.Options().LinkTarget, target)
-
 				// Account for the quotation marks in the positions.
 				start, end := n.Path.Pos()+1, n.Path.End()-1
-				if l, err := toProtocolLink(view, m, target, start, end); err == nil {
+				if l, err := toProtocolLink(view, m, target, start, end, source.Go); err == nil {
 					links = append(links, l)
 				} else {
 					log.Error(ctx, "failed to create protocol link", err)
@@ -58,7 +120,7 @@
 		case *ast.BasicLit:
 			// Look for links in string literals.
 			if n.Kind == token.STRING {
-				links = append(links, findLinksInString(ctx, view, n.Value, n.Pos(), m)...)
+				links = append(links, findLinksInString(ctx, view, n.Value, n.Pos(), m, source.Go)...)
 			}
 			return false
 		}
@@ -67,13 +129,32 @@
 	// Look for links in comments.
 	for _, commentGroup := range file.Comments {
 		for _, comment := range commentGroup.List {
-			links = append(links, findLinksInString(ctx, view, comment.Text, comment.Pos(), m)...)
+			links = append(links, findLinksInString(ctx, view, comment.Text, comment.Pos(), m, source.Go)...)
 		}
 	}
 	return links, nil
 }
 
-func findLinksInString(ctx context.Context, view source.View, src string, pos token.Pos, m *protocol.ColumnMapper) []protocol.DocumentLink {
+func moduleAtVersion(ctx context.Context, target string, ph source.PackageHandle) (string, string, bool) {
+	pkg, err := ph.Check(ctx)
+	if err != nil {
+		return "", "", false
+	}
+	impPkg, err := pkg.GetImport(target)
+	if err != nil {
+		return "", "", false
+	}
+	if impPkg.Module() == nil {
+		return "", "", false
+	}
+	version, modpath := impPkg.Module().Version, impPkg.Module().Path
+	if modpath == "" || version == "" {
+		return "", "", false
+	}
+	return modpath, version, true
+}
+
+func findLinksInString(ctx context.Context, view source.View, src string, pos token.Pos, m *protocol.ColumnMapper, fileKind source.FileKind) []protocol.DocumentLink {
 	var links []protocol.DocumentLink
 	for _, index := range view.Options().URLRegexp.FindAllIndex([]byte(src), -1) {
 		start, end := index[0], index[1]
@@ -88,7 +169,7 @@
 		if url.Scheme == "" {
 			url.Scheme = "https"
 		}
-		l, err := toProtocolLink(view, m, url.String(), startPos, endPos)
+		l, err := toProtocolLink(view, m, url.String(), startPos, endPos, fileKind)
 		if err != nil {
 			log.Error(ctx, "failed to create protocol link", err)
 			continue
@@ -107,7 +188,7 @@
 		}
 		org, repo, number := matches[1], matches[2], matches[3]
 		target := fmt.Sprintf("https://github.com/%s/%s/issues/%s", org, repo, number)
-		l, err := toProtocolLink(view, m, target, startPos, endPos)
+		l, err := toProtocolLink(view, m, target, startPos, endPos, fileKind)
 		if err != nil {
 			log.Error(ctx, "failed to create protocol link", err)
 			continue
@@ -129,14 +210,34 @@
 	issueRegexp *regexp.Regexp
 )
 
-func toProtocolLink(view source.View, m *protocol.ColumnMapper, target string, start, end token.Pos) (protocol.DocumentLink, error) {
-	spn, err := span.NewRange(view.Session().Cache().FileSet(), start, end).Span()
-	if err != nil {
-		return protocol.DocumentLink{}, err
-	}
-	rng, err := m.Range(spn)
-	if err != nil {
-		return protocol.DocumentLink{}, err
+func toProtocolLink(view source.View, m *protocol.ColumnMapper, target string, start, end token.Pos, fileKind source.FileKind) (protocol.DocumentLink, error) {
+	var rng protocol.Range
+	switch fileKind {
+	case source.Go:
+		spn, err := span.NewRange(view.Session().Cache().FileSet(), start, end).Span()
+		if err != nil {
+			return protocol.DocumentLink{}, err
+		}
+		rng, err = m.Range(spn)
+		if err != nil {
+			return protocol.DocumentLink{}, err
+		}
+	case source.Mod:
+		s, e := int(start), int(end)
+		line, col, err := m.Converter.ToPosition(s)
+		if err != nil {
+			return protocol.DocumentLink{}, err
+		}
+		start := span.NewPoint(line, col, s)
+		line, col, err = m.Converter.ToPosition(e)
+		if err != nil {
+			return protocol.DocumentLink{}, err
+		}
+		end := span.NewPoint(line, col, e)
+		rng, err = m.Range(span.New(m.URI, start, end))
+		if err != nil {
+			return protocol.DocumentLink{}, err
+		}
 	}
 	return protocol.DocumentLink{
 		Range:  rng,
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 7617126..b10b073 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -5,15 +5,12 @@
 package lsp
 
 import (
-	"bytes"
 	"context"
 	"fmt"
 	"go/token"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
-	"runtime"
 	"sort"
 	"strings"
 	"testing"
@@ -39,61 +36,95 @@
 }
 
 type runner struct {
-	server *Server
-	data   *tests.Data
-	ctx    context.Context
+	server      *Server
+	data        *tests.Data
+	diagnostics map[span.URI][]source.Diagnostic
+	ctx         context.Context
 }
 
-const viewName = "lsp_test"
-
 func testLSP(t *testing.T, exporter packagestest.Exporter) {
 	ctx := tests.Context(t)
 	data := tests.Load(t, exporter, "testdata")
-	defer data.Exported.Cleanup()
 
-	cache := cache.New(nil)
-	session := cache.NewSession()
-	options := tests.DefaultOptions()
-	session.SetOptions(options)
-	options.Env = data.Config.Env
-	if _, _, err := session.NewView(ctx, viewName, span.FileURI(data.Config.Dir), options); err != nil {
-		t.Fatal(err)
-	}
-	var modifications []source.FileModification
-	for filename, content := range data.Config.Overlay {
-		kind := source.DetectLanguage("", filename)
-		if kind != source.Go {
-			continue
+	for _, datum := range data {
+		defer datum.Exported.Cleanup()
+
+		cache := cache.New(nil, nil)
+		session := cache.NewSession()
+		options := tests.DefaultOptions()
+		session.SetOptions(options)
+		options.Env = datum.Config.Env
+		v, _, err := session.NewView(ctx, datum.Config.Dir, span.URIFromPath(datum.Config.Dir), options)
+		if err != nil {
+			t.Fatal(err)
 		}
-		modifications = append(modifications, source.FileModification{
-			URI:        span.FileURI(filename),
-			Action:     source.Open,
-			Version:    -1,
-			Text:       content,
-			LanguageID: "go",
+		// Check to see if the -modfile flag is available, this is basically a check
+		// to see if the go version >= 1.14. Otherwise, the modfile specific tests
+		// will always fail if this flag is not available.
+		for _, flag := range v.Snapshot().Config(ctx).BuildFlags {
+			if strings.Contains(flag, "-modfile=") {
+				datum.ModfileFlagAvailable = true
+				break
+			}
+		}
+		var modifications []source.FileModification
+		for filename, content := range datum.Config.Overlay {
+			kind := source.DetectLanguage("", filename)
+			if kind != source.Go {
+				continue
+			}
+			modifications = append(modifications, source.FileModification{
+				URI:        span.URIFromPath(filename),
+				Action:     source.Open,
+				Version:    -1,
+				Text:       content,
+				LanguageID: "go",
+			})
+		}
+		if _, err := session.DidModifyFiles(ctx, modifications); err != nil {
+			t.Fatal(err)
+		}
+		r := &runner{
+			server: NewServer(session, nil),
+			data:   datum,
+			ctx:    ctx,
+		}
+		t.Run(datum.Folder, func(t *testing.T) {
+			t.Helper()
+			tests.Run(t, r, datum)
 		})
 	}
-	if _, err := session.DidModifyFiles(ctx, modifications); err != nil {
-		t.Fatal(err)
-	}
-	r := &runner{
-		server: &Server{
-			session:   session,
-			delivered: map[span.URI]sentDiagnostics{},
-		},
-		data: data,
-		ctx:  ctx,
-	}
-	tests.Run(t, r, data)
 }
 
-// TODO: Actually test the LSP diagnostics function in this test.
-func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []source.Diagnostic) {
-	v := r.server.session.View(viewName)
-	_, got, err := source.FileDiagnostics(r.ctx, v.Snapshot(), uri)
+func (r *runner) CodeLens(t *testing.T, spn span.Span, want []protocol.CodeLens) {
+	if source.DetectLanguage("", spn.URI().Filename()) != source.Mod {
+		return
+	}
+	v, err := r.server.session.ViewOf(spn.URI())
 	if err != nil {
 		t.Fatal(err)
 	}
+	got, err := mod.CodeLens(r.ctx, v.Snapshot(), spn.URI())
+	if err != nil {
+		t.Fatal(err)
+	}
+	if diff := tests.DiffCodeLens(spn.URI(), want, got); diff != "" {
+		t.Error(diff)
+	}
+}
+
+func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []source.Diagnostic) {
+	// Get the diagnostics for this view if we have not done it before.
+	if r.diagnostics == nil {
+		r.diagnostics = make(map[span.URI][]source.Diagnostic)
+		v := r.server.session.View(r.data.Config.Dir)
+		// Always run diagnostics with analysis.
+		reports := r.server.diagnose(r.ctx, v.Snapshot(), true)
+		for key, diags := range reports {
+			r.diagnostics[key.id.URI] = diags
+		}
+	}
+	got := r.diagnostics[uri]
 	// A special case to test that there are no diagnostics for a file.
 	if len(want) == 1 && want[0].Source == "no_diagnostics" {
 		if len(got) != 0 {
@@ -124,7 +155,7 @@
 	}
 	ranges, err := r.server.FoldingRange(r.ctx, &protocol.FoldingRangeParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	})
 	if err != nil {
@@ -142,7 +173,7 @@
 	}
 	ranges, err = r.server.FoldingRange(r.ctx, &protocol.FoldingRangeParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	})
 	if err != nil {
@@ -276,7 +307,7 @@
 
 	edits, err := r.server.Formatting(r.ctx, &protocol.DocumentFormattingParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	})
 	if err != nil {
@@ -304,7 +335,7 @@
 	filename := uri.Filename()
 	actions, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	})
 	if err != nil {
@@ -332,23 +363,47 @@
 
 func (r *runner) SuggestedFix(t *testing.T, spn span.Span) {
 	uri := spn.URI()
-	filename := uri.Filename()
 	view, err := r.server.session.ViewOf(uri)
 	if err != nil {
 		t.Fatal(err)
 	}
-	snapshot := view.Snapshot()
-	_, diagnostics, err := source.FileDiagnostics(r.ctx, snapshot, uri)
+	m, err := r.data.Mapper(uri)
 	if err != nil {
 		t.Fatal(err)
 	}
+	rng, err := m.Range(spn)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Get the diagnostics for this view if we have not done it before.
+	if r.diagnostics == nil {
+		r.diagnostics = make(map[span.URI][]source.Diagnostic)
+		// Always run diagnostics with analysis.
+		reports := r.server.diagnose(r.ctx, view.Snapshot(), true)
+		for key, diags := range reports {
+			r.diagnostics[key.id.URI] = diags
+		}
+	}
+	var diag *source.Diagnostic
+	for _, d := range r.diagnostics[uri] {
+		// Compare the start positions rather than the entire range because
+		// some diagnostics have a range with the same start and end position (8:1-8:1).
+		// The current marker functionality prevents us from having a range of 0 length.
+		if protocol.ComparePosition(d.Range.Start, rng.Start) == 0 {
+			diag = &d
+			break
+		}
+	}
+	if diag == nil {
+		t.Fatalf("could not get any suggested fixes for %v", spn)
+	}
 	actions, err := r.server.CodeAction(r.ctx, &protocol.CodeActionParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 		Context: protocol.CodeActionContext{
 			Only:        []protocol.CodeActionKind{protocol.QuickFix},
-			Diagnostics: toProtocolDiagnostics(diagnostics),
+			Diagnostics: toProtocolDiagnostics([]source.Diagnostic{*diag}),
 		},
 	})
 	if err != nil {
@@ -358,19 +413,17 @@
 	if len(actions) == 0 {
 		t.Fatal("no code actions returned")
 	}
-	if len(actions) > 1 {
-		t.Fatal("expected only 1 code action")
-	}
 	res, err := applyWorkspaceEdits(r, actions[0].Edit)
 	if err != nil {
 		t.Fatal(err)
 	}
-	got := res[uri]
-	fixed := string(r.data.Golden("suggestedfix", filename, func() ([]byte, error) {
-		return []byte(got), nil
-	}))
-	if fixed != got {
-		t.Errorf("suggested fixes failed for %s, expected:\n%v\ngot:\n%v", filename, fixed, got)
+	for u, got := range res {
+		fixed := string(r.data.Golden("suggestedfix_"+tests.SpanName(spn), u.Filename(), func() ([]byte, error) {
+			return []byte(got), nil
+		}))
+		if fixed != got {
+			t.Errorf("suggested fixes failed for %s, expected:\n%#v\ngot:\n%#v", u.Filename(), fixed, got)
+		}
 	}
 }
 
@@ -426,7 +479,7 @@
 	}
 	if !d.OnlyHover {
 		didSomething = true
-		locURI := span.NewURI(locs[0].URI)
+		locURI := locs[0].URI.SpanURI()
 		lm, err := r.data.Mapper(locURI)
 		if err != nil {
 			t.Fatal(err)
@@ -469,7 +522,7 @@
 
 	var results []span.Span
 	for i := range locs {
-		locURI := span.NewURI(locs[i].URI)
+		locURI := locs[i].URI.SpanURI()
 		lm, err := r.data.Mapper(locURI)
 		if err != nil {
 			t.Fatal(err)
@@ -607,7 +660,7 @@
 
 	wedit, err := r.server.Rename(r.ctx, &protocol.RenameParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 		Position: loc.Range.Start,
 		NewName:  newText,
@@ -636,7 +689,7 @@
 		if i != 0 {
 			got += "\n"
 		}
-		uri := span.URI(orderedURIs[i])
+		uri := span.URIFromURI(orderedURIs[i])
 		if len(res) > 1 {
 			got += filepath.Base(uri.Filename()) + ":\n"
 		}
@@ -675,25 +728,21 @@
 		return
 	}
 	// we all love typed nils
-	if got == nil || got.(*protocol.Range) == nil {
+	if got == nil {
 		if want.Text != "" { // expected an ident.
 			t.Errorf("prepare rename failed for %v: got nil", src)
 		}
 		return
 	}
-	xx, ok := got.(*protocol.Range)
-	if !ok {
-		t.Fatalf("got %T, wanted Range", got)
-	}
-	if xx.Start == xx.End {
+	if got.Start == got.End {
 		// Special case for 0-length ranges. Marks can't specify a 0-length range,
 		// so just compare the start.
-		if xx.Start != want.Range.Start {
-			t.Errorf("prepare rename failed: incorrect point, got %v want %v", xx.Start, want.Range.Start)
+		if got.Start != want.Range.Start {
+			t.Errorf("prepare rename failed: incorrect point, got %v want %v", got.Start, want.Range.Start)
 		}
 	} else {
-		if protocol.CompareRange(*xx, want.Range) != 0 {
-			t.Errorf("prepare rename failed: incorrect range got %v want %v", *xx, want.Range)
+		if protocol.CompareRange(*got, want.Range) != 0 {
+			t.Errorf("prepare rename failed: incorrect range got %v want %v", *got, want.Range)
 		}
 	}
 }
@@ -701,7 +750,7 @@
 func applyWorkspaceEdits(r *runner, wedit protocol.WorkspaceEdit) (map[span.URI]string, error) {
 	res := map[span.URI]string{}
 	for _, docEdits := range wedit.DocumentChanges {
-		uri := span.URI(docEdits.TextDocument.URI)
+		uri := docEdits.TextDocument.URI.SpanURI()
 		m, err := r.data.Mapper(uri)
 		if err != nil {
 			return nil, err
@@ -734,7 +783,7 @@
 func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) {
 	params := &protocol.DocumentSymbolParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: string(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	}
 	symbols, err := r.server.DocumentSymbol(r.ctx, params)
@@ -745,55 +794,80 @@
 		t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols))
 		return
 	}
-	if diff := r.diffSymbols(t, uri, expectedSymbols, symbols); diff != "" {
+	if diff := tests.DiffSymbols(t, uri, expectedSymbols, symbols); diff != "" {
 		t.Error(diff)
 	}
 }
 
-func (r *runner) diffSymbols(t *testing.T, uri span.URI, want []protocol.DocumentSymbol, got []protocol.DocumentSymbol) string {
-	sort.Slice(want, func(i, j int) bool { return want[i].Name < want[j].Name })
-	sort.Slice(got, func(i, j int) bool { return got[i].Name < got[j].Name })
-	if len(got) != len(want) {
-		return summarizeSymbols(t, -1, want, got, "different lengths got %v want %v", len(got), len(want))
+func (r *runner) WorkspaceSymbols(t *testing.T, query string, expectedSymbols []protocol.SymbolInformation, dirs map[string]struct{}) {
+	got := r.callWorkspaceSymbols(t, query, func(opts *source.Options) {
+		opts.Matcher = source.CaseInsensitive
+	})
+	got = tests.FilterWorkspaceSymbols(got, dirs)
+	if len(got) != len(expectedSymbols) {
+		t.Errorf("want %d symbols, got %d", len(expectedSymbols), len(got))
+		return
 	}
-	for i, w := range want {
-		g := got[i]
-		if w.Name != g.Name {
-			return summarizeSymbols(t, i, want, got, "incorrect name got %v want %v", g.Name, w.Name)
-		}
-		if w.Kind != g.Kind {
-			return summarizeSymbols(t, i, want, got, "incorrect kind got %v want %v", g.Kind, w.Kind)
-		}
-		if protocol.CompareRange(g.SelectionRange, w.SelectionRange) != 0 {
-			return summarizeSymbols(t, i, want, got, "incorrect span got %v want %v", g.SelectionRange, w.SelectionRange)
-		}
-		if msg := r.diffSymbols(t, uri, w.Children, g.Children); msg != "" {
-			return fmt.Sprintf("children of %s: %s", w.Name, msg)
-		}
+	if diff := tests.DiffWorkspaceSymbols(expectedSymbols, got); diff != "" {
+		t.Error(diff)
 	}
-	return ""
 }
 
-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 {
-		fmt.Fprintf(msg, " at %d", i)
+func (r *runner) FuzzyWorkspaceSymbols(t *testing.T, query string, expectedSymbols []protocol.SymbolInformation, dirs map[string]struct{}) {
+	got := r.callWorkspaceSymbols(t, query, func(opts *source.Options) {
+		opts.Matcher = source.Fuzzy
+	})
+	got = tests.FilterWorkspaceSymbols(got, dirs)
+	if len(got) != len(expectedSymbols) {
+		t.Errorf("want %d symbols, got %d", len(expectedSymbols), len(got))
+		return
 	}
-	fmt.Fprint(msg, " because of ")
-	fmt.Fprintf(msg, reason, args...)
-	fmt.Fprint(msg, ":\nexpected:\n")
-	for _, s := range want {
-		fmt.Fprintf(msg, "  %v %v %v\n", s.Name, s.Kind, s.SelectionRange)
+	if diff := tests.DiffWorkspaceSymbols(expectedSymbols, got); diff != "" {
+		t.Error(diff)
 	}
-	fmt.Fprintf(msg, "got:\n")
-	for _, s := range got {
-		fmt.Fprintf(msg, "  %v %v %v\n", s.Name, s.Kind, s.SelectionRange)
-	}
-	return msg.String()
 }
 
-func (r *runner) SignatureHelp(t *testing.T, spn span.Span, expectedSignature *source.SignatureInformation) {
+func (r *runner) CaseSensitiveWorkspaceSymbols(t *testing.T, query string, expectedSymbols []protocol.SymbolInformation, dirs map[string]struct{}) {
+	got := r.callWorkspaceSymbols(t, query, func(opts *source.Options) {
+		opts.Matcher = source.CaseSensitive
+	})
+	got = tests.FilterWorkspaceSymbols(got, dirs)
+	if len(got) != len(expectedSymbols) {
+		t.Errorf("want %d symbols, got %d", len(expectedSymbols), len(got))
+		return
+	}
+	if diff := tests.DiffWorkspaceSymbols(expectedSymbols, got); diff != "" {
+		t.Error(diff)
+	}
+}
+
+func (r *runner) callWorkspaceSymbols(t *testing.T, query string, options func(*source.Options)) []protocol.SymbolInformation {
+	t.Helper()
+
+	for _, view := range r.server.session.Views() {
+		original := view.Options()
+		modified := original
+		options(&modified)
+		var err error
+		view, err = view.SetOptions(r.ctx, modified)
+		if err != nil {
+			t.Error(err)
+			return nil
+		}
+		defer view.SetOptions(r.ctx, original)
+	}
+
+	params := &protocol.WorkspaceSymbolParams{
+		Query: query,
+	}
+	symbols, err := r.server.Symbol(r.ctx, params)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return symbols
+}
+
+func (r *runner) SignatureHelp(t *testing.T, spn span.Span, want *protocol.SignatureHelp) {
 	m, err := r.data.Mapper(spn.URI())
 	if err != nil {
 		t.Fatal(err)
@@ -804,70 +878,35 @@
 	}
 	tdpp := protocol.TextDocumentPositionParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(spn.URI()),
+			URI: protocol.URIFromSpanURI(spn.URI()),
 		},
 		Position: loc.Range.Start,
 	}
 	params := &protocol.SignatureHelpParams{
 		TextDocumentPositionParams: tdpp,
 	}
-	gotSignatures, err := r.server.SignatureHelp(r.ctx, params)
+	got, err := r.server.SignatureHelp(r.ctx, params)
 	if err != nil {
 		// Only fail if we got an error we did not expect.
-		if expectedSignature != nil {
+		if want != nil {
 			t.Fatal(err)
 		}
 		return
 	}
-	if expectedSignature == nil {
-		if gotSignatures != nil {
-			t.Errorf("expected no signature, got %v", gotSignatures)
+	if want == nil {
+		if got != nil {
+			t.Errorf("expected no signature, got %v", got)
 		}
 		return
 	}
-	if gotSignatures == nil {
-		t.Fatalf("expected %v, got nil", expectedSignature)
+	if got == nil {
+		t.Fatalf("expected %v, got nil", want)
 	}
-	if diff := diffSignatures(spn, expectedSignature, gotSignatures); diff != "" {
+	if diff := tests.DiffSignatures(spn, want, got); diff != "" {
 		t.Error(diff)
 	}
 }
 
-func diffSignatures(spn span.Span, want *source.SignatureInformation, got *protocol.SignatureHelp) string {
-	decorate := func(f string, args ...interface{}) string {
-		return fmt.Sprintf("Invalid signature at %s: %s", spn, fmt.Sprintf(f, args...))
-	}
-
-	if len(got.Signatures) != 1 {
-		return decorate("wanted 1 signature, got %d", len(got.Signatures))
-	}
-
-	if got.ActiveSignature != 0 {
-		return decorate("wanted active signature of 0, got %d", got.ActiveSignature)
-	}
-
-	if want.ActiveParameter != int(got.ActiveParameter) {
-		return decorate("wanted active parameter of %d, got %d", want.ActiveParameter, got.ActiveParameter)
-	}
-
-	gotSig := got.Signatures[int(got.ActiveSignature)]
-
-	if want.Label != gotSig.Label {
-		return decorate("wanted label %q, got %q", want.Label, gotSig.Label)
-	}
-
-	var paramParts []string
-	for _, p := range gotSig.Parameters {
-		paramParts = append(paramParts, p.Label)
-	}
-	paramsStr := strings.Join(paramParts, ", ")
-	if !strings.Contains(gotSig.Label, paramsStr) {
-		return decorate("expected signature %q to contain params %q", gotSig.Label, paramsStr)
-	}
-
-	return ""
-}
-
 func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
 	m, err := r.data.Mapper(uri)
 	if err != nil {
@@ -875,7 +914,7 @@
 	}
 	got, err := r.server.DocumentLink(r.ctx, &protocol.DocumentLinkParams{
 		TextDocument: protocol.TextDocumentIdentifier{
-			URI: protocol.NewURI(uri),
+			URI: protocol.URIFromSpanURI(uri),
 		},
 	})
 	if err != nil {
@@ -913,7 +952,7 @@
 		fset := token.NewFileSet()
 		f := fset.AddFile(fname, -1, len(test.text))
 		f.SetLinesForContent([]byte(test.text))
-		uri := span.FileURI(fname)
+		uri := span.URIFromPath(fname)
 		converter := span.NewContentConverter(fname, []byte(test.text))
 		mapper := &protocol.ColumnMapper{
 			URI:       uri,
@@ -929,105 +968,3 @@
 		}
 	}
 }
-
-// TODO(golang/go#36091): This function can be refactored to look like the rest of this file
-// when marker support gets added for go.mod files.
-func TestModfileSuggestedFixes(t *testing.T) {
-	if runtime.GOOS == "android" {
-		t.Skip("this test cannot find mod/testdata files")
-	}
-
-	ctx := tests.Context(t)
-	cache := cache.New(nil)
-	session := cache.NewSession()
-	options := tests.DefaultOptions()
-	options.TempModfile = true
-	options.Env = append(os.Environ(), "GOPACKAGESDRIVER=off", "GOROOT=")
-
-	server := Server{
-		session:   session,
-		delivered: map[span.URI]sentDiagnostics{},
-	}
-
-	for _, tt := range []string{"indirect", "unused"} {
-		t.Run(tt, func(t *testing.T) {
-			folder, err := tests.CopyFolderToTempDir(filepath.Join("mod", "testdata", tt))
-			if err != nil {
-				t.Fatal(err)
-			}
-			defer os.RemoveAll(folder)
-
-			_, snapshot, err := session.NewView(ctx, "suggested_fix_test", span.FileURI(folder), options)
-			if err != nil {
-				t.Fatal(err)
-			}
-
-			realURI, tempURI := snapshot.View().ModFiles()
-			// TODO: Add testing for when the -modfile flag is turned off and we still get diagnostics.
-			if tempURI == "" {
-				return
-			}
-			realfh, err := snapshot.GetFile(realURI)
-			if err != nil {
-				t.Fatal(err)
-			}
-
-			reports, err := mod.Diagnostics(ctx, snapshot)
-			if err != nil {
-				t.Fatal(err)
-			}
-			if len(reports) != 1 {
-				t.Errorf("expected 1 fileHandle, got %d", len(reports))
-			}
-
-			_, m, _, _, err := snapshot.ModTidyHandle(ctx, realfh).Tidy(ctx)
-			if err != nil {
-				t.Fatal(err)
-			}
-
-			for fh, diags := range reports {
-				actions, err := server.CodeAction(ctx, &protocol.CodeActionParams{
-					TextDocument: protocol.TextDocumentIdentifier{
-						URI: protocol.NewURI(fh.URI),
-					},
-					Context: protocol.CodeActionContext{
-						Only:        []protocol.CodeActionKind{protocol.SourceOrganizeImports},
-						Diagnostics: toProtocolDiagnostics(diags),
-					},
-				})
-				if err != nil {
-					t.Fatal(err)
-				}
-				if len(actions) == 0 {
-					t.Fatal("no code actions returned")
-				}
-				if len(actions) > 1 {
-					t.Fatal("expected only 1 code action")
-				}
-				res := map[span.URI]string{}
-				for _, docEdits := range actions[0].Edit.DocumentChanges {
-					uri := span.URI(docEdits.TextDocument.URI)
-					content, err := ioutil.ReadFile(uri.Filename())
-					if err != nil {
-						t.Fatal(err)
-					}
-					res[uri] = string(content)
-					sedits, err := source.FromProtocolEdits(m, docEdits.Edits)
-					if err != nil {
-						t.Fatal(err)
-					}
-					res[uri] = applyEdits(res[uri], sedits)
-				}
-				got := res[realfh.Identity().URI]
-				contents, err := ioutil.ReadFile(filepath.Join(folder, "go.mod.golden"))
-				if err != nil {
-					t.Fatal(err)
-				}
-				want := string(contents)
-				if want != got {
-					t.Errorf("suggested fixes failed for %s, expected:\n%s\ngot:\n%s", fh.URI.Filename(), want, got)
-				}
-			}
-		})
-	}
-}
diff --git a/internal/lsp/lsprpc/autostart_posix.go b/internal/lsp/lsprpc/autostart_posix.go
new file mode 100644
index 0000000..99b6606
--- /dev/null
+++ b/internal/lsp/lsprpc/autostart_posix.go
@@ -0,0 +1,49 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !windows
+
+package lsprpc
+
+import (
+	"crypto/sha1"
+	"fmt"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+)
+
+// autoNetworkAddress resolves an id on the 'auto' pseduo-network to a
+// real network and address. On unix, this uses unix domain sockets.
+func autoNetworkAddress(goplsPath, id string) (network string, address string) {
+	// Especially when doing local development or testing, it's important that
+	// the remote gopls instance we connect to is running the same binary as our
+	// forwarder. So we encode a short hash of the binary path into the daemon
+	// socket name. If possible, we also include the buildid in this hash, to
+	// account for long-running processes where the binary has been subsequently
+	// rebuilt.
+	h := sha1.New()
+	cmd := exec.Command("go", "tool", "buildid", goplsPath)
+	cmd.Stdout = h
+	var pathHash []byte
+	if err := cmd.Run(); err == nil {
+		pathHash = h.Sum(nil)
+	} else {
+		log.Printf("error getting current buildid: %v", err)
+		sum := sha1.Sum([]byte(goplsPath))
+		pathHash = sum[:]
+	}
+	shortHash := fmt.Sprintf("%x", pathHash)[:6]
+	user := os.Getenv("USER")
+	if user == "" {
+		user = "shared"
+	}
+	basename := filepath.Base(goplsPath)
+	idComponent := ""
+	if id != "" {
+		idComponent = "-" + id
+	}
+	return "unix", filepath.Join(os.TempDir(), fmt.Sprintf("%s-%s-daemon.%s%s", basename, shortHash, user, idComponent))
+}
diff --git a/internal/lsp/lsprpc/autostart_windows.go b/internal/lsp/lsprpc/autostart_windows.go
new file mode 100644
index 0000000..68f9bf8
--- /dev/null
+++ b/internal/lsp/lsprpc/autostart_windows.go
@@ -0,0 +1,17 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build windows
+
+package lsprpc
+
+// autoNetworkAddress returns the default network and address for the
+// automatically-started gopls remote. See autostart_posix.go for more
+// information.
+func autoNetworkAddress(goplsPath, id string) (network string, address string) {
+	if id != "" {
+		panic("identified remotes are not supported on windows")
+	}
+	return "tcp", ":37374"
+}
diff --git a/internal/lsp/lsprpc/lsprpc.go b/internal/lsp/lsprpc/lsprpc.go
new file mode 100644
index 0000000..4b1e737
--- /dev/null
+++ b/internal/lsp/lsprpc/lsprpc.go
@@ -0,0 +1,432 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package lsprpc implements a jsonrpc2.StreamServer that may be used to
+// serve the LSP on a jsonrpc2 channel.
+package lsprpc
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	stdlog "log"
+	"net"
+	"os"
+	"os/exec"
+	"strconv"
+	"sync/atomic"
+	"time"
+
+	"golang.org/x/sync/errgroup"
+	"golang.org/x/tools/internal/jsonrpc2"
+	"golang.org/x/tools/internal/lsp"
+	"golang.org/x/tools/internal/lsp/cache"
+	"golang.org/x/tools/internal/lsp/debug"
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/telemetry/log"
+)
+
+// AutoNetwork is the pseudo network type used to signal that gopls should use
+// automatic discovery to resolve a remote address.
+const AutoNetwork = "auto"
+
+// The StreamServer type is a jsonrpc2.StreamServer that handles incoming
+// streams as a new LSP session, using a shared cache.
+type StreamServer struct {
+	withTelemetry bool
+	debug         *debug.Instance
+	cache         *cache.Cache
+
+	// serverForTest may be set to a test fake for testing.
+	serverForTest protocol.Server
+}
+
+var clientIndex, serverIndex int64
+
+// NewStreamServer creates a StreamServer using the shared cache. If
+// withTelemetry is true, each session is instrumented with telemetry that
+// records RPC statistics.
+func NewStreamServer(cache *cache.Cache, withTelemetry bool, debugInstance *debug.Instance) *StreamServer {
+	s := &StreamServer{
+		withTelemetry: withTelemetry,
+		debug:         debugInstance,
+		cache:         cache,
+	}
+	return s
+}
+
+// debugInstance is the common functionality shared between client and server
+// gopls instances.
+type debugInstance struct {
+	id           string
+	debugAddress string
+	logfile      string
+	goplsPath    string
+}
+
+func (d debugInstance) ID() string {
+	return d.id
+}
+
+func (d debugInstance) DebugAddress() string {
+	return d.debugAddress
+}
+
+func (d debugInstance) Logfile() string {
+	return d.logfile
+}
+
+func (d debugInstance) GoplsPath() string {
+	return d.goplsPath
+}
+
+// A debugServer is held by the client to identity the remove server to which
+// it is connected.
+type debugServer struct {
+	debugInstance
+	// clientID is the id of this client on the server.
+	clientID string
+}
+
+func (s debugServer) ClientID() string {
+	return s.clientID
+}
+
+// A debugClient is held by the server to identify an incoming client
+// connection.
+type debugClient struct {
+	debugInstance
+	// session is the session serving this client.
+	session *cache.Session
+	// serverID is this id of this server on the client.
+	serverID string
+}
+
+func (c debugClient) Session() debug.Session {
+	return cache.DebugSession{Session: c.session}
+}
+
+func (c debugClient) ServerID() string {
+	return c.serverID
+}
+
+// ServeStream implements the jsonrpc2.StreamServer interface, by handling
+// incoming streams using a new lsp server.
+func (s *StreamServer) ServeStream(ctx context.Context, stream jsonrpc2.Stream) error {
+	index := atomic.AddInt64(&clientIndex, 1)
+
+	conn := jsonrpc2.NewConn(stream)
+	client := protocol.ClientDispatcher(conn)
+	session := s.cache.NewSession()
+	dc := &debugClient{
+		debugInstance: debugInstance{
+			id: strconv.FormatInt(index, 10),
+		},
+		session: session,
+	}
+	s.debug.State.AddClient(dc)
+	defer s.debug.State.DropClient(dc)
+
+	server := s.serverForTest
+	if server == nil {
+		server = lsp.NewServer(session, client)
+	}
+	// Clients may or may not send a shutdown message. Make sure the server is
+	// shut down.
+	// TODO(rFindley): this shutdown should perhaps be on a disconnected context.
+	defer server.Shutdown(ctx)
+	conn.AddHandler(protocol.ServerHandler(server))
+	conn.AddHandler(protocol.Canceller{})
+	if s.withTelemetry {
+		conn.AddHandler(telemetryHandler{})
+	}
+	executable, err := os.Executable()
+	if err != nil {
+		stdlog.Printf("error getting gopls path: %v", err)
+		executable = ""
+	}
+	conn.AddHandler(&handshaker{
+		client:    dc,
+		debug:     s.debug,
+		goplsPath: executable,
+	})
+	return conn.Run(protocol.WithClient(ctx, client))
+}
+
+// A Forwarder is a jsonrpc2.StreamServer that handles an LSP stream by
+// forwarding it to a remote. This is used when the gopls process started by
+// the editor is in the `-remote` mode, which means it finds and connects to a
+// separate gopls daemon. In these cases, we still want the forwarder gopls to
+// be instrumented with telemetry, and want to be able to in some cases hijack
+// the jsonrpc2 connection with the daemon.
+type Forwarder struct {
+	network, addr string
+
+	// Configuration. Right now, not all of this may be customizable, but in the
+	// future it probably will be.
+	withTelemetry bool
+	dialTimeout   time.Duration
+	retries       int
+	debug         *debug.Instance
+	goplsPath     string
+}
+
+// NewForwarder creates a new Forwarder, ready to forward connections to the
+// remote server specified by network and addr.
+func NewForwarder(network, addr string, withTelemetry bool, debugInstance *debug.Instance) *Forwarder {
+	gp, err := os.Executable()
+	if err != nil {
+		stdlog.Printf("error getting gopls path for forwarder: %v", err)
+		gp = ""
+	}
+
+	return &Forwarder{
+		network:       network,
+		addr:          addr,
+		withTelemetry: withTelemetry,
+		dialTimeout:   1 * time.Second,
+		retries:       5,
+		debug:         debugInstance,
+		goplsPath:     gp,
+	}
+}
+
+// ServeStream dials the forwarder remote and binds the remote to serve the LSP
+// on the incoming stream.
+func (f *Forwarder) ServeStream(ctx context.Context, stream jsonrpc2.Stream) error {
+	clientConn := jsonrpc2.NewConn(stream)
+	client := protocol.ClientDispatcher(clientConn)
+
+	netConn, err := f.connectToRemote(ctx)
+	if err != nil {
+		return fmt.Errorf("forwarder: connecting to remote: %v", err)
+	}
+	serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn, netConn))
+	server := protocol.ServerDispatcher(serverConn)
+
+	// Forward between connections.
+	serverConn.AddHandler(protocol.ClientHandler(client))
+	serverConn.AddHandler(protocol.Canceller{})
+	clientConn.AddHandler(protocol.ServerHandler(server))
+	clientConn.AddHandler(protocol.Canceller{})
+	clientConn.AddHandler(forwarderHandler{})
+	if f.withTelemetry {
+		clientConn.AddHandler(telemetryHandler{})
+	}
+	g, ctx := errgroup.WithContext(ctx)
+	g.Go(func() error {
+		return serverConn.Run(ctx)
+	})
+	// Don't run the clientConn yet, so that we can complete the handshake before
+	// processing any client messages.
+
+	// Do a handshake with the server instance to exchange debug information.
+	index := atomic.AddInt64(&serverIndex, 1)
+	serverID := strconv.FormatInt(index, 10)
+	var (
+		hreq = handshakeRequest{
+			ServerID:  serverID,
+			Logfile:   f.debug.Logfile,
+			DebugAddr: f.debug.ListenedDebugAddress,
+			GoplsPath: f.goplsPath,
+		}
+		hresp handshakeResponse
+	)
+	if err := serverConn.Call(ctx, handshakeMethod, hreq, &hresp); err != nil {
+		log.Error(ctx, "forwarder: gopls handshake failed", err)
+	}
+	if hresp.GoplsPath != f.goplsPath {
+		log.Error(ctx, "", fmt.Errorf("forwarder: gopls path mismatch: forwarder is %q, remote is %q", f.goplsPath, hresp.GoplsPath))
+	}
+	f.debug.State.AddServer(debugServer{
+		debugInstance: debugInstance{
+			id:           serverID,
+			logfile:      hresp.Logfile,
+			debugAddress: hresp.DebugAddr,
+			goplsPath:    hresp.GoplsPath,
+		},
+		clientID: hresp.ClientID,
+	})
+	g.Go(func() error {
+		return clientConn.Run(ctx)
+	})
+
+	return g.Wait()
+}
+
+func (f *Forwarder) connectToRemote(ctx context.Context) (net.Conn, error) {
+	var (
+		netConn          net.Conn
+		err              error
+		network, address = f.network, f.addr
+	)
+	if f.network == AutoNetwork {
+		// f.network is overloaded to support a concept of 'automatic' addresses,
+		// which signals that the gopls remote address should be automatically
+		// derived.
+		// So we need to resolve a real network and address here.
+		network, address = autoNetworkAddress(f.goplsPath, f.addr)
+	}
+	// Try dialing our remote once, in case it is already running.
+	netConn, err = net.DialTimeout(network, address, f.dialTimeout)
+	if err == nil {
+		return netConn, nil
+	}
+	// If our remote is on the 'auto' network, start it if it doesn't exist.
+	if f.network == AutoNetwork {
+		if f.goplsPath == "" {
+			return nil, fmt.Errorf("cannot auto-start remote: gopls path is unknown")
+		}
+		if network == "unix" {
+			// Sometimes the socketfile isn't properly cleaned up when gopls shuts
+			// down. Since we have already tried and failed to dial this address, it
+			// should *usually* be safe to remove the socket before binding to the
+			// address.
+			// TODO(rfindley): there is probably a race here if multiple gopls
+			// instances are simultaneously starting up.
+			if _, err := os.Stat(address); err == nil {
+				if err := os.Remove(address); err != nil {
+					return nil, fmt.Errorf("removing remote socket file: %v", err)
+				}
+			}
+		}
+		if err := startRemote(f.goplsPath, network, address); err != nil {
+			return nil, fmt.Errorf("startRemote(%q, %q): %v", network, address, err)
+		}
+	}
+
+	// It can take some time for the newly started server to bind to our address,
+	// so we retry for a bit.
+	for retry := 0; retry < f.retries; retry++ {
+		startDial := time.Now()
+		netConn, err = net.DialTimeout(network, address, f.dialTimeout)
+		if err == nil {
+			return netConn, nil
+		}
+		log.Print(ctx, fmt.Sprintf("failed attempt #%d to connect to remote: %v\n", retry+2, err))
+		// In case our failure was a fast-failure, ensure we wait at least
+		// f.dialTimeout before trying again.
+		if retry != f.retries-1 {
+			time.Sleep(f.dialTimeout - time.Since(startDial))
+		}
+	}
+	return nil, fmt.Errorf("dialing remote: %v", err)
+}
+
+func startRemote(goplsPath, network, address string) error {
+	args := []string{"serve",
+		"-listen", fmt.Sprintf(`%s;%s`, network, address),
+		"-listen.timeout", "1m",
+		"-debug", ":0",
+		"-logfile", "auto",
+	}
+	cmd := exec.Command(goplsPath, args...)
+	if err := cmd.Start(); err != nil {
+		return fmt.Errorf("starting remote gopls: %v", err)
+	}
+	return nil
+}
+
+// ForwarderExitFunc is used to exit the forwarder process. It is mutable for
+// testing purposes.
+var ForwarderExitFunc = os.Exit
+
+// OverrideExitFuncsForTest can be used from test code to prevent the test
+// process from exiting on server shutdown. The returned func reverts the exit
+// funcs to their previous state.
+func OverrideExitFuncsForTest() func() {
+	// Override functions that would shut down the test process
+	cleanup := func(lspExit, forwarderExit func(code int)) func() {
+		return func() {
+			lsp.ServerExitFunc = lspExit
+			ForwarderExitFunc = forwarderExit
+		}
+	}(lsp.ServerExitFunc, ForwarderExitFunc)
+	// It is an error for a test to shutdown a server process.
+	lsp.ServerExitFunc = func(code int) {
+		panic(fmt.Sprintf("LSP server exited with code %d", code))
+	}
+	// We don't want our forwarders to exit, but it's OK if they would have.
+	ForwarderExitFunc = func(code int) {}
+	return cleanup
+}
+
+// forwarderHandler intercepts 'exit' messages to prevent the shared gopls
+// instance from exiting. In the future it may also intercept 'shutdown' to
+// provide more graceful shutdown of the client connection.
+type forwarderHandler struct {
+	jsonrpc2.EmptyHandler
+}
+
+func (forwarderHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
+	// TODO(golang.org/issues/34111): we should more gracefully disconnect here,
+	// once that process exists.
+	if r.Method == "exit" {
+		ForwarderExitFunc(0)
+		// Still return true here to prevent the message from being delivered: in
+		// tests, ForwarderExitFunc may be overridden to something that doesn't
+		// exit the process.
+		return true
+	}
+	return false
+}
+
+type handshaker struct {
+	jsonrpc2.EmptyHandler
+	client    *debugClient
+	debug     *debug.Instance
+	goplsPath string
+}
+
+type handshakeRequest struct {
+	ServerID  string `json:"serverID"`
+	Logfile   string `json:"logfile"`
+	DebugAddr string `json:"debugAddr"`
+	GoplsPath string `json:"goplsPath"`
+}
+
+type handshakeResponse struct {
+	ClientID  string `json:"clientID"`
+	SessionID string `json:"sessionID"`
+	Logfile   string `json:"logfile"`
+	DebugAddr string `json:"debugAddr"`
+	GoplsPath string `json:"goplsPath"`
+}
+
+const handshakeMethod = "gopls/handshake"
+
+func (h *handshaker) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
+	if r.Method == handshakeMethod {
+		var req handshakeRequest
+		if err := json.Unmarshal(*r.Params, &req); err != nil {
+			sendError(ctx, r, err)
+			return true
+		}
+		h.client.debugAddress = req.DebugAddr
+		h.client.logfile = req.Logfile
+		h.client.serverID = req.ServerID
+		h.client.goplsPath = req.GoplsPath
+		resp := handshakeResponse{
+			ClientID:  h.client.id,
+			SessionID: cache.DebugSession{Session: h.client.session}.ID(),
+			Logfile:   h.debug.Logfile,
+			DebugAddr: h.debug.ListenedDebugAddress,
+			GoplsPath: h.goplsPath,
+		}
+		if err := r.Reply(ctx, resp, nil); err != nil {
+			log.Error(ctx, "replying to handshake", err)
+		}
+		return true
+	}
+	return false
+}
+
+func sendError(ctx context.Context, req *jsonrpc2.Request, err error) {
+	if _, ok := err.(*jsonrpc2.Error); !ok {
+		err = jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err)
+	}
+	if err := req.Reply(ctx, nil, err); err != nil {
+		log.Error(ctx, "", err)
+	}
+}
diff --git a/internal/lsp/lsprpc/lsprpc_test.go b/internal/lsp/lsprpc/lsprpc_test.go
new file mode 100644
index 0000000..1bca640
--- /dev/null
+++ b/internal/lsp/lsprpc/lsprpc_test.go
@@ -0,0 +1,231 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package lsprpc
+
+import (
+	"context"
+	"regexp"
+	"sync"
+	"testing"
+	"time"
+
+	"golang.org/x/tools/internal/jsonrpc2/servertest"
+	"golang.org/x/tools/internal/lsp/cache"
+	"golang.org/x/tools/internal/lsp/debug"
+	"golang.org/x/tools/internal/lsp/fake"
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/telemetry/log"
+)
+
+type fakeClient struct {
+	protocol.Client
+
+	logs chan string
+}
+
+func (c fakeClient) LogMessage(ctx context.Context, params *protocol.LogMessageParams) error {
+	c.logs <- params.Message
+	return nil
+}
+
+type pingServer struct{ protocol.Server }
+
+func (s pingServer) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
+	log.Print(ctx, "ping")
+	return nil
+}
+
+func (s pingServer) Shutdown(ctx context.Context) error {
+	return nil
+}
+
+func TestClientLogging(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	server := pingServer{}
+	client := fakeClient{logs: make(chan string, 10)}
+
+	di := debug.NewInstance("", "")
+	ss := NewStreamServer(cache.New(nil, di.State), false, di)
+	ss.serverForTest = server
+	ts := servertest.NewPipeServer(ctx, ss)
+	defer ts.Close()
+	cc := ts.Connect(ctx)
+	cc.AddHandler(protocol.ClientHandler(client))
+
+	protocol.ServerDispatcher(cc).DidOpen(ctx, &protocol.DidOpenTextDocumentParams{})
+
+	select {
+	case got := <-client.logs:
+		want := "ping"
+		matched, err := regexp.MatchString(want, got)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !matched {
+			t.Errorf("got log %q, want a log containing %q", got, want)
+		}
+	case <-time.After(1 * time.Second):
+		t.Error("timeout waiting for client log")
+	}
+}
+
+// waitableServer instruments LSP request so that we can control their timing.
+// The requests chosen are arbitrary: we simply needed one that blocks, and
+// another that doesn't.
+type waitableServer struct {
+	protocol.Server
+
+	started chan struct{}
+}
+
+func (s waitableServer) Hover(ctx context.Context, _ *protocol.HoverParams) (*protocol.Hover, error) {
+	s.started <- struct{}{}
+	select {
+	case <-ctx.Done():
+		return nil, ctx.Err()
+	case <-time.After(200 * time.Millisecond):
+	}
+	return &protocol.Hover{}, nil
+}
+
+func (s waitableServer) Resolve(_ context.Context, item *protocol.CompletionItem) (*protocol.CompletionItem, error) {
+	return item, nil
+}
+
+func (s waitableServer) Shutdown(ctx context.Context) error {
+	return nil
+}
+
+func TestRequestCancellation(t *testing.T) {
+	server := waitableServer{
+		started: make(chan struct{}),
+	}
+	diserve := debug.NewInstance("", "")
+	ss := NewStreamServer(cache.New(nil, diserve.State), false, diserve)
+	ss.serverForTest = server
+	ctx := context.Background()
+	tsDirect := servertest.NewTCPServer(ctx, ss)
+	defer tsDirect.Close()
+
+	forwarder := NewForwarder("tcp", tsDirect.Addr, false, debug.NewInstance("", ""))
+	tsForwarded := servertest.NewPipeServer(ctx, forwarder)
+	defer tsForwarded.Close()
+
+	tests := []struct {
+		serverType string
+		ts         servertest.Connector
+	}{
+		{"direct", tsDirect},
+		{"forwarder", tsForwarded},
+	}
+
+	for _, test := range tests {
+		t.Run(test.serverType, func(t *testing.T) {
+			cc := test.ts.Connect(ctx)
+			cc.AddHandler(protocol.Canceller{})
+			ctx := context.Background()
+			ctx1, cancel1 := context.WithCancel(ctx)
+			var (
+				err1, err2 error
+				wg         sync.WaitGroup
+			)
+			wg.Add(2)
+			go func() {
+				defer wg.Done()
+				_, err1 = protocol.ServerDispatcher(cc).Hover(ctx1, &protocol.HoverParams{})
+			}()
+			go func() {
+				defer wg.Done()
+				_, err2 = protocol.ServerDispatcher(cc).Resolve(ctx, &protocol.CompletionItem{})
+			}()
+			// Wait for the Hover request to start.
+			<-server.started
+			cancel1()
+			wg.Wait()
+			if err1 == nil {
+				t.Errorf("cancelled Hover(): got nil err")
+			}
+			if err2 != nil {
+				t.Errorf("uncancelled Hover(): err: %v", err2)
+			}
+			if _, err := protocol.ServerDispatcher(cc).Resolve(ctx, &protocol.CompletionItem{}); err != nil {
+				t.Errorf("subsequent Hover(): %v", err)
+			}
+		})
+	}
+}
+
+const exampleProgram = `
+-- go.mod --
+module mod
+
+go 1.12
+-- main.go --
+package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("Hello World.")
+}`
+
+func TestDebugInfoLifecycle(t *testing.T) {
+	resetExitFuncs := OverrideExitFuncsForTest()
+	defer resetExitFuncs()
+
+	clientDebug := debug.NewInstance("", "")
+	serverDebug := debug.NewInstance("", "")
+
+	cache := cache.New(nil, serverDebug.State)
+	ss := NewStreamServer(cache, false, serverDebug)
+	ctx := context.Background()
+	tsBackend := servertest.NewTCPServer(ctx, ss)
+
+	forwarder := NewForwarder("tcp", tsBackend.Addr, false, clientDebug)
+	tsForwarder := servertest.NewPipeServer(ctx, forwarder)
+
+	ws, err := fake.NewWorkspace("gopls-lsprpc-test", []byte(exampleProgram))
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer ws.Close()
+
+	conn1 := tsForwarder.Connect(ctx)
+	ed1, err := fake.NewConnectedEditor(ctx, ws, conn1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer ed1.Shutdown(ctx)
+	conn2 := tsBackend.Connect(ctx)
+	ed2, err := fake.NewConnectedEditor(ctx, ws, conn2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer ed2.Shutdown(ctx)
+
+	if got, want := len(serverDebug.State.Clients()), 2; got != want {
+		t.Errorf("len(server:Clients) = %d, want %d", got, want)
+	}
+	if got, want := len(serverDebug.State.Sessions()), 2; got != want {
+		t.Errorf("len(server:Sessions) = %d, want %d", got, want)
+	}
+	if got, want := len(clientDebug.State.Servers()), 1; got != want {
+		t.Errorf("len(client:Servers) = %d, want %d", got, want)
+	}
+	// Close one of the connections to verify that the client and session were
+	// dropped.
+	if err := ed1.Shutdown(ctx); err != nil {
+		t.Fatal(err)
+	}
+	if got, want := len(serverDebug.State.Sessions()), 1; got != want {
+		t.Errorf("len(server:Sessions()) = %d, want %d", got, want)
+	}
+	// TODO(rfindley): once disconnection works, assert that len(Clients) == 1
+	// (as of writing, it is still 2)
+}
+
+// TODO: add a test for telemetry.
diff --git a/internal/lsp/lsprpc/telemetry.go b/internal/lsp/lsprpc/telemetry.go
new file mode 100644
index 0000000..6909261
--- /dev/null
+++ b/internal/lsp/lsprpc/telemetry.go
@@ -0,0 +1,115 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package lsprpc
+
+import (
+	"context"
+	"encoding/json"
+	"time"
+
+	"golang.org/x/tools/internal/jsonrpc2"
+	"golang.org/x/tools/internal/lsp/telemetry"
+	"golang.org/x/tools/internal/telemetry/trace"
+)
+
+type telemetryHandler struct{}
+
+func (h telemetryHandler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
+	stats := h.getStats(ctx)
+	if stats != nil {
+		stats.delivering()
+	}
+	return false
+}
+
+func (h telemetryHandler) Cancel(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID, cancelled bool) bool {
+	return false
+}
+
+func (h telemetryHandler) Request(ctx context.Context, conn *jsonrpc2.Conn, direction jsonrpc2.Direction, r *jsonrpc2.WireRequest) context.Context {
+	if r.Method == "" {
+		panic("no method in rpc stats")
+	}
+	stats := &rpcStats{
+		method:    r.Method,
+		start:     time.Now(),
+		direction: direction,
+		payload:   r.Params,
+	}
+	ctx = context.WithValue(ctx, statsKey, stats)
+	mode := telemetry.Outbound
+	if direction == jsonrpc2.Receive {
+		mode = telemetry.Inbound
+	}
+	ctx, stats.close = trace.StartSpan(ctx, r.Method,
+		telemetry.Method.Of(r.Method),
+		telemetry.RPCDirection.Of(mode),
+		telemetry.RPCID.Of(r.ID),
+	)
+	telemetry.Started.Record(ctx, 1)
+	_, stats.delivering = trace.StartSpan(ctx, "queued")
+	return ctx
+}
+
+func (h telemetryHandler) Response(ctx context.Context, conn *jsonrpc2.Conn, direction jsonrpc2.Direction, r *jsonrpc2.WireResponse) context.Context {
+	return ctx
+}
+
+func (h telemetryHandler) Done(ctx context.Context, err error) {
+	stats := h.getStats(ctx)
+	if err != nil {
+		ctx = telemetry.StatusCode.With(ctx, "ERROR")
+	} else {
+		ctx = telemetry.StatusCode.With(ctx, "OK")
+	}
+	elapsedTime := time.Since(stats.start)
+	latencyMillis := float64(elapsedTime) / float64(time.Millisecond)
+	telemetry.Latency.Record(ctx, latencyMillis)
+	stats.close()
+}
+
+func (h telemetryHandler) Read(ctx context.Context, bytes int64) context.Context {
+	telemetry.SentBytes.Record(ctx, bytes)
+	return ctx
+}
+
+func (h telemetryHandler) Wrote(ctx context.Context, bytes int64) context.Context {
+	telemetry.ReceivedBytes.Record(ctx, bytes)
+	return ctx
+}
+
+const eol = "\r\n\r\n\r\n"
+
+func (h telemetryHandler) Error(ctx context.Context, err error) {
+}
+
+func (h telemetryHandler) getStats(ctx context.Context) *rpcStats {
+	stats, ok := ctx.Value(statsKey).(*rpcStats)
+	if !ok || stats == nil {
+		method, ok := ctx.Value(telemetry.Method).(string)
+		if !ok {
+			method = "???"
+		}
+		stats = &rpcStats{
+			method: method,
+			close:  func() {},
+		}
+	}
+	return stats
+}
+
+type rpcStats struct {
+	method     string
+	direction  jsonrpc2.Direction
+	id         *jsonrpc2.ID
+	payload    *json.RawMessage
+	start      time.Time
+	delivering func()
+	close      func()
+}
+
+type statsKeyType int
+
+const statsKey = statsKeyType(0)
diff --git a/internal/lsp/mod/code_lens.go b/internal/lsp/mod/code_lens.go
new file mode 100644
index 0000000..bb08788
--- /dev/null
+++ b/internal/lsp/mod/code_lens.go
@@ -0,0 +1,67 @@
+package mod
+
+import (
+	"context"
+	"fmt"
+
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
+	"golang.org/x/tools/internal/lsp/telemetry"
+	"golang.org/x/tools/internal/span"
+	"golang.org/x/tools/internal/telemetry/trace"
+)
+
+func CodeLens(ctx context.Context, snapshot source.Snapshot, uri span.URI) ([]protocol.CodeLens, error) {
+	realURI, _ := snapshot.View().ModFiles()
+	if realURI == "" {
+		return nil, nil
+	}
+	// Only get code lens on the go.mod for the view.
+	if uri != realURI {
+		return nil, nil
+	}
+	ctx, done := trace.StartSpan(ctx, "mod.CodeLens", telemetry.File.Of(realURI))
+	defer done()
+
+	fh, err := snapshot.GetFile(realURI)
+	if err != nil {
+		return nil, err
+	}
+	f, m, upgrades, err := snapshot.ModHandle(ctx, fh).Upgrades(ctx)
+	if err != nil {
+		return nil, err
+	}
+	var codelens []protocol.CodeLens
+	for _, req := range f.Require {
+		dep := req.Mod.Path
+		latest, ok := upgrades[dep]
+		if !ok {
+			continue
+		}
+		// Get the range of the require directive.
+		s, e := req.Syntax.Start, req.Syntax.End
+		line, col, err := m.Converter.ToPosition(s.Byte)
+		if err != nil {
+			return nil, err
+		}
+		start := span.NewPoint(line, col, s.Byte)
+		line, col, err = m.Converter.ToPosition(e.Byte)
+		if err != nil {
+			return nil, err
+		}
+		end := span.NewPoint(line, col, e.Byte)
+		rng, err := m.Range(span.New(uri, start, end))
+		if err != nil {
+			return nil, err
+		}
+		codelens = append(codelens, protocol.CodeLens{
+			Range: rng,
+			Command: protocol.Command{
+				Title:     fmt.Sprintf("Upgrade dependency to %s", latest),
+				Command:   "upgrade.dependency",
+				Arguments: []interface{}{uri, dep},
+			},
+		})
+	}
+	return codelens, err
+}
diff --git a/internal/lsp/mod/diagnostics.go b/internal/lsp/mod/diagnostics.go
index 960f493..401f9ef 100644
--- a/internal/lsp/mod/diagnostics.go
+++ b/internal/lsp/mod/diagnostics.go
@@ -8,7 +8,10 @@
 
 import (
 	"context"
+	"fmt"
+	"regexp"
 
+	"golang.org/x/mod/modfile"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/telemetry"
@@ -16,13 +19,13 @@
 	"golang.org/x/tools/internal/telemetry/trace"
 )
 
-func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.FileIdentity][]source.Diagnostic, error) {
+func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.FileIdentity][]source.Diagnostic, map[string]*modfile.Require, error) {
 	// TODO: We will want to support diagnostics for go.mod files even when the -modfile flag is turned off.
 	realURI, tempURI := snapshot.View().ModFiles()
 
 	// Check the case when the tempModfile flag is turned off.
 	if realURI == "" || tempURI == "" {
-		return nil, nil
+		return nil, nil, nil
 	}
 
 	ctx, done := trace.StartSpan(ctx, "mod.Diagnostics", telemetry.File.Of(realURI))
@@ -30,11 +33,15 @@
 
 	realfh, err := snapshot.GetFile(realURI)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
-	_, _, _, parseErrors, err := snapshot.ModTidyHandle(ctx, realfh).Tidy(ctx)
+	mth, err := snapshot.ModTidyHandle(ctx, realfh)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
+	}
+	_, _, missingDeps, parseErrors, err := mth.Tidy(ctx)
+	if err != nil {
+		return nil, nil, err
 	}
 
 	reports := map[source.FileIdentity][]source.Diagnostic{
@@ -54,11 +61,15 @@
 		}
 		reports[realfh.Identity()] = append(reports[realfh.Identity()], diag)
 	}
-	return reports, nil
+	return reports, missingDeps, nil
 }
 
 func SuggestedFixes(ctx context.Context, snapshot source.Snapshot, realfh source.FileHandle, diags []protocol.Diagnostic) []protocol.CodeAction {
-	_, _, _, parseErrors, err := snapshot.ModTidyHandle(ctx, realfh).Tidy(ctx)
+	mth, err := snapshot.ModTidyHandle(ctx, realfh)
+	if err != nil {
+		return nil
+	}
+	_, _, _, parseErrors, err := mth.Tidy(ctx)
 	if err != nil {
 		return nil
 	}
@@ -94,7 +105,7 @@
 						TextDocument: protocol.VersionedTextDocumentIdentifier{
 							Version: fh.Identity().Version,
 							TextDocumentIdentifier: protocol.TextDocumentIdentifier{
-								URI: protocol.NewURI(fh.Identity().URI),
+								URI: protocol.URIFromSpanURI(fh.Identity().URI),
 							},
 						},
 						Edits: edits,
@@ -107,6 +118,89 @@
 	return actions
 }
 
+func SuggestedGoFixes(ctx context.Context, snapshot source.Snapshot, gofh source.FileHandle, diags []protocol.Diagnostic) ([]protocol.CodeAction, error) {
+	// TODO: We will want to support diagnostics for go.mod files even when the -modfile flag is turned off.
+	realURI, tempURI := snapshot.View().ModFiles()
+
+	// Check the case when the tempModfile flag is turned off.
+	if realURI == "" || tempURI == "" {
+		return nil, nil
+	}
+
+	ctx, done := trace.StartSpan(ctx, "mod.SuggestedGoFixes", telemetry.File.Of(realURI))
+	defer done()
+
+	realfh, err := snapshot.GetFile(realURI)
+	if err != nil {
+		return nil, err
+	}
+	mth, err := snapshot.ModTidyHandle(ctx, realfh)
+	if err != nil {
+		return nil, err
+	}
+	realFile, realMapper, missingDeps, _, err := mth.Tidy(ctx)
+	if err != nil {
+		return nil, err
+	}
+	// Get the contents of the go.mod file before we make any changes.
+	oldContents, _, err := realfh.Read(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	var actions []protocol.CodeAction
+	for _, diag := range diags {
+		re := regexp.MustCompile(`(.+) is not in your go.mod file`)
+		matches := re.FindStringSubmatch(diag.Message)
+		if len(matches) != 2 {
+			continue
+		}
+		req := missingDeps[matches[1]]
+		if req == nil {
+			continue
+		}
+		// Calculate the quick fix edits that need to be made to the go.mod file.
+		if err := realFile.AddRequire(req.Mod.Path, req.Mod.Version); err != nil {
+			return nil, err
+		}
+		realFile.Cleanup()
+		newContents, err := realFile.Format()
+		if err != nil {
+			return nil, err
+		}
+		// Reset the *modfile.File back to before we added the dependency.
+		if err := realFile.DropRequire(req.Mod.Path); err != nil {
+			return nil, err
+		}
+		// Calculate the edits to be made due to the change.
+		diff := snapshot.View().Options().ComputeEdits(realfh.Identity().URI, string(oldContents), string(newContents))
+		edits, err := source.ToProtocolEdits(realMapper, diff)
+		if err != nil {
+			return nil, err
+		}
+		action := protocol.CodeAction{
+			Title:       fmt.Sprintf("Add %s to go.mod", req.Mod.Path),
+			Kind:        protocol.QuickFix,
+			Diagnostics: []protocol.Diagnostic{diag},
+			Edit: protocol.WorkspaceEdit{
+				DocumentChanges: []protocol.TextDocumentEdit{
+					{
+						TextDocument: protocol.VersionedTextDocumentIdentifier{
+							Version: realfh.Identity().Version,
+							TextDocumentIdentifier: protocol.TextDocumentIdentifier{
+								URI: protocol.URIFromSpanURI(realfh.Identity().URI),
+							},
+						},
+						Edits: edits,
+					},
+				},
+			},
+		}
+		actions = append(actions, action)
+	}
+	return actions, nil
+}
+
 func sameDiagnostic(d protocol.Diagnostic, e source.Error) bool {
 	return d.Message == e.Message && protocol.CompareRange(d.Range, e.Range) == 0 && d.Source == e.Category
 }
diff --git a/internal/lsp/mod/hover.go b/internal/lsp/mod/hover.go
new file mode 100644
index 0000000..a6a79b9
--- /dev/null
+++ b/internal/lsp/mod/hover.go
@@ -0,0 +1,149 @@
+package mod
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"go/token"
+	"strings"
+
+	"golang.org/x/mod/modfile"
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
+	"golang.org/x/tools/internal/span"
+	"golang.org/x/tools/internal/telemetry/trace"
+)
+
+func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, position protocol.Position) (*protocol.Hover, error) {
+	realURI, _ := snapshot.View().ModFiles()
+	// Only get hover information on the go.mod for the view.
+	if realURI == "" || fh.Identity().URI != realURI {
+		return nil, nil
+	}
+	ctx, done := trace.StartSpan(ctx, "mod.Hover")
+	defer done()
+
+	file, m, why, err := snapshot.ModHandle(ctx, fh).Why(ctx)
+	if err != nil {
+		return nil, err
+	}
+	// Get the position of the cursor.
+	spn, err := m.PointSpan(position)
+	if err != nil {
+		return nil, err
+	}
+	hoverRng, err := spn.Range(m.Converter)
+	if err != nil {
+		return nil, err
+	}
+
+	var req *modfile.Require
+	var startPos, endPos int
+	for _, r := range file.Require {
+		dep := []byte(r.Mod.Path)
+		s, e := r.Syntax.Start.Byte, r.Syntax.End.Byte
+		i := bytes.Index(m.Content[s:e], dep)
+		if i == -1 {
+			continue
+		}
+		// Shift the start position to the location of the
+		// dependency within the require statement.
+		startPos, endPos = s+i, s+i+len(dep)
+		if token.Pos(startPos) <= hoverRng.Start && hoverRng.Start <= token.Pos(endPos) {
+			req = r
+			break
+		}
+	}
+	if req == nil || why == nil {
+		return nil, nil
+	}
+	explanation, ok := why[req.Mod.Path]
+	if !ok {
+		return nil, nil
+	}
+	// Get the range to highlight for the hover.
+	line, col, err := m.Converter.ToPosition(startPos)
+	if err != nil {
+		return nil, err
+	}
+	start := span.NewPoint(line, col, startPos)
+
+	line, col, err = m.Converter.ToPosition(endPos)
+	if err != nil {
+		return nil, err
+	}
+	end := span.NewPoint(line, col, endPos)
+
+	spn = span.New(fh.Identity().URI, start, end)
+	rng, err := m.Range(spn)
+	if err != nil {
+		return nil, err
+	}
+	options := snapshot.View().Options()
+	explanation = formatExplanation(explanation, req, options)
+	return &protocol.Hover{
+		Contents: protocol.MarkupContent{
+			Kind:  options.PreferredContentFormat,
+			Value: explanation,
+		},
+		Range: rng,
+	}, nil
+}
+
+func formatExplanation(text string, req *modfile.Require, options source.Options) string {
+	text = strings.TrimSuffix(text, "\n")
+	splt := strings.Split(text, "\n")
+	length := len(splt)
+
+	var b strings.Builder
+	// Write the heading as an H3.
+	b.WriteString("##" + splt[0])
+	if options.PreferredContentFormat == protocol.Markdown {
+		b.WriteString("\n\n")
+	} else {
+		b.WriteRune('\n')
+	}
+
+	// If the explanation is 2 lines, then it is of the form:
+	// # golang.org/x/text/encoding
+	// (main module does not need package golang.org/x/text/encoding)
+	if length == 2 {
+		b.WriteString(splt[1])
+		return b.String()
+	}
+
+	imp := splt[length-1]
+	target := imp
+	if strings.ToLower(options.LinkTarget) == "pkg.go.dev" {
+		target = strings.Replace(target, req.Mod.Path, req.Mod.String(), 1)
+	}
+	target = fmt.Sprintf("https://%s/%s", options.LinkTarget, target)
+
+	b.WriteString("This module is necessary because ")
+	msg := fmt.Sprintf("[%s](%s) is imported in", imp, target)
+	b.WriteString(msg)
+
+	// If the explanation is 3 lines, then it is of the form:
+	// # golang.org/x/tools
+	// modtest
+	// golang.org/x/tools/go/packages
+	if length == 3 {
+		msg := fmt.Sprintf(" `%s`.", splt[1])
+		b.WriteString(msg)
+		return b.String()
+	}
+
+	// If the explanation is more than 3 lines, then it is of the form:
+	// # golang.org/x/text/language
+	// rsc.io/quote
+	// rsc.io/sampler
+	// golang.org/x/text/language
+	b.WriteString(":\n```text")
+	dash := ""
+	for _, imp := range splt[1 : length-1] {
+		dash += "-"
+		b.WriteString("\n" + dash + " " + imp)
+	}
+	b.WriteString("\n```")
+	return b.String()
+}
diff --git a/internal/lsp/mod/mod_test.go b/internal/lsp/mod/mod_test.go
index 7a149c1..ada211c 100644
--- a/internal/lsp/mod/mod_test.go
+++ b/internal/lsp/mod/mod_test.go
@@ -5,15 +5,12 @@
 package mod
 
 import (
-	"context"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"testing"
 
 	"golang.org/x/tools/internal/lsp/cache"
-	"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"
 	"golang.org/x/tools/internal/testenv"
@@ -24,11 +21,9 @@
 	os.Exit(m.Run())
 }
 
-// TODO(golang/go#36091): This file can be refactored to look like lsp_test.go
-// when marker support gets added for go.mod files.
 func TestModfileRemainsUnchanged(t *testing.T) {
 	ctx := tests.Context(t)
-	cache := cache.New(nil)
+	cache := cache.New(nil, nil)
 	session := cache.NewSession()
 	options := tests.DefaultOptions()
 	options.TempModfile = true
@@ -46,11 +41,11 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	_, snapshot, err := session.NewView(ctx, "diagnostics_test", span.FileURI(folder), options)
+	_, snapshot, err := session.NewView(ctx, "diagnostics_test", span.URIFromPath(folder), options)
 	if err != nil {
 		t.Fatal(err)
 	}
-	if !hasTempModfile(ctx, snapshot) {
+	if _, t := snapshot.View().ModFiles(); t == "" {
 		return
 	}
 	after, err := ioutil.ReadFile(filepath.Join(folder, "go.mod"))
@@ -61,119 +56,3 @@
 		t.Errorf("the real go.mod file was changed even when tempModfile=true")
 	}
 }
-
-// TODO(golang/go#36091): This file can be refactored to look like lsp_test.go
-// when marker support gets added for go.mod files.
-func TestDiagnostics(t *testing.T) {
-	ctx := tests.Context(t)
-	cache := cache.New(nil)
-	session := cache.NewSession()
-	options := tests.DefaultOptions()
-	options.TempModfile = true
-	options.Env = append(os.Environ(), "GOPACKAGESDRIVER=off", "GOROOT=")
-
-	for _, tt := range []struct {
-		testdir string
-		want    []source.Diagnostic
-	}{
-		{
-			testdir: "indirect",
-			want: []source.Diagnostic{
-				{
-					Message: "golang.org/x/tools should be a direct dependency.",
-					Source:  "go mod tidy",
-					// TODO(golang/go#36091): When marker support gets added for go.mod files, we
-					// can remove these hard coded positions.
-					Range:    protocol.Range{Start: getRawPos(4, 62), End: getRawPos(4, 73)},
-					Severity: protocol.SeverityWarning,
-				},
-			},
-		},
-		{
-			testdir: "unused",
-			want: []source.Diagnostic{
-				{
-					Message:  "golang.org/x/tools is not used in this module.",
-					Source:   "go mod tidy",
-					Range:    protocol.Range{Start: getRawPos(4, 0), End: getRawPos(4, 61)},
-					Severity: protocol.SeverityWarning,
-				},
-			},
-		},
-		{
-			testdir: "invalidrequire",
-			want: []source.Diagnostic{
-				{
-					Message:  "usage: require module/path v1.2.3",
-					Source:   "syntax",
-					Range:    protocol.Range{Start: getRawPos(4, 0), End: getRawPos(4, 16)},
-					Severity: protocol.SeverityError,
-				},
-			},
-		},
-		{
-			testdir: "invalidgo",
-			want: []source.Diagnostic{
-				{
-					Message:  "usage: go 1.23",
-					Source:   "syntax",
-					Range:    protocol.Range{Start: getRawPos(2, 0), End: getRawPos(2, 3)},
-					Severity: protocol.SeverityError,
-				},
-			},
-		},
-		{
-			testdir: "unknowndirective",
-			want: []source.Diagnostic{
-				{
-					Message:  "unknown directive: yo",
-					Source:   "syntax",
-					Range:    protocol.Range{Start: getRawPos(6, 0), End: getRawPos(6, 1)},
-					Severity: protocol.SeverityError,
-				},
-			},
-		},
-	} {
-		t.Run(tt.testdir, func(t *testing.T) {
-			// TODO: Once we refactor this to work with go/packages/packagestest. We do not
-			// need to copy to a temporary directory.
-			folder, err := tests.CopyFolderToTempDir(filepath.Join("testdata", tt.testdir))
-			if err != nil {
-				t.Fatal(err)
-			}
-			defer os.RemoveAll(folder)
-			_, snapshot, err := session.NewView(ctx, "diagnostics_test", span.FileURI(folder), options)
-			if err != nil {
-				t.Fatal(err)
-			}
-			// TODO: Add testing for when the -modfile flag is turned off and we still get diagnostics.
-			if !hasTempModfile(ctx, snapshot) {
-				return
-			}
-			reports, err := Diagnostics(ctx, snapshot)
-			if err != nil {
-				t.Fatal(err)
-			}
-			if len(reports) != 1 {
-				t.Errorf("expected 1 diagnostic, got %d", len(reports))
-			}
-			for fh, got := range reports {
-				if diff := tests.DiffDiagnostics(fh.URI, tt.want, got); diff != "" {
-					t.Error(diff)
-				}
-			}
-		})
-	}
-}
-
-func hasTempModfile(ctx context.Context, snapshot source.Snapshot) bool {
-	_, t := snapshot.View().ModFiles()
-	return t != ""
-}
-
-func getRawPos(line, character int) protocol.Position {
-	return protocol.Position{
-		Line:      float64(line),
-		Character: float64(character),
-	}
-}
diff --git a/internal/lsp/mod/testdata/indirect/go.mod b/internal/lsp/mod/testdata/indirect/go.mod
deleted file mode 100644
index 2e5dc13..0000000
--- a/internal/lsp/mod/testdata/indirect/go.mod
+++ /dev/null
@@ -1,5 +0,0 @@
-module indirect
-
-go 1.12
-
-require golang.org/x/tools v0.0.0-20191219192050-56b0b28a00f7 // indirect
diff --git a/internal/lsp/mod/testdata/indirect/go.mod.golden b/internal/lsp/mod/testdata/indirect/go.mod.golden
deleted file mode 100644
index 7e4be77..0000000
--- a/internal/lsp/mod/testdata/indirect/go.mod.golden
+++ /dev/null
@@ -1,5 +0,0 @@
-module indirect
-
-go 1.12
-
-require golang.org/x/tools v0.0.0-20191219192050-56b0b28a00f7
diff --git a/internal/lsp/mod/testdata/indirect/go.sum b/internal/lsp/mod/testdata/indirect/go.sum
deleted file mode 100644
index 8fec86c..0000000
--- a/internal/lsp/mod/testdata/indirect/go.sum
+++ /dev/null
@@ -1,12 +0,0 @@
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20191219192050-56b0b28a00f7 h1:u+nComwpgIe2VK1OTg8C74VQWda+MuB+wkIEsqFeoxY=
-golang.org/x/tools v0.0.0-20191219192050-56b0b28a00f7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/lsp/mod/testdata/indirect/main.go b/internal/lsp/mod/testdata/indirect/main.go
deleted file mode 100644
index a596abf..0000000
--- a/internal/lsp/mod/testdata/indirect/main.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// Package indirect does something
-package indirect
-
-import (
-	"golang.org/x/tools/go/packages"
-)
-
-func Yo() {
-	var _ packages.Config
-}
diff --git a/internal/lsp/mod/testdata/invalidgo/go.mod b/internal/lsp/mod/testdata/invalidgo/go.mod
deleted file mode 100644
index ca06b60..0000000
--- a/internal/lsp/mod/testdata/invalidgo/go.mod
+++ /dev/null
@@ -1,5 +0,0 @@
-module invalidgo
-
-go 1
-
-require golang.org/x/tools v0.0.0-20191219192050-56b0b28a00f7 // indirect
diff --git a/internal/lsp/mod/testdata/invalidgo/main.go b/internal/lsp/mod/testdata/invalidgo/main.go
deleted file mode 100644
index 5577a36..0000000
--- a/internal/lsp/mod/testdata/invalidgo/main.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// Package invalidgo does something
-package invalidgo
-
-import (
-	"golang.org/x/tools/go/packages"
-)
-
-func Yo() {
-	var _ packages.Config
-}
diff --git a/internal/lsp/mod/testdata/invalidrequire/go.mod b/internal/lsp/mod/testdata/invalidrequire/go.mod
deleted file mode 100644
index 98c5b05..0000000
--- a/internal/lsp/mod/testdata/invalidrequire/go.mod
+++ /dev/null
@@ -1,5 +0,0 @@
-module invalidrequire
-
-go 1.12
-
-require golang.or
diff --git a/internal/lsp/mod/testdata/invalidrequire/main.go b/internal/lsp/mod/testdata/invalidrequire/main.go
deleted file mode 100644
index dd24341..0000000
--- a/internal/lsp/mod/testdata/invalidrequire/main.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// Package invalidrequire does something
-package invalidrequire
-
-import (
-	"golang.org/x/tools/go/packages"
-)
-
-func Yo() {
-	var _ packages.Config
-}
diff --git a/internal/lsp/mod/testdata/unchanged/go.mod b/internal/lsp/mod/testdata/unchanged/go.mod
index e5bdaef..e3d13ce 100644
--- a/internal/lsp/mod/testdata/unchanged/go.mod
+++ b/internal/lsp/mod/testdata/unchanged/go.mod
@@ -1,3 +1 @@
 module unchanged
-
-go 1.14
diff --git a/internal/lsp/mod/testdata/unknowndirective/go.mod b/internal/lsp/mod/testdata/unknowndirective/go.mod
deleted file mode 100644
index 4f07729..0000000
--- a/internal/lsp/mod/testdata/unknowndirective/go.mod
+++ /dev/null
@@ -1,7 +0,0 @@
-module unknowndirective
-
-go 1.12
-
-require golang.org/x/tools v0.0.0-20191219192050-56b0b28a00f7
-
-yo
diff --git a/internal/lsp/mod/testdata/unknowndirective/main.go b/internal/lsp/mod/testdata/unknowndirective/main.go
deleted file mode 100644
index 5ee984e..0000000
--- a/internal/lsp/mod/testdata/unknowndirective/main.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// Package unknowndirective does something
-package unknowndirective
-
-import (
-	"golang.org/x/tools/go/packages"
-)
-
-func Yo() {
-	var _ packages.Config
-}
diff --git a/internal/lsp/mod/testdata/unused/go.mod b/internal/lsp/mod/testdata/unused/go.mod
deleted file mode 100644
index 2c2f19c..0000000
--- a/internal/lsp/mod/testdata/unused/go.mod
+++ /dev/null
@@ -1,5 +0,0 @@
-module unused
-
-go 1.12
-
-require golang.org/x/tools v0.0.0-20191219192050-56b0b28a00f7
diff --git a/internal/lsp/mod/testdata/unused/go.mod.golden b/internal/lsp/mod/testdata/unused/go.mod.golden
deleted file mode 100644
index 34ca63a..0000000
--- a/internal/lsp/mod/testdata/unused/go.mod.golden
+++ /dev/null
@@ -1,3 +0,0 @@
-module unused
-
-go 1.12
diff --git a/internal/lsp/mod/testdata/unused/go.sum b/internal/lsp/mod/testdata/unused/go.sum
deleted file mode 100644
index e8a4a48..0000000
--- a/internal/lsp/mod/testdata/unused/go.sum
+++ /dev/null
@@ -1,11 +0,0 @@
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20191219192050-56b0b28a00f7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/lsp/protocol/context.go b/internal/lsp/protocol/context.go
index d024e00..7e9246d 100644
--- a/internal/lsp/protocol/context.go
+++ b/internal/lsp/protocol/context.go
@@ -5,14 +5,9 @@
 	"fmt"
 
 	"golang.org/x/tools/internal/telemetry"
-	"golang.org/x/tools/internal/telemetry/export"
 	"golang.org/x/tools/internal/xcontext"
 )
 
-func init() {
-	export.AddExporters(logExporter{})
-}
-
 type contextKey int
 
 const (
@@ -23,16 +18,7 @@
 	return context.WithValue(ctx, clientKey, client)
 }
 
-// logExporter sends the log event back to the client if there is one stored on the
-// context.
-type logExporter struct{}
-
-func (logExporter) StartSpan(context.Context, *telemetry.Span)   {}
-func (logExporter) FinishSpan(context.Context, *telemetry.Span)  {}
-func (logExporter) Metric(context.Context, telemetry.MetricData) {}
-func (logExporter) Flush()                                       {}
-
-func (logExporter) Log(ctx context.Context, event telemetry.Event) {
+func LogEvent(ctx context.Context, event telemetry.Event) {
 	client, ok := ctx.Value(clientKey).(Client)
 	if !ok {
 		return
diff --git a/internal/lsp/protocol/protocol.go b/internal/lsp/protocol/protocol.go
index 8b3777f..d66941a 100644
--- a/internal/lsp/protocol/protocol.go
+++ b/internal/lsp/protocol/protocol.go
@@ -20,19 +20,44 @@
 	RequestCancelledError = -32800
 )
 
-type canceller struct{ jsonrpc2.EmptyHandler }
-
 type clientHandler struct {
 	jsonrpc2.EmptyHandler
 	client Client
 }
 
+// ClientHandler returns a jsonrpc2.Handler that handles the LSP client
+// protocol.
+func ClientHandler(client Client) jsonrpc2.Handler {
+	return &clientHandler{client: client}
+}
+
 type serverHandler struct {
 	jsonrpc2.EmptyHandler
 	server Server
 }
 
-func (canceller) Request(ctx context.Context, conn *jsonrpc2.Conn, direction jsonrpc2.Direction, r *jsonrpc2.WireRequest) context.Context {
+// ServerHandler returns a jsonrpc2.Handler that handles the LSP server
+// protocol.
+func ServerHandler(server Server) jsonrpc2.Handler {
+	return &serverHandler{server: server}
+}
+
+// ClientDispatcher returns a Client that dispatches LSP requests across the
+// given jsonrpc2 connection.
+func ClientDispatcher(conn *jsonrpc2.Conn) Client {
+	return &clientDispatcher{Conn: conn}
+}
+
+// ServerDispatcher returns a Server that dispatches LSP requests across the
+// given jsonrpc2 connection.
+func ServerDispatcher(conn *jsonrpc2.Conn) Server {
+	return &serverDispatcher{Conn: conn}
+}
+
+// Canceller is a jsonrpc2.Handler that handles LSP request cancellation.
+type Canceller struct{ jsonrpc2.EmptyHandler }
+
+func (Canceller) Request(ctx context.Context, conn *jsonrpc2.Conn, direction jsonrpc2.Direction, r *jsonrpc2.WireRequest) context.Context {
 	if direction == jsonrpc2.Receive && r.Method == "$/cancelRequest" {
 		var params CancelParams
 		if err := json.Unmarshal(*r.Params, &params); err != nil {
@@ -53,39 +78,23 @@
 	return ctx
 }
 
-func (canceller) Cancel(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID, cancelled bool) bool {
+func (Canceller) Cancel(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID, cancelled bool) bool {
 	if cancelled {
 		return false
 	}
 	ctx = xcontext.Detach(ctx)
 	ctx, done := trace.StartSpan(ctx, "protocol.canceller")
 	defer done()
-	conn.Notify(ctx, "$/cancelRequest", &CancelParams{ID: id})
+	// Note that only *jsonrpc2.ID implements json.Marshaler.
+	conn.Notify(ctx, "$/cancelRequest", &CancelParams{ID: &id})
 	return true
 }
 
-func (canceller) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
+func (Canceller) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
 	// Hide cancellations from downstream handlers.
 	return r.Method == "$/cancelRequest"
 }
 
-func NewClient(ctx context.Context, stream jsonrpc2.Stream, client Client) (context.Context, *jsonrpc2.Conn, Server) {
-	ctx = WithClient(ctx, client)
-	conn := jsonrpc2.NewConn(stream)
-	conn.AddHandler(&clientHandler{client: client})
-	conn.AddHandler(&canceller{})
-	return ctx, conn, &serverDispatcher{Conn: conn}
-}
-
-func NewServer(ctx context.Context, stream jsonrpc2.Stream, server Server) (context.Context, *jsonrpc2.Conn, Client) {
-	conn := jsonrpc2.NewConn(stream)
-	client := &clientDispatcher{Conn: conn}
-	ctx = WithClient(ctx, client)
-	conn.AddHandler(&serverHandler{server: server})
-	conn.AddHandler(&canceller{})
-	return ctx, conn, client
-}
-
 func sendParseError(ctx context.Context, req *jsonrpc2.Request, err error) {
 	if _, ok := err.(*jsonrpc2.Error); !ok {
 		err = jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err)
diff --git a/internal/lsp/protocol/span.go b/internal/lsp/protocol/span.go
index 5c9c4d1..8363d5c 100644
--- a/internal/lsp/protocol/span.go
+++ b/internal/lsp/protocol/span.go
@@ -19,8 +19,16 @@
 	Content   []byte
 }
 
-func NewURI(uri span.URI) string {
-	return string(uri)
+func URIFromSpanURI(uri span.URI) DocumentURI {
+	return DocumentURI(uri)
+}
+
+func URIFromPath(path string) DocumentURI {
+	return URIFromSpanURI(span.URIFromPath(path))
+}
+
+func (u DocumentURI) SpanURI() span.URI {
+	return span.URIFromURI(string(u))
 }
 
 func (m *ColumnMapper) Location(s span.Span) (Location, error) {
@@ -28,7 +36,7 @@
 	if err != nil {
 		return Location{}, err
 	}
-	return Location{URI: NewURI(s.URI()), Range: rng}, nil
+	return Location{URI: URIFromSpanURI(s.URI()), Range: rng}, nil
 }
 
 func (m *ColumnMapper) Range(s span.Span) (Range, error) {
diff --git a/internal/lsp/protocol/tsclient.go b/internal/lsp/protocol/tsclient.go
index ab2abc9..53327ed 100644
--- a/internal/lsp/protocol/tsclient.go
+++ b/internal/lsp/protocol/tsclient.go
@@ -3,7 +3,7 @@
 // Package protocol contains data types and code for LSP jsonrpcs
 // generated automatically from vscode-languageserver-node
 // commit: 7b90c29d0cb5cd7b9c41084f6cb3781a955adeba
-// last fetched Thu Jan 23 2020 11:10:31 GMT-0500 (Eastern Standard Time)
+// last fetched Wed Feb 12 2020 17:16:47 GMT-0500 (Eastern Standard Time)
 
 // Code generated (see typescript/README.md) DO NOT EDIT.
 
diff --git a/internal/lsp/protocol/tsprotocol.go b/internal/lsp/protocol/tsprotocol.go
index e8be364..f5b66fe 100644
--- a/internal/lsp/protocol/tsprotocol.go
+++ b/internal/lsp/protocol/tsprotocol.go
@@ -1,7 +1,7 @@
 // Package protocol contains data types and code for LSP jsonrpcs
 // generated automatically from vscode-languageserver-node
 // commit: 7b90c29d0cb5cd7b9c41084f6cb3781a955adeba
-// last fetched Thu Jan 23 2020 11:10:31 GMT-0500 (Eastern Standard Time)
+// last fetched Wed Feb 12 2020 17:16:47 GMT-0500 (Eastern Standard Time)
 package protocol
 
 // Code generated (see typescript/README.md) DO NOT EDIT.
@@ -1532,7 +1532,7 @@
 /**
  * A tagging type for string properties that are actually URIs.
  */
-type DocumentURI = string
+type DocumentURI string
 
 /**
  * The client capabilities of a [ExecuteCommandRequest](#ExecuteCommandRequest).
diff --git a/internal/lsp/protocol/tsserver.go b/internal/lsp/protocol/tsserver.go
index 24a9ad1..7853449 100644
--- a/internal/lsp/protocol/tsserver.go
+++ b/internal/lsp/protocol/tsserver.go
@@ -3,7 +3,7 @@
 // Package protocol contains data types and code for LSP jsonrpcs
 // generated automatically from vscode-languageserver-node
 // commit: 7b90c29d0cb5cd7b9c41084f6cb3781a955adeba
-// last fetched Thu Jan 23 2020 11:10:31 GMT-0500 (Eastern Standard Time)
+// last fetched Wed Feb 12 2020 17:16:47 GMT-0500 (Eastern Standard Time)
 
 // Code generated (see typescript/README.md) DO NOT EDIT.
 
@@ -60,7 +60,7 @@
 	RangeFormatting(context.Context, *DocumentRangeFormattingParams) ([]TextEdit /*TextEdit[] | null*/, error)
 	OnTypeFormatting(context.Context, *DocumentOnTypeFormattingParams) ([]TextEdit /*TextEdit[] | null*/, error)
 	Rename(context.Context, *RenameParams) (*WorkspaceEdit /*WorkspaceEdit | null*/, error)
-	PrepareRename(context.Context, *PrepareRenameParams) (interface{} /* Range | struct{;  Range Range`json:"range"`;  Placeholder string`json:"placeholder"`; } | nil*/, error)
+	PrepareRename(context.Context, *PrepareRenameParams) (*Range /*Range | { range: Range, placeholder: string } | null*/, error)
 	ExecuteCommand(context.Context, *ExecuteCommandParams) (interface{} /*any | null*/, error)
 	PrepareCallHierarchy(context.Context, *CallHierarchyPrepareParams) ([]CallHierarchyItem /*CallHierarchyItem[] | null*/, error)
 	IncomingCalls(context.Context, *CallHierarchyIncomingCallsParams) ([]CallHierarchyIncomingCall /*CallHierarchyIncomingCall[] | null*/, error)
@@ -920,12 +920,12 @@
 	return &result, nil
 }
 
-func (s *serverDispatcher) PrepareRename(ctx context.Context, params *PrepareRenameParams) (interface{} /* Range | struct{;  Range Range`json:"range"`;  Placeholder string`json:"placeholder"`; } | nil*/, error) {
-	var result interface{} /* Range | struct{;  Range Range`json:"range"`;  Placeholder string`json:"placeholder"`; } | nil*/
+func (s *serverDispatcher) PrepareRename(ctx context.Context, params *PrepareRenameParams) (*Range /*Range | { range: Range, placeholder: string } | null*/, error) {
+	var result Range /*Range | { range: Range, placeholder: string } | null*/
 	if err := s.Conn.Call(ctx, "textDocument/prepareRename", params, &result); err != nil {
 		return nil, err
 	}
-	return result, nil
+	return &result, nil
 }
 
 func (s *serverDispatcher) ExecuteCommand(ctx context.Context, params *ExecuteCommandParams) (interface{} /*any | null*/, error) {
diff --git a/internal/lsp/protocol/typescript/code.ts b/internal/lsp/protocol/typescript/code.ts
index c37f62b..fe35d80 100644
--- a/internal/lsp/protocol/typescript/code.ts
+++ b/internal/lsp/protocol/typescript/code.ts
@@ -19,15 +19,15 @@
 import * as fs from 'fs';
 import * as ts from 'typescript';
 import * as u from './util';
-import { constName, getComments, goName, loc, strKind } from './util';
+import {constName, getComments, goName, loc, strKind} from './util';
 
 var program: ts.Program;
 
 function parse() {
   // this won't complain if some fnames don't exist
   program = ts.createProgram(
-    u.fnames,
-    { target: ts.ScriptTarget.ES2018, module: ts.ModuleKind.CommonJS });
+      u.fnames,
+      {target: ts.ScriptTarget.ES2018, module: ts.ModuleKind.CommonJS});
   program.getTypeChecker();  // finish type checking and assignment
 }
 
@@ -35,14 +35,16 @@
 let req = new Map<string, ts.NewExpression>();               // requests
 let not = new Map<string, ts.NewExpression>();               // notifications
 let ptypes = new Map<string, [ts.TypeNode, ts.TypeNode]>();  // req, resp types
-let receives = new Map<string, 'server' | 'client'>();         // who receives it
+let receives = new Map<string, 'server'|'client'>();         // who receives it
 let rpcTypes = new Set<string>();  // types seen in the rpcs
 
 function findRPCs(node: ts.Node) {
   if (!ts.isModuleDeclaration(node)) {
     return
-  } if (!ts.isIdentifier(node.name)) {
-    throw new Error(`expected Identifier, got ${strKind(node.name)} at ${loc(node)}`)
+  }
+  if (!ts.isIdentifier(node.name)) {
+    throw new Error(
+        `expected Identifier, got ${strKind(node.name)} at ${loc(node)}`)
   }
   let reqnot = req
   let v = node.name.getText()
@@ -50,7 +52,8 @@
   else if (!v.endsWith('Request')) return;
 
   if (!ts.isModuleBlock(node.body)) {
-    throw new Error(`expected ModuleBody got ${strKind(node.body)} at ${loc(node)}`)
+    throw new Error(
+        `expected ModuleBody got ${strKind(node.body)} at ${loc(node)}`)
   }
   let x: ts.ModuleBlock = node.body
   // The story is to expect const method = 'textDocument/implementation'
@@ -59,48 +62,53 @@
   let rpc: string = '';
   let newNode: ts.NewExpression;
   for (let i = 0; i < x.statements.length; i++) {
-    const uu = x.statements[i]
+    const uu = x.statements[i];
     if (!ts.isVariableStatement(uu)) continue;
-    const dl: ts.VariableDeclarationList = uu.declarationList
-    if (dl.declarations.length != 1) throw new Error(`expected a single decl at ${loc(dl)}`)
-    const decl: ts.VariableDeclaration = dl.declarations[0]
+    const dl: ts.VariableDeclarationList = uu.declarationList;
+    if (dl.declarations.length != 1)
+      throw new Error(`expected a single decl at ${loc(dl)}`);
+    const decl: ts.VariableDeclaration = dl.declarations[0];
     const name = decl.name.getText()
     // we want the initializers
-    if (name == 'method') { // StringLiteral
-      if (!ts.isStringLiteral(decl.initializer)) throw new Error(`expect StringLiteral at ${loc(decl)}`)
+    if (name == 'method') {  // StringLiteral
+      if (!ts.isStringLiteral(decl.initializer))
+        throw new Error(`expect StringLiteral at ${loc(decl)}`);
       rpc = decl.initializer.getText()
-    } else if (name == 'type') { // NewExpression
-      if (!ts.isNewExpression(decl.initializer)) throw new Error(`expecte new at ${loc(decl)}`)
+    }
+    else if (name == 'type') {  // NewExpression
+      if (!ts.isNewExpression(decl.initializer))
+        throw new Error(`expecte new at ${loc(decl)}`);
       const nn: ts.NewExpression = decl.initializer
       newNode = nn
-      const mtd = nn.arguments[0]
+      const mtd = nn.arguments[0];
       if (ts.isStringLiteral(mtd)) rpc = mtd.getText();
       switch (nn.typeArguments.length) {
-        case 1: // exit
+        case 1:  // exit
           ptypes.set(rpc, [nn.typeArguments[0], null])
           break;
-        case 2: // notifications
+        case 2:  // notifications
           ptypes.set(rpc, [nn.typeArguments[0], null])
           break;
-        case 4:// request with no parameters
+        case 4:  // request with no parameters
           ptypes.set(rpc, [null, nn.typeArguments[0]])
           break;
-        case 5: // request req, resp, partial(?)
+        case 5:  // request req, resp, partial(?)
           ptypes.set(rpc, [nn.typeArguments[0], nn.typeArguments[1]])
           break;
-        default: throw new Error(`${nn.typeArguments.length} at ${loc(nn)}`)
+        default:
+          throw new Error(`${nn.typeArguments.length} at ${loc(nn)}`)
       }
     }
   }
-  if (rpc == '') throw new Error(`no name found at ${loc(x)}`)
+  if (rpc == '') throw new Error(`no name found at ${loc(x)}`);
   // remember the implied types
   const [a, b] = ptypes.get(rpc);
-  const add = function (n: ts.Node) {
+  const add = function(n: ts.Node) {
     rpcTypes.add(goName(n.getText()))
   };
   underlying(a, add);
   underlying(b, add);
-  rpc = rpc.substring(1, rpc.length - 1) // 'exit'
+  rpc = rpc.substring(1, rpc.length - 1);  // 'exit'
   reqnot.set(rpc, newNode)
 }
 
@@ -122,8 +130,8 @@
   // it would be nice to have some independent check on this
   // (this logic fails if the server ever sends $/canceRequest
   //  or $/progress)
-  req.forEach((_, k) => { receives.set(k, 'server') });
-  not.forEach((_, k) => { receives.set(k, 'server') });
+  req.forEach((_, k) => {receives.set(k, 'server')});
+  not.forEach((_, k) => {receives.set(k, 'server')});
   receives.set('window/showMessage', 'client');
   receives.set('window/showMessageRequest', 'client');
   receives.set('window/logMessage', 'client');
@@ -158,22 +166,22 @@
 function newData(n: ts.Node, nm: string): Data {
   return {
     me: n, name: goName(nm),
-    generics: ts.createNodeArray<ts.TypeParameterDeclaration>(), as: ts.createNodeArray<ts.HeritageClause>(),
-    properties: ts.createNodeArray<ts.TypeElement>(), alias: undefined,
-    statements: ts.createNodeArray<ts.Statement>(),
-    enums: ts.createNodeArray<ts.EnumMember>(),
-    members: ts.createNodeArray<ts.PropertyDeclaration>(),
+        generics: ts.createNodeArray<ts.TypeParameterDeclaration>(), as: ts.createNodeArray<ts.HeritageClause>(),
+        properties: ts.createNodeArray<ts.TypeElement>(), alias: undefined,
+        statements: ts.createNodeArray<ts.Statement>(),
+        enums: ts.createNodeArray<ts.EnumMember>(),
+        members: ts.createNodeArray<ts.PropertyDeclaration>(),
   }
 }
 
 // for debugging, produce a skeleton description
 function strData(d: Data): string {
-  const f = function (na: ts.NodeArray<any>): number {
+  const f = function(na: ts.NodeArray<any>): number {
     return na.length
   };
   return `D(${d.name}) g;${f(d.generics)} a:${f(d.as)} p:${f(d.properties)} s:${
-    f(d.statements)} e:${f(d.enums)} m:${f(d.members)} ${
-    d.alias != undefined}`
+      f(d.statements)} e:${f(d.enums)} m:${f(d.members)} ${
+      d.alias != undefined}`
 }
 
 let data = new Map<string, Data>();            // parsed data types
@@ -184,16 +192,16 @@
 function genTypes(node: ts.Node) {
   // Ignore top-level items that can't produce output
   if (ts.isExpressionStatement(node) || ts.isFunctionDeclaration(node) ||
-    ts.isImportDeclaration(node) || ts.isVariableStatement(node) ||
-    ts.isExportDeclaration(node) || ts.isEmptyStatement(node) ||
-    node.kind == ts.SyntaxKind.EndOfFileToken) {
+      ts.isImportDeclaration(node) || ts.isVariableStatement(node) ||
+      ts.isExportDeclaration(node) || ts.isEmptyStatement(node) ||
+      node.kind == ts.SyntaxKind.EndOfFileToken) {
     return;
   }
   if (ts.isInterfaceDeclaration(node)) {
     const v: ts.InterfaceDeclaration = node;
     // need to check the members, many of which are disruptive
     let mems: ts.TypeElement[] = [];
-    const f = function (t: ts.TypeElement) {
+    const f = function(t: ts.TypeElement) {
       if (ts.isPropertySignature(t)) {
         mems.push(t);
       } else if (ts.isMethodSignature(t) || ts.isCallSignatureDeclaration(t)) {
@@ -208,7 +216,7 @@
     };
     v.members.forEach(f);
     if (mems.length == 0 && !v.heritageClauses &&
-      v.name.getText() != 'InitializedParams') {
+        v.name.getText() != 'InitializedParams') {
       return  // really? (Don't seem to need any of these)
     };
     // Found one we want
@@ -232,7 +240,7 @@
     // (at the top level)
     // Unfortunately this is false for TraceValues
     if (ts.isUnionTypeNode(v.type) &&
-      v.type.types.every((n: ts.TypeNode) => ts.isLiteralTypeNode(n))) {
+        v.type.types.every((n: ts.TypeNode) => ts.isLiteralTypeNode(n))) {
       if (x.name != 'TraceValues') return;
     }
     if (v.typeParameters) {
@@ -251,7 +259,7 @@
     const b: ts.ModuleBlock = v.body;
     var s: ts.Statement[] = [];
     // we don't want most of these
-    const fx = function (x: ts.Statement) {
+    const fx = function(x: ts.Statement) {
       if (ts.isFunctionDeclaration(x)) {
         return
       };
@@ -259,7 +267,8 @@
         return
       };
       if (!ts.isVariableStatement(x))
-        throw new Error(`expected VariableStatment ${loc(x)} ${strKind(x)} ${x.getText()}`);
+        throw new Error(
+            `expected VariableStatment ${loc(x)} ${strKind(x)} ${x.getText()}`);
       if (hasNewExpression(x)) {
         return
       };
@@ -285,7 +294,7 @@
     const v: ts.ClassDeclaration = node;
     var d: ts.PropertyDeclaration[] = [];
     // look harder at the PropertyDeclarations.
-    const wanted = function (c: ts.ClassElement): string {
+    const wanted = function(c: ts.ClassElement): string {
       if (ts.isConstructorDeclaration(c)) {
         return ''
       };
@@ -320,7 +329,7 @@
       c.as = v.heritageClauses
     }
     if (data.has(c.name))
-      throw new Error(`Class dup ${loc(c.me)} and ${loc(data.get(c.name).me)}`)
+      throw new Error(`Class dup ${loc(c.me)} and ${loc(data.get(c.name).me)}`);
     data.set(c.name, c);
   } else {
     throw new Error(`unexpected ${strKind(node)} ${loc(node)} `)
@@ -336,7 +345,8 @@
   }
   const ax = `(${a.statements.length},${a.properties.length})`
   const bx = `(${b.statements.length},${b.properties.length})`
-  //console.log(`397 ${a.name}${ax}${bx}\n${a.me.getText()}\n${b.me.getText()}\n`)
+  // console.log(`397
+  // ${a.name}${ax}${bx}\n${a.me.getText()}\n${b.me.getText()}\n`)
   switch (a.name) {
     case 'InitializeError':
     case 'MessageType':
@@ -348,11 +358,11 @@
     case 'CancellationToken':
       // want the Interface
       return a.properties.length > 0 ? a : b;
-    case 'TextDocumentContentChangeEvent': // almost the same
+    case 'TextDocumentContentChangeEvent':  // almost the same
       return a;
   }
   console.log(
-    `${strKind(a.me)} ${strKind(b.me)} ${a.name} ${loc(a.me)} ${loc(b.me)}`)
+      `${strKind(a.me)} ${strKind(b.me)} ${a.name} ${loc(a.me)} ${loc(b.me)}`)
   throw new Error(`Fix dataMerge for ${a.name}`)
 }
 
@@ -375,19 +385,19 @@
 // helper function to find underlying types
 function underlying(n: ts.Node, f: (n: ts.Node) => void) {
   if (!n) return;
-  const ff = function (n: ts.Node) {
+  const ff = function(n: ts.Node) {
     underlying(n, f)
   };
   if (ts.isIdentifier(n)) {
     f(n)
   } else if (
-    n.kind == ts.SyntaxKind.StringKeyword ||
-    n.kind == ts.SyntaxKind.NumberKeyword ||
-    n.kind == ts.SyntaxKind.AnyKeyword ||
-    n.kind == ts.SyntaxKind.NullKeyword ||
-    n.kind == ts.SyntaxKind.BooleanKeyword ||
-    n.kind == ts.SyntaxKind.ObjectKeyword ||
-    n.kind == ts.SyntaxKind.VoidKeyword) {
+      n.kind == ts.SyntaxKind.StringKeyword ||
+      n.kind == ts.SyntaxKind.NumberKeyword ||
+      n.kind == ts.SyntaxKind.AnyKeyword ||
+      n.kind == ts.SyntaxKind.NullKeyword ||
+      n.kind == ts.SyntaxKind.BooleanKeyword ||
+      n.kind == ts.SyntaxKind.ObjectKeyword ||
+      n.kind == ts.SyntaxKind.VoidKeyword) {
     // nothing to do
   } else if (ts.isTypeReferenceNode(n)) {
     f(n.typeName)
@@ -408,8 +418,8 @@
   } else if (ts.isParenthesizedTypeNode(n)) {
     underlying(n.type, f)
   } else if (
-    ts.isLiteralTypeNode(n) || ts.isVariableStatement(n) ||
-    ts.isTupleTypeNode(n)) {
+      ts.isLiteralTypeNode(n) || ts.isVariableStatement(n) ||
+      ts.isTupleTypeNode(n)) {
     // we only see these in moreTypes, but they are handled elsewhere
     return;
   } else if (ts.isEnumMember(n)) {
@@ -424,8 +434,8 @@
 // Simplest way to the transitive closure is to stabilize the size of seenTypes
 // but it is slow
 function moreTypes() {
-  const extra = function (s: string) {
-    if (!data.has(s)) throw new Error(`moreTypes needs ${s}`)
+  const extra = function(s: string) {
+    if (!data.has(s)) throw new Error(`moreTypes needs ${s}`);
     seenTypes.set(s, data.get(s))
   };
   rpcTypes.forEach(extra);  // all the types needed by the rpcs
@@ -441,17 +451,17 @@
     old = seenTypes.size
 
     const m = new Map<string, Data>();
-    const add = function (n: ts.Node) {
+    const add = function(n: ts.Node) {
       const nm = goName(n.getText());
       if (seenTypes.has(nm) || m.has(nm)) return;
       // For generic parameters, this might set it to undefined
       m.set(nm, data.get(nm));
     };
     // expect all the heritage clauses have single Identifiers
-    const h = function (n: ts.Node) {
+    const h = function(n: ts.Node) {
       underlying(n, add);
     };
-    const f = function (x: ts.NodeArray<ts.Node>) {
+    const f = function(x: ts.NodeArray<ts.Node>) {
       x.forEach(h)
     };
     seenTypes.forEach((d: Data) => d && f(d.as))
@@ -480,11 +490,11 @@
   } else if (d.enums.length > 0) {
     goEnum(d, nm);
   } else if (
-    d.properties.length > 0 || d.as.length > 0 || nm == 'InitializedParams') {
+      d.properties.length > 0 || d.as.length > 0 || nm == 'InitializedParams') {
     goInterface(d, nm);
   } else
     throw new Error(
-      `more cases in toGo ${nm} ${d.as.length} ${d.generics.length} `)
+        `more cases in toGo ${nm} ${d.as.length} ${d.generics.length} `)
 }
 
 // these fields need a *
@@ -499,7 +509,7 @@
   let ans = `type ${goName(nm)} struct {\n`;
 
   // generate the code for each member
-  const g = function (n: ts.TypeElement) {
+  const g = function(n: ts.TypeElement) {
     if (!ts.isPropertySignature(n))
       throw new Error(`expected PropertySignature got ${strKind(n)} `);
     ans = ans.concat(getComments(n));
@@ -516,7 +526,7 @@
   d.properties.forEach(g)
   // heritage clauses become embedded types
   // check they are all Identifiers
-  const f = function (n: ts.ExpressionWithTypeArguments) {
+  const f = function(n: ts.ExpressionWithTypeArguments) {
     if (!ts.isIdentifier(n.expression))
       throw new Error(`Interface ${nm} heritage ${strKind(n.expression)} `);
     ans = ans.concat(goName(n.expression.getText()), '\n')
@@ -539,7 +549,7 @@
   // They are VariableStatements with x.declarationList having a single
   //   VariableDeclaration
   let isNumeric = false;
-  const f = function (n: ts.Statement, i: number) {
+  const f = function(n: ts.Statement, i: number) {
     if (!ts.isVariableStatement(n)) {
       throw new Error(` ${nm} ${i} expected VariableStatement,
       got ${strKind(n)}`);
@@ -566,7 +576,7 @@
 // generate Go code for an enum. Both types and named constants
 function goEnum(d: Data, nm: string) {
   let isNumeric = false
-  const f = function (v: ts.EnumMember, j: number) {  // same as goModule
+  const f = function(v: ts.EnumMember, j: number) {  // same as goModule
     if (!v.initializer)
       throw new Error(`goEnum no initializer ${nm} ${j} ${v.name.getText()}`);
     isNumeric = strKind(v.initializer) == 'NumericLiteral';
@@ -587,11 +597,12 @@
   if (d.as.length != 0 || d.generics.length != 0) {
     if (nm != 'ServerCapabilities')
       throw new Error(`${nm} has extra fields(${d.as.length},${
-        d.generics.length}) ${d.me.getText()}`);
+          d.generics.length}) ${d.me.getText()}`);
   }
   typesOut.push(getComments(d.me))
   // d.alias doesn't seem to have comments
-  typesOut.push(`type ${goName(nm)} = ${goType(d.alias, nm)}\n`)
+  let aliasStr = goName(nm) == "DocumentURI" ? " " : " = "
+  typesOut.push(`type ${goName(nm)}${aliasStr}${goType(d.alias, nm)}\n`)
 }
 
 // return a go type and maybe an assocated javascript tag
@@ -628,7 +639,7 @@
     const v = goTypeLiteral(n, nm);
     return v
   } else if (ts.isTupleTypeNode(n)) {
-    if (n.getText() == '[number, number]') return '[]float64'
+    if (n.getText() == '[number, number]') return '[]float64';
     throw new Error(`goType unexpected Tuple ${n.getText()}`)
   }
   throw new Error(`${strKind(n)} goType unexpected ${n.getText()} for ${nm}`)
@@ -661,16 +672,21 @@
         if (nm == 'renameProvider') return `interface{} ${help}`;
         return `${goType(n.types[0], 'b')} ${help}`
       }
-      if (b == 'ArrayType') return `${goType(n.types[1], 'c')} ${help}`
-      if (a == 'TypeReference' && a == b) return `interface{} ${help}`
+      if (b == 'ArrayType') return `${goType(n.types[1], 'c')} ${help}`;
+      if (a == 'TypeReference' && a == b) return `interface{} ${help}`;
       if (a == 'StringKeyword') return `string ${help}`;
       if (a == 'TypeLiteral' && nm == 'TextDocumentContentChangeEvent') {
         return `${goType(n.types[0], nm)}`
       }
-      throw new Error(`724 ${a} ${b} ${n.getText()} ${loc(n)}`)
-    case 3: const aa = strKind(n.types[0])
+      throw new Error(`724 ${a} ${b} ${n.getText()} ${loc(n)}`);
+    case 3:
+      const aa = strKind(n.types[0])
       const bb = strKind(n.types[1])
       const cc = strKind(n.types[2])
+      if (nm == 'textDocument/prepareRename') {
+        // want Range, not interface{}
+        return `${goType(n.types[0], nm)} ${help}`
+      }
       if (nm == 'DocumentFilter') {
         // not really a union. the first is enough, up to a missing
         // omitempty but avoid repetitious comments
@@ -690,7 +706,7 @@
         // check this is nm == 'textDocument/completion'
         return `${goType(n.types[1], 'f')} ${help}`
       }
-      if (aa == 'LiteralType' && bb == aa && cc == aa) return `string ${help}`
+      if (aa == 'LiteralType' && bb == aa && cc == aa) return `string ${help}`;
       break;
     case 4:
       if (nm == 'documentChanges') return `TextDocumentEdit ${help} `;
@@ -731,7 +747,7 @@
   if (nm == 'ServerCapabilities') return expandIntersection(n);
   let inner = '';
   n.types.forEach(
-    (t: ts.TypeNode) => { inner = inner.concat(goType(t, nm), '\n') });
+      (t: ts.TypeNode) => {inner = inner.concat(goType(t, nm), '\n')});
   return `struct{ \n${inner}} `
 }
 
@@ -740,7 +756,7 @@
 // of them by name. The names that occur once can be output. The names
 // that occur more than once need to be combined.
 function expandIntersection(n: ts.IntersectionTypeNode): string {
-  const bad = function (n: ts.Node, s: string) {
+  const bad = function(n: ts.Node, s: string) {
     return new Error(`expandIntersection ${strKind(n)} ${s}`)
   };
   let props = new Map<string, ts.PropertySignature[]>();
@@ -774,10 +790,11 @@
         if (!ts.isPropertySignature(b)) throw bad(b, 'D');
         ans = ans.concat(getComments(b));
         ans = ans.concat(
-          goName(b.name.getText()), ' ', goType(b.type, 'a'), u.JSON(b), '\n')
+            goName(b.name.getText()), ' ', goType(b.type, 'a'), u.JSON(b), '\n')
       } else if (a.type.kind == ts.SyntaxKind.ObjectKeyword) {
         ans = ans.concat(getComments(a))
-        ans = ans.concat(goName(a.name.getText()), ' ', 'interface{}', u.JSON(a), '\n')
+        ans = ans.concat(
+            goName(a.name.getText()), ' ', 'interface{}', u.JSON(a), '\n')
       } else {
         throw bad(a.type, `E ${a.getText()} in ${goName(k)} at ${loc(a)}`)
       }
@@ -790,8 +807,8 @@
 
 function goTypeLiteral(n: ts.TypeLiteralNode, nm: string): string {
   let ans: string[] = [];  // in case we generate a new extra type
-  let res = 'struct{\n' // the actual answer usually
-  const g = function (nx: ts.TypeElement) {
+  let res = 'struct{\n'    // the actual answer usually
+  const g = function(nx: ts.TypeElement) {
     // add the json, as in goInterface(). Strange inside union types.
     if (ts.isPropertySignature(nx)) {
       let json = u.JSON(nx);
@@ -941,7 +958,7 @@
   let case1 = notNil;
   if (a != '') {
     if (extraTypes.has('Param' + nm)) a = 'Param' + nm
-    case1 = `var params ${a}
+      case1 = `var params ${a}
     if err := json.Unmarshal(*r.Params, &params); err != nil {
       sendParseError(ctx, r, err)
       return true
@@ -975,7 +992,7 @@
     if (indirect(b)) theRet = '&result';
     callBody = `var result ${b}
 			if err := s.Conn.Call(ctx, "${m}", ${
-      p2}, &result); err != nil {
+        p2}, &result); err != nil {
 				return nil, err
       }
       return ${theRet}, nil
@@ -1003,7 +1020,7 @@
   if (seenNames.has(x)) {
     // Resolve, ResolveCodeLens, ResolveDocumentLink
     if (!x.startsWith('Resolve')) throw new Error(`expected Resolve, not ${x}`)
-    x += m[0].toUpperCase() + m.substring(1, i)
+      x += m[0].toUpperCase() + m.substring(1, i)
   }
   seenNames.add(x);
   return x;
@@ -1014,7 +1031,7 @@
   if (s == '' || s == 'void') return false;
   const skip = (x: string) => s.startsWith(x);
   if (skip('[]') || skip('interface') || skip('Declaration') ||
-    skip('Definition') || skip('DocumentSelector'))
+      skip('Definition') || skip('DocumentSelector'))
     return false;
   return true
 }
@@ -1054,7 +1071,7 @@
     side.outputFile = `ts${side.name}.go`;
     side.fd = fs.openSync(side.outputFile, 'w');
   }
-  const f = function (s: string) {
+  const f = function(s: string) {
     fs.writeSync(side.fd, s);
     fs.writeSync(side.fd, '\n');
   };
@@ -1071,10 +1088,10 @@
         `);
   const a = side.name[0].toUpperCase() + side.name.substring(1)
   f(`type ${a} interface {`);
-  side.methods.forEach((v) => { f(v) });
+  side.methods.forEach((v) => {f(v)});
   f('}\n');
   f(`func (h ${
-    side.name}Handler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
+      side.name}Handler) Deliver(ctx context.Context, r *jsonrpc2.Request, delivered bool) bool {
             if delivered {
               return false
             }
@@ -1084,7 +1101,7 @@
               return true
             }
             switch r.Method {`);
-  side.cases.forEach((v) => { f(v) });
+  side.cases.forEach((v) => {f(v)});
   f(`
         }
       }`);
@@ -1093,13 +1110,15 @@
           *jsonrpc2.Conn
         }
         `);
-  side.calls.forEach((v) => { f(v) });
+  side.calls.forEach((v) => {f(v)});
 }
 
 // Handling of non-standard requests, so we can add gopls-specific calls.
 function nonstandardRequests() {
-  server.methods.push('NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error)')
-  server.calls.push(`func (s *serverDispatcher) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
+  server.methods.push(
+      'NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error)')
+  server.calls.push(
+      `func (s *serverDispatcher) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
       var result interface{}
       if err := s.Conn.Call(ctx, method, params, &result); err != nil {
         return nil, err
@@ -1127,7 +1146,7 @@
 function main() {
   if (u.gitHash != u.git()) {
     throw new Error(
-      `git hash mismatch, wanted\n${u.gitHash} but source is at\n${u.git()}`);
+        `git hash mismatch, wanted\n${u.gitHash} but source is at\n${u.git()}`);
   }
   u.createOutputFiles()
   parse()
@@ -1154,13 +1173,11 @@
   // 2. func (h *serverHandler) Deliver(...) { switch r.method }
   // 3. func (x *xDispatcher) Method(ctx, parm)
   not.forEach(  // notifications
-    (v, k) => {
-      receives.get(k) == 'client' ? goNot(client, k) : goNot(server, k)
-    });
+      (v, k) => {
+          receives.get(k) == 'client' ? goNot(client, k) : goNot(server, k)});
   req.forEach(  // requests
-    (v, k) => {
-      receives.get(k) == 'client' ? goReq(client, k) : goReq(server, k)
-    });
+      (v, k) => {
+          receives.get(k) == 'client' ? goReq(client, k) : goReq(server, k)});
   nonstandardRequests();
   // find all the types implied by seenTypes and rpcs to try to avoid
   // generating types that aren't used
diff --git a/internal/lsp/references.go b/internal/lsp/references.go
index d91fc16..57e92c0 100644
--- a/internal/lsp/references.go
+++ b/internal/lsp/references.go
@@ -9,26 +9,14 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 func (s *Server) references(ctx context.Context, params *protocol.ReferenceParams) ([]protocol.Location, error) {
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	// Find all references to the identifier at the position.
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
-
-	references, err := source.References(ctx, view.Snapshot(), fh, params.Position, params.Context.IncludeDeclaration)
+	references, err := source.References(ctx, snapshot, fh, params.Position, params.Context.IncludeDeclaration)
 	if err != nil {
 		return nil, err
 	}
@@ -41,7 +29,7 @@
 		}
 
 		locations = append(locations, protocol.Location{
-			URI:   protocol.NewURI(ref.URI()),
+			URI:   protocol.URIFromSpanURI(ref.URI()),
 			Range: refRange,
 		})
 	}
diff --git a/internal/lsp/regtest/definition_test.go b/internal/lsp/regtest/definition_test.go
new file mode 100644
index 0000000..502e7b7
--- /dev/null
+++ b/internal/lsp/regtest/definition_test.go
@@ -0,0 +1,85 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package regtest
+
+import (
+	"context"
+	"path"
+	"testing"
+
+	"golang.org/x/tools/internal/lsp/fake"
+)
+
+const internalDefinition = `
+-- go.mod --
+module mod
+
+go 1.12
+-- main.go --
+package main
+
+import "fmt"
+
+func main() {
+	fmt.Println(message)
+}
+-- const.go --
+package main
+
+const message = "Hello World."
+`
+
+func TestGoToInternalDefinition(t *testing.T) {
+	t.Parallel()
+	runner.Run(t, internalDefinition, func(ctx context.Context, t *testing.T, env *Env) {
+		env.OpenFile("main.go")
+		name, pos := env.GoToDefinition("main.go", fake.Pos{Line: 5, Column: 13})
+		if want := "const.go"; name != want {
+			t.Errorf("GoToDefinition: got file %q, want %q", name, want)
+		}
+		if want := (fake.Pos{Line: 2, Column: 6}); pos != want {
+			t.Errorf("GoToDefinition: got position %v, want %v", pos, want)
+		}
+	})
+}
+
+const stdlibDefinition = `
+-- go.mod --
+module mod
+
+go 1.12
+-- main.go --
+package main
+
+import (
+	"fmt"
+	"time"
+)
+
+func main() {
+	fmt.Println(time.Now())
+}`
+
+func TestGoToStdlibDefinition(t *testing.T) {
+	t.Skip("skipping due to golang.org/issues/37318")
+	t.Parallel()
+	runner.Run(t, stdlibDefinition, func(ctx context.Context, t *testing.T, env *Env) {
+		env.OpenFile("main.go")
+		name, pos := env.GoToDefinition("main.go", fake.Pos{Line: 8, Column: 19})
+		if got, want := path.Base(name), "time.go"; got != want {
+			t.Errorf("GoToDefinition: got file %q, want %q", name, want)
+		}
+
+		// Test that we can jump to definition from outside our workspace.
+		// See golang.org/issues/37045.
+		newName, newPos := env.GoToDefinition(name, pos)
+		if newName != name {
+			t.Errorf("GoToDefinition is not idempotent: got %q, want %q", newName, name)
+		}
+		if newPos != pos {
+			t.Errorf("GoToDefinition is not idempotent: got %v, want %v", newPos, pos)
+		}
+	})
+}
diff --git a/internal/lsp/regtest/diagnostics_test.go b/internal/lsp/regtest/diagnostics_test.go
new file mode 100644
index 0000000..1006b5e
--- /dev/null
+++ b/internal/lsp/regtest/diagnostics_test.go
@@ -0,0 +1,103 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package regtest
+
+import (
+	"context"
+	"testing"
+
+	"golang.org/x/tools/internal/lsp/fake"
+)
+
+const exampleProgram = `
+-- go.mod --
+module mod
+
+go 1.12
+-- main.go --
+package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("Hello World.")
+}`
+
+func TestDiagnosticErrorInEditedFile(t *testing.T) {
+	t.Parallel()
+	runner.Run(t, exampleProgram, func(ctx context.Context, t *testing.T, env *Env) {
+		// Deleting the 'n' at the end of Println should generate a single error
+		// diagnostic.
+		edit := fake.NewEdit(5, 11, 5, 12, "")
+		env.OpenFile("main.go")
+		env.EditBuffer("main.go", edit)
+		env.Await(DiagnosticAt("main.go", 5, 5))
+	})
+}
+
+const brokenFile = `package main
+
+const Foo = "abc
+`
+
+func TestDiagnosticErrorInNewFile(t *testing.T) {
+	t.Parallel()
+	runner.Run(t, brokenFile, func(ctx context.Context, t *testing.T, env *Env) {
+		env.CreateBuffer("broken.go", brokenFile)
+		env.Await(DiagnosticAt("broken.go", 2, 12))
+	})
+}
+
+// badPackage contains a duplicate definition of the 'a' const.
+const badPackage = `
+-- go.mod --
+module mod
+
+go 1.12
+-- a.go --
+package consts
+
+const a = 1
+-- b.go --
+package consts
+
+const a = 2
+`
+
+func TestDiagnosticClearingOnEdit(t *testing.T) {
+	t.Parallel()
+	runner.Run(t, badPackage, func(ctx context.Context, t *testing.T, env *Env) {
+		env.OpenFile("b.go")
+		env.Await(DiagnosticAt("a.go", 2, 6), DiagnosticAt("b.go", 2, 6))
+
+		// Fix the error by editing the const name in b.go to `b`.
+		edit := fake.NewEdit(2, 6, 2, 7, "b")
+		env.EditBuffer("b.go", edit)
+		env.Await(EmptyDiagnostics("a.go"), EmptyDiagnostics("b.go"))
+	})
+}
+
+func TestDiagnosticClearingOnDelete(t *testing.T) {
+	t.Parallel()
+	runner.Run(t, badPackage, func(ctx context.Context, t *testing.T, env *Env) {
+		env.OpenFile("a.go")
+		env.Await(DiagnosticAt("a.go", 2, 6), DiagnosticAt("b.go", 2, 6))
+		env.RemoveFileFromWorkspace("b.go")
+
+		env.Await(EmptyDiagnostics("a.go"), EmptyDiagnostics("b.go"))
+	})
+}
+
+func TestDiagnosticClearingOnClose(t *testing.T) {
+	t.Parallel()
+	runner.Run(t, badPackage, func(ctx context.Context, t *testing.T, env *Env) {
+		env.CreateBuffer("c.go", `package consts
+
+const a = 3`)
+		env.Await(DiagnosticAt("a.go", 2, 6), DiagnosticAt("b.go", 2, 6), DiagnosticAt("c.go", 2, 6))
+		env.CloseBuffer("c.go")
+		env.Await(DiagnosticAt("a.go", 2, 6), DiagnosticAt("b.go", 2, 6), EmptyDiagnostics("c.go"))
+	})
+}
diff --git a/internal/lsp/regtest/env.go b/internal/lsp/regtest/env.go
new file mode 100644
index 0000000..034deaa
--- /dev/null
+++ b/internal/lsp/regtest/env.go
@@ -0,0 +1,453 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package regtest provides an environment for writing regression tests.
+package regtest
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+
+	"golang.org/x/tools/internal/jsonrpc2/servertest"
+	"golang.org/x/tools/internal/lsp/cache"
+	"golang.org/x/tools/internal/lsp/debug"
+	"golang.org/x/tools/internal/lsp/fake"
+	"golang.org/x/tools/internal/lsp/lsprpc"
+	"golang.org/x/tools/internal/lsp/protocol"
+)
+
+// EnvMode is a bitmask that defines in which execution environments a test
+// should run.
+type EnvMode int
+
+const (
+	// Singleton mode uses a separate cache for each test.
+	Singleton EnvMode = 1 << iota
+	// Shared mode uses a Shared cache.
+	Shared
+	// Forwarded forwards connections to an in-process gopls instance.
+	Forwarded
+	// SeparateProcess runs a separate gopls process, and forwards connections to
+	// it.
+	SeparateProcess
+	// NormalModes runs tests in all modes.
+	NormalModes = Singleton | Shared | Forwarded
+)
+
+// A Runner runs tests in gopls execution environments, as specified by its
+// modes. For modes that share state (for example, a shared cache or common
+// remote), any tests that execute on the same Runner will share the same
+// state.
+type Runner struct {
+	defaultModes EnvMode
+	timeout      time.Duration
+	goplsPath    string
+
+	mu        sync.Mutex
+	ts        *servertest.TCPServer
+	socketDir string
+}
+
+// NewTestRunner creates a Runner with its shared state initialized, ready to
+// run tests.
+func NewTestRunner(modes EnvMode, testTimeout time.Duration, goplsPath string) *Runner {
+	return &Runner{
+		defaultModes: modes,
+		timeout:      testTimeout,
+		goplsPath:    goplsPath,
+	}
+}
+
+// Modes returns the bitmask of environment modes this runner is configured to
+// test.
+func (r *Runner) Modes() EnvMode {
+	return r.defaultModes
+}
+
+// getTestServer gets the test server instance to connect to, or creates one if
+// it doesn't exist.
+func (r *Runner) getTestServer() *servertest.TCPServer {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	if r.ts == nil {
+		di := debug.NewInstance("", "")
+		ss := lsprpc.NewStreamServer(cache.New(nil, di.State), false, di)
+		r.ts = servertest.NewTCPServer(context.Background(), ss)
+	}
+	return r.ts
+}
+
+// runTestAsGoplsEnvvar triggers TestMain to run gopls instead of running
+// tests. It's a trick to allow tests to find a binary to use to start a gopls
+// subprocess.
+const runTestAsGoplsEnvvar = "_GOPLS_TEST_BINARY_RUN_AS_GOPLS"
+
+func (r *Runner) getRemoteSocket(t *testing.T) string {
+	t.Helper()
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	const daemonFile = "gopls-test-daemon"
+	if r.socketDir != "" {
+		return filepath.Join(r.socketDir, daemonFile)
+	}
+
+	if r.goplsPath == "" {
+		t.Fatal("cannot run tests with a separate process unless a path to a gopls binary is configured")
+	}
+	var err error
+	r.socketDir, err = ioutil.TempDir("", "gopls-regtests")
+	if err != nil {
+		t.Fatalf("creating tempdir: %v", err)
+	}
+	socket := filepath.Join(r.socketDir, daemonFile)
+	args := []string{"serve", "-listen", "unix;" + socket, "-listen.timeout", "10s"}
+	cmd := exec.Command(r.goplsPath, args...)
+	cmd.Env = append(os.Environ(), runTestAsGoplsEnvvar+"=true")
+	var stderr bytes.Buffer
+	cmd.Stderr = &stderr
+	go func() {
+		if err := cmd.Run(); err != nil {
+			panic(fmt.Sprintf("error running external gopls: %v\nstderr:\n%s", err, stderr.String()))
+		}
+	}()
+	return socket
+}
+
+// Close cleans up resource that have been allocated to this workspace.
+func (r *Runner) Close() error {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+	if r.ts != nil {
+		r.ts.Close()
+	}
+	if r.socketDir != "" {
+		os.RemoveAll(r.socketDir)
+	}
+	return nil
+}
+
+// Run executes the test function in the default configured gopls execution
+// modes. For each a test run, a new workspace is created containing the
+// un-txtared files specified by filedata.
+func (r *Runner) Run(t *testing.T, filedata string, test func(context.Context, *testing.T, *Env)) {
+	t.Helper()
+	r.RunInMode(r.defaultModes, t, filedata, test)
+}
+
+// RunInMode runs the test in the execution modes specified by the modes bitmask.
+func (r *Runner) RunInMode(modes EnvMode, t *testing.T, filedata string, test func(ctx context.Context, t *testing.T, e *Env)) {
+	t.Helper()
+	tests := []struct {
+		name         string
+		mode         EnvMode
+		getConnector func(context.Context, *testing.T) (servertest.Connector, func())
+	}{
+		{"singleton", Singleton, r.singletonEnv},
+		{"shared", Shared, r.sharedEnv},
+		{"forwarded", Forwarded, r.forwardedEnv},
+		{"separate_process", SeparateProcess, r.separateProcessEnv},
+	}
+
+	for _, tc := range tests {
+		tc := tc
+		if modes&tc.mode == 0 {
+			continue
+		}
+		t.Run(tc.name, func(t *testing.T) {
+			t.Helper()
+			ctx, cancel := context.WithTimeout(context.Background(), r.timeout)
+			defer cancel()
+			ws, err := fake.NewWorkspace("lsprpc", []byte(filedata))
+			if err != nil {
+				t.Fatal(err)
+			}
+			defer ws.Close()
+			ts, cleanup := tc.getConnector(ctx, t)
+			defer cleanup()
+			env := NewEnv(ctx, t, ws, ts)
+			defer func() {
+				if err := env.E.Shutdown(ctx); err != nil {
+					panic(err)
+				}
+			}()
+			test(ctx, t, env)
+		})
+	}
+}
+
+func (r *Runner) singletonEnv(ctx context.Context, t *testing.T) (servertest.Connector, func()) {
+	di := debug.NewInstance("", "")
+	ss := lsprpc.NewStreamServer(cache.New(nil, di.State), false, di)
+	ts := servertest.NewPipeServer(ctx, ss)
+	cleanup := func() {
+		ts.Close()
+	}
+	return ts, cleanup
+}
+
+func (r *Runner) sharedEnv(ctx context.Context, t *testing.T) (servertest.Connector, func()) {
+	return r.getTestServer(), func() {}
+}
+
+func (r *Runner) forwardedEnv(ctx context.Context, t *testing.T) (servertest.Connector, func()) {
+	ts := r.getTestServer()
+	forwarder := lsprpc.NewForwarder("tcp", ts.Addr, false, debug.NewInstance("", ""))
+	ts2 := servertest.NewPipeServer(ctx, forwarder)
+	cleanup := func() {
+		ts2.Close()
+	}
+	return ts2, cleanup
+}
+
+func (r *Runner) separateProcessEnv(ctx context.Context, t *testing.T) (servertest.Connector, func()) {
+	socket := r.getRemoteSocket(t)
+	// TODO(rfindley): can we use the autostart behavior here, instead of
+	// pre-starting the remote?
+	forwarder := lsprpc.NewForwarder("unix", socket, false, debug.NewInstance("", ""))
+	ts2 := servertest.NewPipeServer(ctx, forwarder)
+	cleanup := func() {
+		ts2.Close()
+	}
+	return ts2, cleanup
+}
+
+// Env holds an initialized fake Editor, Workspace, and Server, which may be
+// used for writing tests. It also provides adapter methods that call t.Fatal
+// on any error, so that tests for the happy path may be written without
+// checking errors.
+type Env struct {
+	t   *testing.T
+	ctx context.Context
+
+	// Most tests should not need to access the workspace or editor, or server,
+	// but they are available if needed.
+	W      *fake.Workspace
+	E      *fake.Editor
+	Server servertest.Connector
+
+	// mu guards the fields below, for the purpose of checking conditions on
+	// every change to diagnostics.
+	mu sync.Mutex
+	// For simplicity, each waiter gets a unique ID.
+	nextWaiterID    int
+	lastDiagnostics map[string]*protocol.PublishDiagnosticsParams
+	waiters         map[int]*diagnosticCondition
+}
+
+// A diagnosticCondition is satisfied when all expectations are simultaneously
+// met. At that point, the 'met' channel is closed.
+type diagnosticCondition struct {
+	expectations []DiagnosticExpectation
+	met          chan struct{}
+}
+
+// NewEnv creates a new test environment using the given workspace and gopls
+// server.
+func NewEnv(ctx context.Context, t *testing.T, ws *fake.Workspace, ts servertest.Connector) *Env {
+	t.Helper()
+	conn := ts.Connect(ctx)
+	editor, err := fake.NewConnectedEditor(ctx, ws, conn)
+	if err != nil {
+		t.Fatal(err)
+	}
+	env := &Env{
+		t:               t,
+		ctx:             ctx,
+		W:               ws,
+		E:               editor,
+		Server:          ts,
+		lastDiagnostics: make(map[string]*protocol.PublishDiagnosticsParams),
+		waiters:         make(map[int]*diagnosticCondition),
+	}
+	env.E.Client().OnDiagnostics(env.onDiagnostics)
+	return env
+}
+
+// RemoveFileFromWorkspace deletes a file on disk but does nothing in the
+// editor. It calls t.Fatal on any error.
+func (e *Env) RemoveFileFromWorkspace(name string) {
+	e.t.Helper()
+	if err := e.W.RemoveFile(e.ctx, name); err != nil {
+		e.t.Fatal(err)
+	}
+}
+
+// OpenFile opens a file in the editor, calling t.Fatal on any error.
+func (e *Env) OpenFile(name string) {
+	e.t.Helper()
+	if err := e.E.OpenFile(e.ctx, name); err != nil {
+		e.t.Fatal(err)
+	}
+}
+
+// CreateBuffer creates a buffer in the editor, calling t.Fatal on any error.
+func (e *Env) CreateBuffer(name string, content string) {
+	e.t.Helper()
+	if err := e.E.CreateBuffer(e.ctx, name, content); err != nil {
+		e.t.Fatal(err)
+	}
+}
+
+// CloseBuffer closes an editor buffer, calling t.Fatal on any error.
+func (e *Env) CloseBuffer(name string) {
+	e.t.Helper()
+	if err := e.E.CloseBuffer(e.ctx, name); err != nil {
+		e.t.Fatal(err)
+	}
+}
+
+// EditBuffer applies edits to an editor buffer, calling t.Fatal on any error.
+func (e *Env) EditBuffer(name string, edits ...fake.Edit) {
+	e.t.Helper()
+	if err := e.E.EditBuffer(e.ctx, name, edits); err != nil {
+		e.t.Fatal(err)
+	}
+}
+
+// GoToDefinition goes to definition in the editor, calling t.Fatal on any
+// error.
+func (e *Env) GoToDefinition(name string, pos fake.Pos) (string, fake.Pos) {
+	e.t.Helper()
+	n, p, err := e.E.GoToDefinition(e.ctx, name, pos)
+	if err != nil {
+		e.t.Fatal(err)
+	}
+	return n, p
+}
+
+func (e *Env) onDiagnostics(_ context.Context, d *protocol.PublishDiagnosticsParams) error {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+
+	pth := e.W.URIToPath(d.URI)
+	e.lastDiagnostics[pth] = d
+
+	for id, condition := range e.waiters {
+		if meetsCondition(e.lastDiagnostics, condition.expectations) {
+			delete(e.waiters, id)
+			close(condition.met)
+		}
+	}
+	return nil
+}
+
+// CloseEditor shuts down the editor, calling t.Fatal on any error.
+func (e *Env) CloseEditor() {
+	e.t.Helper()
+	if err := e.E.Shutdown(e.ctx); err != nil {
+		e.t.Fatal(err)
+	}
+	if err := e.E.Exit(e.ctx); err != nil {
+		e.t.Fatal(err)
+	}
+}
+
+func meetsCondition(m map[string]*protocol.PublishDiagnosticsParams, expectations []DiagnosticExpectation) bool {
+	for _, e := range expectations {
+		if !e.IsMet(m) {
+			return false
+		}
+	}
+	return true
+}
+
+// A DiagnosticExpectation is a condition that must be met by the current set
+// of diagnostics.
+type DiagnosticExpectation struct {
+	IsMet       func(map[string]*protocol.PublishDiagnosticsParams) bool
+	Description string
+}
+
+// EmptyDiagnostics asserts that diagnostics are empty for the
+// workspace-relative path name.
+func EmptyDiagnostics(name string) DiagnosticExpectation {
+	isMet := func(diags map[string]*protocol.PublishDiagnosticsParams) bool {
+		ds, ok := diags[name]
+		return ok && len(ds.Diagnostics) == 0
+	}
+	return DiagnosticExpectation{
+		IsMet:       isMet,
+		Description: fmt.Sprintf("empty diagnostics for %q", name),
+	}
+}
+
+// DiagnosticAt asserts that there is a diagnostic entry at the position
+// specified by line and col, for the workspace-relative path name.
+func DiagnosticAt(name string, line, col int) DiagnosticExpectation {
+	isMet := func(diags map[string]*protocol.PublishDiagnosticsParams) bool {
+		ds, ok := diags[name]
+		if !ok || len(ds.Diagnostics) == 0 {
+			return false
+		}
+		for _, d := range ds.Diagnostics {
+			if d.Range.Start.Line == float64(line) && d.Range.Start.Character == float64(col) {
+				return true
+			}
+		}
+		return false
+	}
+	return DiagnosticExpectation{
+		IsMet:       isMet,
+		Description: fmt.Sprintf("diagnostic in %q at (line:%d, column:%d)", name, line, col),
+	}
+}
+
+// Await waits for all diagnostic expectations to simultaneously be met.
+func (e *Env) Await(expectations ...DiagnosticExpectation) {
+	// NOTE: in the future this mechanism extend beyond just diagnostics, for
+	// example by modifying IsMet to be a func(*Env) boo.  However, that would
+	// require careful checking of conditions around every state change, so for
+	// now we just limit the scope to diagnostic conditions.
+
+	e.t.Helper()
+	e.mu.Lock()
+	// Before adding the waiter, we check if the condition is currently met to
+	// avoid a race where the condition was realized before Await was called.
+	if meetsCondition(e.lastDiagnostics, expectations) {
+		e.mu.Unlock()
+		return
+	}
+	met := make(chan struct{})
+	e.waiters[e.nextWaiterID] = &diagnosticCondition{
+		expectations: expectations,
+		met:          met,
+	}
+	e.nextWaiterID++
+	e.mu.Unlock()
+
+	select {
+	case <-e.ctx.Done():
+		// Debugging an unmet expectation can be tricky, so we put some effort into
+		// nicely formatting the failure.
+		var descs []string
+		for _, e := range expectations {
+			descs = append(descs, e.Description)
+		}
+		e.mu.Lock()
+		diagString := formatDiagnostics(e.lastDiagnostics)
+		e.mu.Unlock()
+		e.t.Fatalf("waiting on (%s):\nerr:%v\ndiagnostics:\n%s", strings.Join(descs, ", "), e.ctx.Err(), diagString)
+	case <-met:
+	}
+}
+
+func formatDiagnostics(diags map[string]*protocol.PublishDiagnosticsParams) string {
+	var b strings.Builder
+	for name, params := range diags {
+		b.WriteString(name + ":\n")
+		for _, d := range params.Diagnostics {
+			b.WriteString(fmt.Sprintf("\t(%d, %d): %s\n", int(d.Range.Start.Line), int(d.Range.Start.Character), d.Message))
+		}
+	}
+	return b.String()
+}
diff --git a/internal/lsp/regtest/reg_test.go b/internal/lsp/regtest/reg_test.go
new file mode 100644
index 0000000..9246ccb
--- /dev/null
+++ b/internal/lsp/regtest/reg_test.go
@@ -0,0 +1,53 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package regtest
+
+import (
+	"context"
+	"flag"
+	"fmt"
+	"os"
+	"testing"
+	"time"
+
+	"golang.org/x/tools/internal/lsp/cmd"
+	"golang.org/x/tools/internal/lsp/lsprpc"
+	"golang.org/x/tools/internal/tool"
+)
+
+var (
+	runSubprocessTests = flag.Bool("enable_gopls_subprocess_tests", false, "run regtests against a gopls subprocess")
+	goplsBinaryPath    = flag.String("gopls_test_binary", "", "path to the gopls binary for use as a remote, for use with the -gopls_subprocess_testmode flag")
+)
+
+var runner *Runner
+
+func TestMain(m *testing.M) {
+	flag.Parse()
+	if os.Getenv("_GOPLS_TEST_BINARY_RUN_AS_GOPLS") == "true" {
+		tool.Main(context.Background(), cmd.New("gopls", "", nil, nil), os.Args[1:])
+		os.Exit(0)
+	}
+	resetExitFuncs := lsprpc.OverrideExitFuncsForTest()
+	defer resetExitFuncs()
+
+	const testTimeout = 60 * time.Second
+	if *runSubprocessTests {
+		goplsPath := *goplsBinaryPath
+		if goplsPath == "" {
+			var err error
+			goplsPath, err = os.Executable()
+			if err != nil {
+				panic(fmt.Sprintf("finding test binary path: %v", err))
+			}
+		}
+		runner = NewTestRunner(NormalModes|SeparateProcess, testTimeout, goplsPath)
+	} else {
+		runner = NewTestRunner(NormalModes, testTimeout, "")
+	}
+	code := m.Run()
+	runner.Close()
+	os.Exit(code)
+}
diff --git a/internal/lsp/regtest/shared_test.go b/internal/lsp/regtest/shared_test.go
new file mode 100644
index 0000000..565241b
--- /dev/null
+++ b/internal/lsp/regtest/shared_test.go
@@ -0,0 +1,67 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package regtest
+
+import (
+	"context"
+	"testing"
+
+	"golang.org/x/tools/internal/lsp/fake"
+)
+
+const sharedProgram = `
+-- go.mod --
+module mod
+
+go 1.12
+-- main.go --
+package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("Hello World.")
+}`
+
+func runShared(t *testing.T, program string, testFunc func(ctx context.Context, t *testing.T, env1 *Env, env2 *Env)) {
+	// Only run these tests in forwarded modes.
+	modes := runner.Modes() & (Forwarded | SeparateProcess)
+	runner.RunInMode(modes, t, sharedProgram, func(ctx context.Context, t *testing.T, env1 *Env) {
+		// Create a second test session connected to the same workspace and server
+		// as the first.
+		env2 := NewEnv(ctx, t, env1.W, env1.Server)
+		testFunc(ctx, t, env1, env2)
+	})
+}
+
+func TestSimultaneousEdits(t *testing.T) {
+	t.Parallel()
+	runShared(t, exampleProgram, func(ctx context.Context, t *testing.T, env1 *Env, env2 *Env) {
+		// In editor #1, break fmt.Println as before.
+		edit1 := fake.NewEdit(5, 11, 5, 12, "")
+		env1.OpenFile("main.go")
+		env1.EditBuffer("main.go", edit1)
+		// In editor #2 remove the closing brace.
+		edit2 := fake.NewEdit(6, 0, 6, 1, "")
+		env2.OpenFile("main.go")
+		env2.EditBuffer("main.go", edit2)
+
+		// Now check that we got different diagnostics in each environment.
+		env1.Await(DiagnosticAt("main.go", 5, 5))
+		env2.Await(DiagnosticAt("main.go", 7, 0))
+	})
+}
+
+func TestShutdown(t *testing.T) {
+	t.Parallel()
+	runShared(t, sharedProgram, func(ctx context.Context, t *testing.T, env1 *Env, env2 *Env) {
+		env1.CloseEditor()
+		// Now make an edit in editor #2 to trigger diagnostics.
+		edit2 := fake.NewEdit(6, 0, 6, 1, "")
+		env2.OpenFile("main.go")
+		env2.EditBuffer("main.go", edit2)
+		env2.Await(DiagnosticAt("main.go", 7, 0))
+	})
+}
diff --git a/internal/lsp/rename.go b/internal/lsp/rename.go
index 04f0054..9fe59e9 100644
--- a/internal/lsp/rename.go
+++ b/internal/lsp/rename.go
@@ -9,24 +9,13 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 func (s *Server) rename(ctx context.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) {
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
-
 	edits, err := source.Rename(ctx, snapshot, fh, params.Position, params.NewName)
 	if err != nil {
 		return nil, err
@@ -46,20 +35,10 @@
 }
 
 func (s *Server) prepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (*protocol.Range, error) {
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
-
 	// Do not return errors here, as it adds clutter.
 	// Returning a nil result means there is not a valid rename.
 	item, err := source.PrepareRename(ctx, snapshot, fh, params.Position)
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index 9de3c06..b5bdb6f 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -7,61 +7,26 @@
 
 import (
 	"context"
-	"fmt"
-	"net"
 	"sync"
 
 	"golang.org/x/tools/internal/jsonrpc2"
+	"golang.org/x/tools/internal/lsp/mod"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/span"
 )
 
-// NewClientServer
-func NewClientServer(ctx context.Context, session source.Session, client protocol.Client) (context.Context, *Server) {
-	ctx = protocol.WithClient(ctx, client)
-	return ctx, &Server{
-		client:    client,
-		session:   session,
-		delivered: make(map[span.URI]sentDiagnostics),
-	}
-}
+const concurrentAnalyses = 1
 
 // NewServer creates an LSP server and binds it to handle incoming client
 // messages on on the supplied stream.
-func NewServer(ctx context.Context, session source.Session, stream jsonrpc2.Stream) (context.Context, *Server) {
-	s := &Server{
-		delivered: make(map[span.URI]sentDiagnostics),
-		session:   session,
+func NewServer(session source.Session, client protocol.Client) *Server {
+	return &Server{
+		delivered:       make(map[span.URI]sentDiagnostics),
+		session:         session,
+		client:          client,
+		diagnosticsSema: make(chan struct{}, concurrentAnalyses),
 	}
-	ctx, s.Conn, s.client = protocol.NewServer(ctx, stream, s)
-	return ctx, s
-}
-
-// RunServerOnPort starts an LSP server on the given port and does not exit.
-// This function exists for debugging purposes.
-func RunServerOnPort(ctx context.Context, cache source.Cache, port int, h func(ctx context.Context, s *Server)) error {
-	return RunServerOnAddress(ctx, cache, fmt.Sprintf(":%v", port), h)
-}
-
-// RunServerOnAddress starts an LSP server on the given address and does not
-// exit. This function exists for debugging purposes.
-func RunServerOnAddress(ctx context.Context, cache source.Cache, addr string, h func(ctx context.Context, s *Server)) error {
-	ln, err := net.Listen("tcp", addr)
-	if err != nil {
-		return err
-	}
-	for {
-		conn, err := ln.Accept()
-		if err != nil {
-			return err
-		}
-		h(NewServer(ctx, cache.NewSession(), jsonrpc2.NewHeaderStream(conn, conn)))
-	}
-}
-
-func (s *Server) Run(ctx context.Context) error {
-	return s.Conn.Run(ctx)
 }
 
 type serverState int
@@ -73,8 +38,8 @@
 	serverShutDown
 )
 
+// Server implements the protocol.Server interface.
 type Server struct {
-	Conn   *jsonrpc2.Conn
 	client protocol.Client
 
 	stateMu sync.Mutex
@@ -92,6 +57,12 @@
 	// delivered is a cache of the diagnostics that the server has sent.
 	deliveredMu sync.Mutex
 	delivered   map[span.URI]sentDiagnostics
+
+	showedInitialError   bool
+	showedInitialErrorMu sync.Mutex
+
+	// diagnosticsSema limits the concurrency of diagnostics runs, which can be expensive.
+	diagnosticsSema chan struct{}
 }
 
 // sentDiagnostics is used to cache diagnostics that have been sent for a given file.
@@ -108,24 +79,31 @@
 }
 
 func (s *Server) codeLens(ctx context.Context, params *protocol.CodeLensParams) ([]protocol.CodeLens, error) {
-	return nil, nil
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Mod)
+	if !ok {
+		return nil, err
+	}
+	if !snapshot.IsSaved(fh.Identity().URI) {
+		return nil, nil
+	}
+	return mod.CodeLens(ctx, snapshot, fh.Identity().URI)
 }
 
 func (s *Server) nonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
 	paramMap := params.(map[string]interface{})
 	if method == "gopls/diagnoseFiles" {
 		for _, file := range paramMap["files"].([]interface{}) {
-			uri := span.URI(file.(string))
-			view, err := s.session.ViewOf(uri)
-			if err != nil {
+			snapshot, fh, ok, err := s.beginFileRequest(protocol.DocumentURI(file.(string)), source.UnknownKind)
+			if !ok {
 				return nil, err
 			}
-			fileID, diagnostics, err := source.FileDiagnostics(ctx, view.Snapshot(), uri)
+
+			fileID, diagnostics, err := source.FileDiagnostics(ctx, snapshot, fh.Identity().URI)
 			if err != nil {
 				return nil, err
 			}
 			if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
-				URI:         protocol.NewURI(uri),
+				URI:         protocol.URIFromSpanURI(fh.Identity().URI),
 				Diagnostics: toProtocolDiagnostics(diagnostics),
 				Version:     fileID.Version,
 			}); err != nil {
diff --git a/internal/lsp/server_gen.go b/internal/lsp/server_gen.go
index ea5f8be..4205ad5 100644
--- a/internal/lsp/server_gen.go
+++ b/internal/lsp/server_gen.go
@@ -132,7 +132,7 @@
 	return nil, notImplemented("PrepareCallHierarchy")
 }
 
-func (s *Server) PrepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (interface{}, error) {
+func (s *Server) PrepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (*protocol.Range, error) {
 	return s.prepareRename(ctx, params)
 }
 
@@ -192,8 +192,8 @@
 	return s.signatureHelp(ctx, params)
 }
 
-func (s *Server) Symbol(context.Context, *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) {
-	return nil, notImplemented("Symbol")
+func (s *Server) Symbol(ctx context.Context, params *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) {
+	return s.symbol(ctx, params)
 }
 
 func (s *Server) TypeDefinition(ctx context.Context, params *protocol.TypeDefinitionParams) (protocol.Definition, error) {
diff --git a/internal/lsp/signature_help.go b/internal/lsp/signature_help.go
index fab6453..a978fd8 100644
--- a/internal/lsp/signature_help.go
+++ b/internal/lsp/signature_help.go
@@ -9,53 +9,22 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/log"
 	"golang.org/x/tools/internal/telemetry/tag"
 )
 
 func (s *Server) signatureHelp(ctx context.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) {
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
-	info, err := source.SignatureHelp(ctx, snapshot, fh, params.Position)
+	info, activeParameter, err := source.SignatureHelp(ctx, snapshot, fh, params.Position)
 	if err != nil {
 		log.Print(ctx, "no signature help", tag.Of("At", params.Position), tag.Of("Failure", err))
 		return nil, nil
 	}
-	return toProtocolSignatureHelp(info), nil
-}
-
-func toProtocolSignatureHelp(info *source.SignatureInformation) *protocol.SignatureHelp {
 	return &protocol.SignatureHelp{
-		ActiveParameter: float64(info.ActiveParameter),
-		ActiveSignature: 0, // there is only ever one possible signature
-		Signatures: []protocol.SignatureInformation{
-			{
-				Label:         info.Label,
-				Documentation: info.Documentation,
-				Parameters:    toProtocolParameterInformation(info.Parameters),
-			},
-		},
-	}
-}
-
-func toProtocolParameterInformation(info []source.ParameterInformation) []protocol.ParameterInformation {
-	var result []protocol.ParameterInformation
-	for _, p := range info {
-		result = append(result, protocol.ParameterInformation{
-			Label: p.Label,
-		})
-	}
-	return result
+		Signatures:      []protocol.SignatureInformation{*info},
+		ActiveParameter: float64(activeParameter),
+	}, nil
 }
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index 03fe101..b9cab74 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -9,6 +9,7 @@
 	"fmt"
 	"go/ast"
 	"go/constant"
+	"go/scanner"
 	"go/token"
 	"go/types"
 	"math"
@@ -323,7 +324,16 @@
 		return
 	}
 
-	if c.matchingCandidate(&cand, nil) {
+	// If we know we want a type name, don't offer non-type name
+	// candidates. However, do offer package names since they can
+	// contain type names, and do offer any candidate without a type
+	// since we aren't sure if it is a type name or not (i.e. unimported
+	// candidate).
+	if c.wantTypeName() && obj.Type() != nil && !isTypeName(obj) && !isPkgName(obj) {
+		return
+	}
+
+	if c.matchingCandidate(&cand) {
 		cand.score *= highScore
 	} else if isTypeName(obj) {
 		// If obj is a *types.TypeName that didn't otherwise match, check
@@ -383,8 +393,9 @@
 	// makePointer is true if the candidate type name T should be made into *T.
 	makePointer bool
 
-	// dereference is true if the candidate obj should be made into *obj.
-	dereference bool
+	// dereference is a count of how many times to dereference the candidate obj.
+	// For example, dereference=2 turns "foo" into "**foo" when formatting.
+	dereference int
 
 	// imp is the import that needs to be added to this package in order
 	// for this candidate to be valid. nil if no import needed.
@@ -422,7 +433,7 @@
 	if err != nil {
 		return nil, nil, fmt.Errorf("getting file for Completion: %v", err)
 	}
-	file, m, _, err := pgh.Cached()
+	file, src, m, _, err := pgh.Cached()
 	if err != nil {
 		return nil, nil, err
 	}
@@ -481,9 +492,26 @@
 		c.deepState.maxDepth = -1
 	}
 
-	// Set the filter surrounding.
-	if ident, ok := path[0].(*ast.Ident); ok {
-		c.setSurrounding(ident)
+	// Detect our surrounding identifier.
+	switch leaf := path[0].(type) {
+	case *ast.Ident:
+		// In the normal case, our leaf AST node is the identifier being completed.
+		c.setSurrounding(leaf)
+	case *ast.BadDecl:
+		// You don't get *ast.Idents at the file level, so look for bad
+		// decls and manually extract the surrounding token.
+		pos, _, lit := c.scanToken(ctx, src)
+		if pos.IsValid() {
+			c.setSurrounding(&ast.Ident{Name: lit, NamePos: pos})
+		}
+	default:
+		// Otherwise, manually extract the prefix if our containing token
+		// is a keyword. This improves completion after an "accidental
+		// keyword", e.g. completing to "variance" in "someFunc(var<>)".
+		pos, tkn, lit := c.scanToken(ctx, src)
+		if pos.IsValid() && tkn.IsKeyword() {
+			c.setSurrounding(&ast.Ident{Name: lit, NamePos: pos})
+		}
 	}
 
 	c.inference = expectedCandidate(c)
@@ -492,7 +520,7 @@
 
 	// If we're inside a comment return comment completions
 	for _, comment := range file.Comments {
-		if comment.Pos() <= rng.Start && rng.Start <= comment.End() {
+		if comment.Pos() < rng.Start && rng.Start <= comment.End() {
 			c.populateCommentCompletions(comment)
 			return c.items, c.getSurrounding(), nil
 		}
@@ -536,10 +564,6 @@
 		if err := c.lexical(); err != nil {
 			return nil, nil, err
 		}
-		if err := c.keyword(); err != nil {
-			return nil, nil, err
-		}
-
 	// The function name hasn't been typed yet, but the parens are there:
 	//   recv.‸(arg)
 	case *ast.TypeAssertExpr:
@@ -549,19 +573,14 @@
 		}
 
 	case *ast.SelectorExpr:
-		// The go parser inserts a phantom "_" Sel node when the selector is
-		// not followed by an identifier or a "(". The "_" isn't actually in
-		// the text, so don't think it is our surrounding.
-		// TODO: Find a way to differentiate between phantom "_" and real "_",
-		//       perhaps by checking if "_" is present in file contents.
-		if n.Sel.Name != "_" || c.pos != n.Sel.Pos() {
-			c.setSurrounding(n.Sel)
-		}
-
 		if err := c.selector(n); err != nil {
 			return nil, nil, err
 		}
 
+	// At the file scope, only keywords are allowed.
+	case *ast.BadDecl, *ast.File:
+		c.addKeywordCompletions()
+
 	default:
 		// fallback to lexical completions
 		if err := c.lexical(); err != nil {
@@ -572,6 +591,24 @@
 	return c.items, c.getSurrounding(), nil
 }
 
+// scanToken scans pgh's contents for the token containing pos.
+func (c *completer) scanToken(ctx context.Context, contents []byte) (token.Pos, token.Token, string) {
+	tok := c.snapshot.View().Session().Cache().FileSet().File(c.pos)
+
+	var s scanner.Scanner
+	s.Init(tok, contents, nil, 0)
+	for {
+		tknPos, tkn, lit := s.Scan()
+		if tkn == token.EOF || tknPos >= c.pos {
+			return token.NoPos, token.ILLEGAL, ""
+		}
+
+		if len(lit) > 0 && tknPos <= c.pos && c.pos <= tknPos+token.Pos(len(lit)) {
+			return tknPos, tkn, lit
+		}
+	}
+}
+
 func (c *completer) sortItems() {
 	sort.SliceStable(c.items, func(i, j int) bool {
 		// Sort by score first.
@@ -642,14 +679,17 @@
 }
 
 // See https://golang.org/issue/36001. Unimported completions are expensive.
-const unimportedTarget = 100
+const (
+	maxUnimportedPackageNames = 5
+	unimportedMemberTarget    = 100
+)
 
 // selector finds completions for the specified selector expression.
 func (c *completer) selector(sel *ast.SelectorExpr) error {
 	// Is sel a qualified identifier?
 	if id, ok := sel.X.(*ast.Ident); ok {
-		if pkgname, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok {
-			c.packageMembers(pkgname.Imported(), stdScore, nil)
+		if pkgName, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok {
+			c.packageMembers(pkgName.Imported(), stdScore, nil)
 			return nil
 		}
 	}
@@ -661,7 +701,7 @@
 	}
 
 	// Try unimported packages.
-	if id, ok := sel.X.(*ast.Ident); ok && c.opts.unimported && len(c.items) < unimportedTarget {
+	if id, ok := sel.X.(*ast.Ident); ok && c.opts.unimported {
 		if err := c.unimportedMembers(id); err != nil {
 			return err
 		}
@@ -675,6 +715,7 @@
 	if err != nil {
 		return err
 	}
+
 	var paths []string
 	for path, pkg := range known {
 		if pkg.GetTypes().Name() != id.Name {
@@ -682,6 +723,7 @@
 		}
 		paths = append(paths, path)
 	}
+
 	var relevances map[string]int
 	if len(paths) != 0 {
 		c.snapshot.View().RunProcessEnvFunc(c.ctx, func(opts *imports.Options) error {
@@ -689,6 +731,7 @@
 			return nil
 		})
 	}
+
 	for path, pkg := range known {
 		if pkg.GetTypes().Name() != id.Name {
 			continue
@@ -700,8 +743,8 @@
 		if imports.ImportPathToAssumedName(path) != pkg.GetTypes().Name() {
 			imp.name = pkg.GetTypes().Name()
 		}
-		c.packageMembers(pkg.GetTypes(), .01*float64(relevances[path]), imp)
-		if len(c.items) >= unimportedTarget {
+		c.packageMembers(pkg.GetTypes(), stdScore+.01*float64(relevances[path]), imp)
+		if len(c.items) >= unimportedMemberTarget {
 			return nil
 		}
 	}
@@ -719,7 +762,7 @@
 		// Continue with untyped proposals.
 		pkg := types.NewPackage(pkgExport.Fix.StmtInfo.ImportPath, pkgExport.Fix.IdentName)
 		for _, export := range pkgExport.Exports {
-			score := 0.01 * float64(pkgExport.Fix.Relevance)
+			score := stdScore + 0.01*float64(pkgExport.Fix.Relevance)
 			c.found(candidate{
 				obj:   types.NewVar(0, pkg, export, nil),
 				score: score,
@@ -729,7 +772,7 @@
 				},
 			})
 		}
-		if len(c.items) >= unimportedTarget {
+		if len(c.items) >= unimportedMemberTarget {
 			cancel()
 		}
 	}
@@ -828,7 +871,7 @@
 
 			// If obj's type is invalid, find the AST node that defines the lexical block
 			// containing the declaration of obj. Don't resolve types for packages.
-			if _, ok := obj.(*types.PkgName); !ok && !typeIsValid(obj.Type()) {
+			if !isPkgName(obj) && !typeIsValid(obj.Type()) {
 				// Match the scope to its ast.Node. If the scope is the package scope,
 				// use the *ast.File as the starting node.
 				var node ast.Node
@@ -909,7 +952,7 @@
 		}
 	}
 
-	if c.opts.unimported && len(c.items) < unimportedTarget {
+	if c.opts.unimported {
 		ctx, cancel := c.deepCompletionContext()
 		defer cancel()
 		// Suggest packages that have not been imported yet.
@@ -917,13 +960,22 @@
 		if c.surrounding != nil {
 			prefix = c.surrounding.Prefix()
 		}
-		var mu sync.Mutex
+		var (
+			mu               sync.Mutex
+			initialItemCount = len(c.items)
+		)
 		add := func(pkg imports.ImportFix) {
 			mu.Lock()
 			defer mu.Unlock()
 			if _, ok := seen[pkg.IdentName]; ok {
 				return
 			}
+
+			if len(c.items)-initialItemCount >= maxUnimportedPackageNames {
+				cancel()
+				return
+			}
+
 			// Rank unimported packages significantly lower than other results.
 			score := 0.01 * float64(pkg.Relevance)
 
@@ -939,18 +991,6 @@
 					name:       pkg.StmtInfo.Name,
 				},
 			})
-
-			if len(c.items) >= unimportedTarget {
-				cancel()
-			}
-			c.found(candidate{
-				obj:   obj,
-				score: score,
-				imp: &importInfo{
-					importPath: pkg.StmtInfo.ImportPath,
-					name:       pkg.StmtInfo.Name,
-				},
-			})
 		}
 		if err := c.snapshot.View().RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
 			return imports.GetAllCandidates(ctx, add, prefix, c.filename, c.pkg.GetTypes().Name(), opts)
@@ -970,6 +1010,9 @@
 		}
 	}
 
+	// Add keyword completion items appropriate in the current context.
+	c.addKeywordCompletions()
+
 	return nil
 }
 
@@ -1256,9 +1299,10 @@
 	// objKind is a mask of expected kinds of types such as "map", "slice", etc.
 	objKind objKind
 
-	// variadic is true if objType is a slice type from an initial
-	// variadic param.
-	variadic bool
+	// variadicType is the scalar variadic element type. For example,
+	// when completing "append([]T{}, <>)" objType is []T and
+	// variadicType is T.
+	variadicType types.Type
 
 	// modifiers are prefixes such as "*", "&" or "<-" that influence how
 	// a candidate type relates to the expected type.
@@ -1270,6 +1314,26 @@
 	// typeName holds information about the expected type name at
 	// position, if any.
 	typeName typeNameInference
+
+	// assignees are the types that would receive a function call's
+	// results at the position. For example:
+	//
+	// foo := 123
+	// foo, bar := <>
+	//
+	// at "<>", the assignees are [int, <invalid>].
+	assignees []types.Type
+
+	// variadicAssignees is true if we could be completing an inner
+	// function call that fills out an outer function call's variadic
+	// params. For example:
+	//
+	// func foo(int, ...string) {}
+	//
+	// foo(<>)         // variadicAssignees=true
+	// foo(bar<>)      // variadicAssignees=true
+	// foo(bar, baz<>) // variadicAssignees=false
+	variadicAssignees bool
 }
 
 // typeNameInference holds information about the expected type name at
@@ -1296,7 +1360,6 @@
 
 	if c.enclosingCompositeLiteral != nil {
 		inf.objType = c.expectedCompositeLiteralType()
-		return inf
 	}
 
 Nodes:
@@ -1322,6 +1385,20 @@
 				if tv, ok := c.pkg.GetTypesInfo().Types[node.Lhs[i]]; ok {
 					inf.objType = tv.Type
 				}
+
+				// If we have a single expression on the RHS, record the LHS
+				// assignees so we can favor multi-return function calls with
+				// matching result values.
+				if len(node.Rhs) <= 1 {
+					for _, lhs := range node.Lhs {
+						inf.assignees = append(inf.assignees, c.pkg.GetTypesInfo().TypeOf(lhs))
+					}
+				} else {
+					// Otherwse, record our single assignee, even if its type is
+					// not available. We use this info to downrank functions
+					// with the wrong number of result values.
+					inf.assignees = append(inf.assignees, c.pkg.GetTypesInfo().TypeOf(node.Lhs[i]))
+				}
 			}
 			return inf
 		case *ast.ValueSpec:
@@ -1352,12 +1429,28 @@
 							beyondLastParam = exprIdx >= numParams
 						)
 
+						// If we have one or zero arg expressions, we may be
+						// completing to a function call that returns multiple
+						// values, in turn getting passed in to the surrounding
+						// call. Record the assignees so we can favor function
+						// calls that return matching values.
+						if len(node.Args) <= 1 {
+							for i := 0; i < sig.Params().Len(); i++ {
+								inf.assignees = append(inf.assignees, sig.Params().At(i).Type())
+							}
+
+							// Record that we may be completing into variadic parameters.
+							inf.variadicAssignees = sig.Variadic()
+						}
+
 						if sig.Variadic() {
+							variadicType := deslice(sig.Params().At(numParams - 1).Type())
+
 							// If we are beyond the last param or we are the last
 							// param w/ further expressions, we expect a single
 							// variadic item.
 							if beyondLastParam || isLastParam && len(node.Args) > numParams {
-								inf.objType = sig.Params().At(numParams - 1).Type().(*types.Slice).Elem()
+								inf.objType = variadicType
 								break Nodes
 							}
 
@@ -1365,7 +1458,7 @@
 							// completing the variadic positition (i.e. we expect a
 							// slice type []T or an individual item T).
 							if isLastParam {
-								inf.variadic = true
+								inf.variadicType = variadicType
 							}
 						}
 
@@ -1375,8 +1468,6 @@
 						} else {
 							inf.objType = sig.Params().At(exprIdx).Type()
 						}
-
-						break Nodes
 					}
 				}
 
@@ -1384,16 +1475,11 @@
 					obj := c.pkg.GetTypesInfo().ObjectOf(funIdent)
 
 					if obj != nil && obj.Parent() == types.Universe {
-						inf.objKind |= c.builtinArgKind(obj, node)
-
-						if obj.Name() == "new" {
-							inf.typeName.wantTypeName = true
-						}
-
 						// Defer call to builtinArgType so we can provide it the
 						// inferred type from its parent node.
 						defer func() {
-							inf.objType, inf.variadic = c.builtinArgType(obj, node, inf.objType)
+							inf = c.builtinArgType(obj, node, inf)
+							inf.objKind = c.builtinArgKind(obj, node)
 						}()
 
 						// The expected type of builtin arguments like append() is
@@ -1473,7 +1559,6 @@
 				inf.modifiers = append(inf.modifiers, typeModifier{mod: address})
 			case token.ARROW:
 				inf.modifiers = append(inf.modifiers, typeModifier{mod: chanRead})
-				inf.objKind |= kindChan
 			}
 		default:
 			if breaksExpectedTypeInference(node) {
@@ -1538,7 +1623,7 @@
 // matchesVariadic returns true if we are completing a variadic
 // parameter and candType is a compatible slice type.
 func (ci candidateInference) matchesVariadic(candType types.Type) bool {
-	return ci.variadic && types.AssignableTo(ci.objType, candType)
+	return ci.variadicType != nil && types.AssignableTo(ci.objType, candType)
 
 }
 
@@ -1668,6 +1753,11 @@
 				wantComparable = c.pos == n.Pos()+token.Pos(len("map["))
 			}
 			break Nodes
+		case *ast.ValueSpec:
+			if n.Type != nil && n.Type.Pos() <= c.pos && c.pos <= n.Type.End() {
+				wantTypeName = true
+			}
+			break Nodes
 		default:
 			if breaksExpectedTypeInference(p) {
 				return typeNameInference{}
@@ -1687,10 +1777,81 @@
 	return types.NewVar(token.NoPos, c.pkg.GetTypes(), "", T)
 }
 
-// matchingCandidate reports whether a candidate matches our type
-// inferences. seen is used to detect recursive types in certain cases
-// and should be set to nil when calling matchingCandidate.
-func (c *completer) matchingCandidate(cand *candidate, seen map[types.Type]struct{}) bool {
+// anyCandType reports whether f returns true for any candidate type
+// derivable from c. For example, from "foo" we might derive "&foo",
+// and "foo()".
+func (c *candidate) anyCandType(f func(t types.Type, addressable bool) bool) bool {
+	if c.obj == nil || c.obj.Type() == nil {
+		return false
+	}
+
+	objType := c.obj.Type()
+
+	if f(objType, c.addressable) {
+		return true
+	}
+
+	// If c is a func type with a single result, offer the result type.
+	if sig, ok := objType.Underlying().(*types.Signature); ok {
+		if sig.Results().Len() == 1 && f(sig.Results().At(0).Type(), false) {
+			// Mark the candidate so we know to append "()" when formatting.
+			c.expandFuncCall = true
+			return true
+		}
+	}
+
+	var (
+		seenPtrTypes map[types.Type]bool
+		ptrType      = objType
+		ptrDepth     int
+	)
+
+	// Check if dereferencing c would match our type inference. We loop
+	// since c could have arbitrary levels of pointerness.
+	for {
+		ptr, ok := ptrType.Underlying().(*types.Pointer)
+		if !ok {
+			break
+		}
+
+		ptrDepth++
+
+		// Avoid pointer type cycles.
+		if seenPtrTypes[ptrType] {
+			break
+		}
+
+		if _, named := ptrType.(*types.Named); named {
+			// Lazily allocate "seen" since it isn't used normally.
+			if seenPtrTypes == nil {
+				seenPtrTypes = make(map[types.Type]bool)
+			}
+
+			// Track named pointer types we have seen to detect cycles.
+			seenPtrTypes[ptrType] = true
+		}
+
+		if f(ptr.Elem(), false) {
+			// Mark the candidate so we know to prepend "*" when formatting.
+			c.dereference = ptrDepth
+			return true
+		}
+
+		ptrType = ptr.Elem()
+	}
+
+	// Check if c is addressable and a pointer to c matches our type inference.
+	if c.addressable && f(types.NewPointer(objType), false) {
+		// Mark the candidate so we know to prepend "&" when formatting.
+		c.takeAddress = true
+		return true
+	}
+
+	return false
+}
+
+// matchingCandidate reports whether cand matches our type inferences.
+func (c *completer) matchingCandidate(cand *candidate) bool {
 	if isTypeName(cand.obj) {
 		return c.matchingTypeName(cand)
 	} else if c.wantTypeName() {
@@ -1698,118 +1859,179 @@
 		return false
 	}
 
+	if c.inference.candTypeMatches(cand) {
+		return true
+	}
+
 	candType := cand.obj.Type()
 	if candType == nil {
 		return false
 	}
 
+	if sig, ok := candType.Underlying().(*types.Signature); ok {
+		if c.inference.assigneesMatch(cand, sig) {
+			// Invoke the candidate if its results are multi-assignable.
+			cand.expandFuncCall = true
+			return true
+		}
+	}
+
 	// Default to invoking *types.Func candidates. This is so function
 	// completions in an empty statement (or other cases with no expected type)
 	// are invoked by default.
 	cand.expandFuncCall = isFunc(cand.obj)
 
-	typeMatches := func(expType, candType types.Type) bool {
-		if expType == nil {
-			// If we don't expect a specific type, check if we expect a particular
-			// kind of object (map, slice, etc).
-			if c.inference.objKind > 0 {
-				return c.inference.objKind&candKind(candType) > 0
-			}
+	return false
+}
 
-			return false
-		}
+// candTypeMatches reports whether cand makes a good completion
+// candidate given the candidate inference. cand's score may be
+// mutated to downrank the candidate in certain situations.
+func (ci *candidateInference) candTypeMatches(cand *candidate) bool {
+	expTypes := make([]types.Type, 0, 2)
+	if ci.objType != nil {
+		expTypes = append(expTypes, ci.objType)
+	}
+	if ci.variadicType != nil {
+		expTypes = append(expTypes, ci.variadicType)
+	}
 
+	return cand.anyCandType(func(candType types.Type, addressable bool) bool {
 		// Take into account any type modifiers on the expected type.
-		candType = c.inference.applyTypeModifiers(candType, cand.addressable)
+		candType = ci.applyTypeModifiers(candType, addressable)
 		if candType == nil {
 			return false
 		}
 
-		// Handle untyped values specially since AssignableTo gives false negatives
-		// for them (see https://golang.org/issue/32146).
-		if candBasic, ok := candType.Underlying().(*types.Basic); ok {
-			if wantBasic, ok := expType.Underlying().(*types.Basic); ok {
-				// Make sure at least one of them is untyped.
-				if isUntyped(candType) || isUntyped(expType) {
-					// Check that their constant kind (bool|int|float|complex|string) matches.
-					// This doesn't take into account the constant value, so there will be some
-					// false positives due to integer sign and overflow.
-					if candBasic.Info()&types.IsConstType == wantBasic.Info()&types.IsConstType {
-						// Lower candidate score if the types are not identical. This avoids
-						// ranking untyped constants above candidates with an exact type
-						// match. Don't lower score of builtin constants (e.g. "true").
-						if !types.Identical(candType, expType) && cand.obj.Parent() != types.Universe {
-							cand.score /= 2
-						}
-						return true
-					}
+		if ci.convertibleTo != nil && types.ConvertibleTo(candType, ci.convertibleTo) {
+			return true
+		}
+
+		if len(expTypes) == 0 {
+			// If we have no expected type but were able to apply type
+			// modifiers to our candidate type, count that as a match. This
+			// handles cases like:
+			//
+			//   var foo chan int
+			//   <-fo<>
+			//
+			// There is no exected type at "<>", but we were able to apply
+			// the "<-" type modifier to "foo", so it matches.
+			if len(ci.modifiers) > 0 {
+				return true
+			}
+
+			// If we have no expected type, fall back to checking the
+			// expected "kind" of object, if available.
+			return ci.kindMatches(candType)
+		}
+
+		for _, expType := range expTypes {
+			matches, untyped := ci.typeMatches(expType, candType)
+			if !matches {
+				continue
+			}
+
+			// Lower candidate score for untyped conversions. This avoids
+			// ranking untyped constants above candidates with an exact type
+			// match. Don't lower score of builtin constants, e.g. "true".
+			if untyped && !types.Identical(candType, expType) && cand.obj.Parent() != types.Universe {
+				cand.score /= 2
+			}
+
+			return true
+		}
+
+		return false
+	})
+}
+
+// typeMatches reports whether an object of candType makes a good
+// completion candidate given the expected type expType. It also
+// returns a second bool which is true if both types are basic types
+// of the same kind, and at least one is untyped.
+func (ci *candidateInference) typeMatches(expType, candType types.Type) (bool, bool) {
+	// Handle untyped values specially since AssignableTo gives false negatives
+	// for them (see https://golang.org/issue/32146).
+	if candBasic, ok := candType.Underlying().(*types.Basic); ok {
+		if wantBasic, ok := expType.Underlying().(*types.Basic); ok {
+			// Make sure at least one of them is untyped.
+			if isUntyped(candType) || isUntyped(expType) {
+				// Check that their constant kind (bool|int|float|complex|string) matches.
+				// This doesn't take into account the constant value, so there will be some
+				// false positives due to integer sign and overflow.
+				if candBasic.Info()&types.IsConstType == wantBasic.Info()&types.IsConstType {
+					return true, true
 				}
 			}
 		}
-
-		// AssignableTo covers the case where the types are equal, but also handles
-		// cases like assigning a concrete type to an interface type.
-		return types.AssignableTo(candType, expType)
 	}
 
-	if typeMatches(c.inference.objType, candType) {
-		// If obj's type matches, we don't want to expand to an invocation of obj.
-		cand.expandFuncCall = false
-		return true
+	// AssignableTo covers the case where the types are equal, but also handles
+	// cases like assigning a concrete type to an interface type.
+	return types.AssignableTo(candType, expType), false
+}
+
+// kindMatches reports whether candType's kind matches our expected
+// kind (e.g. slice, map, etc.).
+func (ci *candidateInference) kindMatches(candType types.Type) bool {
+	return ci.objKind&candKind(candType) > 0
+}
+
+// assigneesMatch reports whether an invocation of sig matches the
+// number and type of any assignees.
+func (ci *candidateInference) assigneesMatch(cand *candidate, sig *types.Signature) bool {
+	if len(ci.assignees) == 0 {
+		return false
 	}
 
-	// Try using a function's return type as its type.
-	if sig, ok := candType.Underlying().(*types.Signature); ok && sig.Results().Len() == 1 {
-		if typeMatches(c.inference.objType, sig.Results().At(0).Type()) {
-			// If obj's return value matches the expected type, we need to invoke obj
-			// in the completion.
-			cand.expandFuncCall = true
-			return true
-		}
+	// Uniresult functions are always usable and are handled by the
+	// normal, non-assignees type matching logic.
+	if sig.Results().Len() == 1 {
+		return false
 	}
 
-	// When completing the variadic parameter, if the expected type is
-	// []T then check candType against T.
-	if c.inference.variadic {
-		if slice, ok := c.inference.objType.(*types.Slice); ok && typeMatches(slice.Elem(), candType) {
-			return true
-		}
+	var numberOfResultsCouldMatch bool
+	if ci.variadicAssignees {
+		numberOfResultsCouldMatch = sig.Results().Len() >= len(ci.assignees)-1
+	} else {
+		numberOfResultsCouldMatch = sig.Results().Len() == len(ci.assignees)
 	}
 
-	if c.inference.convertibleTo != nil && types.ConvertibleTo(candType, c.inference.convertibleTo) {
-		return true
+	// If our signature doesn't return the right number of values, it's
+	// not a match, so downrank it. For example:
+	//
+	//  var foo func() (int, int)
+	//  a, b, c := <> // downrank "foo()" since it only returns two values
+	if !numberOfResultsCouldMatch {
+		cand.score /= 2
+		return false
 	}
 
-	// Check if dereferencing cand would match our type inference.
-	if ptr, ok := cand.obj.Type().Underlying().(*types.Pointer); ok {
-		// Notice if we have already encountered this pointer type before.
-		_, saw := seen[cand.obj.Type()]
+	// If at least one assignee has a valid type, and all valid
+	// assignees match the corresponding sig result value, the signature
+	// is a match.
+	allMatch := false
+	for i := 0; i < sig.Results().Len(); i++ {
+		var assignee types.Type
 
-		if _, named := cand.obj.Type().(*types.Named); named {
-			// Lazily allocate "seen" since it isn't used normally.
-			if seen == nil {
-				seen = make(map[types.Type]struct{})
+		// If we are completing into variadic parameters, deslice the
+		// expected variadic type.
+		if ci.variadicAssignees && i >= len(ci.assignees)-1 {
+			assignee = ci.assignees[len(ci.assignees)-1]
+			if elem := deslice(assignee); elem != nil {
+				assignee = elem
 			}
-
-			// Track named pointer types we have seen to detect cycles.
-			seen[cand.obj.Type()] = struct{}{}
+		} else {
+			assignee = ci.assignees[i]
 		}
 
-		if !saw && c.matchingCandidate(&candidate{obj: c.fakeObj(ptr.Elem())}, seen) {
-			// Mark the candidate so we know to prepend "*" when formatting.
-			cand.dereference = true
-			return true
+		allMatch, _ = ci.typeMatches(assignee, sig.Results().At(i).Type())
+		if !allMatch {
+			break
 		}
 	}
-
-	// Check if cand is addressable and a pointer to cand matches our type inference.
-	if cand.addressable && c.matchingCandidate(&candidate{obj: c.fakeObj(types.NewPointer(candType))}, seen) {
-		// Mark the candidate so we know to prepend "&" when formatting.
-		cand.takeAddress = true
-		return true
-	}
-
-	return false
+	return allMatch
 }
 
 func (c *completer) matchingTypeName(cand *candidate) bool {
diff --git a/internal/lsp/source/completion_builtin.go b/internal/lsp/source/completion_builtin.go
index da2c5f8..d65eb8f 100644
--- a/internal/lsp/source/completion_builtin.go
+++ b/internal/lsp/source/completion_builtin.go
@@ -49,23 +49,25 @@
 }
 
 // builtinArgType infers the type of an argument to a builtin
-// function. "parentType" is the inferred type for the builtin call's
-// parent node.
-func (c *completer) builtinArgType(obj types.Object, call *ast.CallExpr, parentType types.Type) (infType types.Type, variadic bool) {
-	exprIdx := exprAtPos(c.pos, call.Args)
+// function. parentInf is the inferred type info for the builtin
+// call's parent node.
+func (c *completer) builtinArgType(obj types.Object, call *ast.CallExpr, parentInf candidateInference) candidateInference {
+	var (
+		exprIdx = exprAtPos(c.pos, call.Args)
+		inf     = candidateInference{}
+	)
 
 	switch obj.Name() {
 	case "append":
-		// Check if we are completing the variadic append() param.
-		variadic = exprIdx == 1 && len(call.Args) <= 2
-		infType = parentType
+		inf.objType = parentInf.objType
 
-		// If we are completing an individual element of the variadic
-		// param, "deslice" the expected type.
-		if !variadic && exprIdx > 0 {
-			if slice, ok := parentType.(*types.Slice); ok {
-				infType = slice.Elem()
-			}
+		// Check if we are completing the variadic append() param.
+		if exprIdx == 1 && len(call.Args) <= 2 {
+			inf.variadicType = deslice(inf.objType)
+		} else if exprIdx > 0 {
+			// If we are completing an individual element of the variadic
+			// param, "deslice" the expected type.
+			inf.objType = deslice(inf.objType)
 		}
 	case "delete":
 		if exprIdx > 0 && len(call.Args) > 0 {
@@ -73,7 +75,7 @@
 			firstArgType := c.pkg.GetTypesInfo().TypeOf(call.Args[0])
 			if firstArgType != nil {
 				if mt, ok := firstArgType.Underlying().(*types.Map); ok {
-					infType = mt.Key()
+					inf.objType = mt.Key()
 				}
 			}
 		}
@@ -88,22 +90,26 @@
 
 		// Fill in expected type of either arg if the other is already present.
 		if exprIdx == 1 && t1 != nil {
-			infType = t1
+			inf.objType = t1
 		} else if exprIdx == 0 && t2 != nil {
-			infType = t2
+			inf.objType = t2
 		}
 	case "new":
-		if parentType != nil {
+		inf.typeName.wantTypeName = true
+		if parentInf.objType != nil {
 			// Expected type for "new" is the de-pointered parent type.
-			if ptr, ok := parentType.Underlying().(*types.Pointer); ok {
-				infType = ptr.Elem()
+			if ptr, ok := parentInf.objType.Underlying().(*types.Pointer); ok {
+				inf.objType = ptr.Elem()
 			}
 		}
 	case "make":
 		if exprIdx == 0 {
-			infType = parentType
+			inf.typeName.wantTypeName = true
+			inf.objType = parentInf.objType
+		} else {
+			inf.objType = types.Typ[types.Int]
 		}
 	}
 
-	return infType, variadic
+	return inf
 }
diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go
index c5e8bbd..600ad5c 100644
--- a/internal/lsp/source/completion_format.go
+++ b/internal/lsp/source/completion_format.go
@@ -134,8 +134,10 @@
 	var prefixOp string
 	if cand.takeAddress {
 		prefixOp = "&"
-	} else if cand.makePointer || cand.dereference {
+	} else if cand.makePointer {
 		prefixOp = "*"
+	} else if cand.dereference > 0 {
+		prefixOp = strings.Repeat("*", cand.dereference)
 	}
 
 	if prefixOp != "" {
@@ -177,7 +179,7 @@
 	if !pos.IsValid() {
 		return item, nil
 	}
-	uri := span.FileURI(pos.Filename)
+	uri := span.URIFromPath(pos.Filename)
 
 	// Find the source file of the candidate, starting from a package
 	// that should have it in its dependencies.
@@ -211,7 +213,7 @@
 		return nil, nil
 	}
 
-	uri := span.FileURI(c.filename)
+	uri := span.URIFromPath(c.filename)
 	var ph ParseGoHandle
 	for _, h := range c.pkg.CompiledGoFiles() {
 		if h.File().Identity().URI == uri {
diff --git a/internal/lsp/source/completion_keywords.go b/internal/lsp/source/completion_keywords.go
index dcb5c95..4cb7117 100644
--- a/internal/lsp/source/completion_keywords.go
+++ b/internal/lsp/source/completion_keywords.go
@@ -4,8 +4,6 @@
 	"go/ast"
 
 	"golang.org/x/tools/internal/lsp/protocol"
-
-	errors "golang.org/x/xerrors"
 )
 
 const (
@@ -36,22 +34,48 @@
 	VAR         = "var"
 )
 
-// keyword looks at the current scope of an *ast.Ident and recommends keywords
-func (c *completer) keyword() error {
-	keywordScore := float64(0.9)
-	if _, ok := c.path[0].(*ast.Ident); !ok {
-		// TODO(golang/go#34009): Support keyword completion in any context
-		return errors.Errorf("keywords are currently only recommended for identifiers")
-	}
-	// Track which keywords we've already determined are in a valid scope
-	// Use score to order keywords by how close we are to where they are useful
-	valid := make(map[string]float64)
+// addKeywordCompletions offers keyword candidates appropriate at the position.
+func (c *completer) addKeywordCompletions() {
+	const keywordScore = 0.9
 
-	// only suggest keywords at the begnning of a statement
+	seen := make(map[string]bool)
+
+	// addKeywords dedupes and adds completion items for the specified
+	// keywords with the specified score.
+	addKeywords := func(score float64, kws ...string) {
+		for _, kw := range kws {
+			if seen[kw] {
+				continue
+			}
+			seen[kw] = true
+
+			if c.matcher.Score(kw) > 0 {
+				c.items = append(c.items, CompletionItem{
+					Label:      kw,
+					Kind:       protocol.KeywordCompletion,
+					InsertText: kw,
+					Score:      score,
+				})
+			}
+		}
+	}
+
+	// If we are at the file scope, only offer decl keywords. We don't
+	// get *ast.Idents at the file scope because non-keyword identifiers
+	// turn into *ast.BadDecl, not *ast.Ident.
+	if len(c.path) == 1 || isASTFile(c.path[1]) {
+		addKeywords(keywordScore, TYPE, CONST, VAR, FUNC, IMPORT)
+		return
+	} else if _, ok := c.path[0].(*ast.Ident); !ok {
+		// Otherwise only offer keywords if the client is completing an identifier.
+		return
+	}
+
+	// Only suggest keywords if we are beginning a statement.
 	switch c.path[1].(type) {
 	case *ast.BlockStmt, *ast.CommClause, *ast.CaseClause, *ast.ExprStmt:
 	default:
-		return nil
+		return
 	}
 
 	// Filter out keywords depending on scope
@@ -62,7 +86,7 @@
 		case *ast.CaseClause:
 			// only recommend "fallthrough" and "break" within the bodies of a case clause
 			if c.pos > node.Colon {
-				valid[BREAK] = keywordScore
+				addKeywords(keywordScore, BREAK)
 				// "fallthrough" is only valid in switch statements.
 				// A case clause is always nested within a block statement in a switch statement,
 				// that block statement is nested within either a TypeSwitchStmt or a SwitchStmt.
@@ -70,45 +94,23 @@
 					continue
 				}
 				if _, ok := path[i+2].(*ast.SwitchStmt); ok {
-					valid[FALLTHROUGH] = keywordScore
+					addKeywords(keywordScore, FALLTHROUGH)
 				}
 			}
 		case *ast.CommClause:
 			if c.pos > node.Colon {
-				valid[BREAK] = keywordScore
+				addKeywords(keywordScore, BREAK)
 			}
 		case *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.SwitchStmt:
-			valid[CASE] = keywordScore + lowScore
-			valid[DEFAULT] = keywordScore + lowScore
+			addKeywords(keywordScore+lowScore, CASE, DEFAULT)
 		case *ast.ForStmt:
-			valid[BREAK] = keywordScore
-			valid[CONTINUE] = keywordScore
+			addKeywords(keywordScore, BREAK, CONTINUE)
 		// This is a bit weak, functions allow for many keywords
 		case *ast.FuncDecl:
 			if node.Body != nil && c.pos > node.Body.Lbrace {
-				valid[DEFER] = keywordScore - lowScore
-				valid[RETURN] = keywordScore - lowScore
-				valid[FOR] = keywordScore - lowScore
-				valid[GO] = keywordScore - lowScore
-				valid[SWITCH] = keywordScore - lowScore
-				valid[SELECT] = keywordScore - lowScore
-				valid[IF] = keywordScore - lowScore
-				valid[ELSE] = keywordScore - lowScore
-				valid[VAR] = keywordScore - lowScore
-				valid[CONST] = keywordScore - lowScore
+				addKeywords(keywordScore-lowScore, DEFER, RETURN, FOR, GO, SWITCH, SELECT, IF, ELSE, VAR, CONST, GOTO, TYPE)
 			}
 		}
 	}
 
-	for ident, score := range valid {
-		if c.matcher.Score(ident) > 0 {
-			c.items = append(c.items, CompletionItem{
-				Label:      ident,
-				Kind:       protocol.KeywordCompletion,
-				InsertText: ident,
-				Score:      score,
-			})
-		}
-	}
-	return nil
 }
diff --git a/internal/lsp/source/completion_literal.go b/internal/lsp/source/completion_literal.go
index b95974e..8c9917c 100644
--- a/internal/lsp/source/completion_literal.go
+++ b/internal/lsp/source/completion_literal.go
@@ -26,7 +26,7 @@
 
 	expType := c.inference.objType
 
-	if c.inference.variadic {
+	if c.inference.variadicType != nil {
 		// Don't offer literal slice candidates for variadic arguments.
 		// For example, don't offer "[]interface{}{}" in "fmt.Print(<>)".
 		if c.inference.matchesVariadic(literalType) {
@@ -35,9 +35,7 @@
 
 		// Otherwise, consider our expected type to be the variadic
 		// element type, not the slice type.
-		if slice, ok := expType.(*types.Slice); ok {
-			expType = slice.Elem()
-		}
+		expType = c.inference.variadicType
 	}
 
 	// Avoid literal candidates if the expected type is an empty
@@ -75,7 +73,7 @@
 		cand.addressable = true
 	}
 
-	if !c.matchingCandidate(&cand, nil) {
+	if !c.matchingCandidate(&cand) {
 		return
 	}
 
diff --git a/internal/lsp/source/deep_completion.go b/internal/lsp/source/deep_completion.go
index 74f2bbf..674ce49 100644
--- a/internal/lsp/source/deep_completion.go
+++ b/internal/lsp/source/deep_completion.go
@@ -138,7 +138,7 @@
 	return false
 }
 
-// deepSearch searches through obj's subordinate objects for more
+// deepSearch searches through cand's subordinate objects for more
 // completion items.
 func (c *completer) deepSearch(cand candidate) {
 	if c.deepState.maxDepth == 0 {
diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
index 9e04898..ee81f53 100644
--- a/internal/lsp/source/diagnostics.go
+++ b/internal/lsp/source/diagnostics.go
@@ -6,8 +6,12 @@
 
 import (
 	"context"
+	"fmt"
+	"go/ast"
+	"strconv"
 	"strings"
 
+	"golang.org/x/mod/modfile"
 	"golang.org/x/tools/go/analysis"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/telemetry"
@@ -39,7 +43,7 @@
 	Message string
 }
 
-func Diagnostics(ctx context.Context, snapshot Snapshot, ph PackageHandle, withAnalysis bool) (map[FileIdentity][]Diagnostic, bool, error) {
+func Diagnostics(ctx context.Context, snapshot Snapshot, ph PackageHandle, missingModules map[string]*modfile.Require, withAnalysis bool) (map[FileIdentity][]Diagnostic, bool, error) {
 	// If we are missing dependencies, it may because the user's workspace is
 	// not correctly configured. Report errors, if possible.
 	var warn bool
@@ -56,12 +60,41 @@
 	if len(pkg.CompiledGoFiles()) == 1 && hasUndeclaredErrors(pkg) {
 		warn = true
 	}
+
+	isMissingModule := false
+	for _, imp := range pkg.Imports() {
+		if _, ok := missingModules[imp.PkgPath()]; ok {
+			isMissingModule = true
+			continue
+		}
+		for dep, req := range missingModules {
+			// If the import is a package of the dependency, then add the package to the map, this will
+			// eliminate the need to do this prefix package search on each import for each file.
+			// Example:
+			// import (
+			//   "golang.org/x/tools/go/expect"
+			//   "golang.org/x/tools/go/packages"
+			// )
+			// They both are related to the same module: "golang.org/x/tools"
+			if req != nil && strings.HasPrefix(imp.PkgPath(), dep) {
+				missingModules[imp.PkgPath()] = req
+				isMissingModule = true
+				break
+			}
+		}
+	}
+
 	// Prepare the reports we will send for the files in this package.
 	reports := make(map[FileIdentity][]Diagnostic)
 	for _, fh := range pkg.CompiledGoFiles() {
 		if err := clearReports(snapshot, reports, fh.File().Identity().URI); err != nil {
 			return nil, warn, err
 		}
+		if isMissingModule {
+			if err := missingModulesDiagnostics(ctx, snapshot, reports, missingModules, fh.File().Identity().URI); err != nil {
+				return nil, warn, err
+			}
+		}
 	}
 	// Prepare any additional reports for the errors in this package.
 	for _, e := range pkg.GetErrors() {
@@ -72,7 +105,7 @@
 		// If no file is associated with the error, pick an open file from the package.
 		if e.URI.Filename() == "" {
 			for _, ph := range pkg.CompiledGoFiles() {
-				if snapshot.View().Session().IsOpen(ph.File().Identity().URI) {
+				if snapshot.IsOpen(ph.File().Identity().URI) {
 					e.URI = ph.File().Identity().URI
 				}
 			}
@@ -87,9 +120,13 @@
 		return nil, warn, err
 	}
 	if !hadDiagnostics && withAnalysis {
+		// Exit early if the context has been canceled. This also protects us
+		// from a race on Options, see golang/go#36699.
+		if ctx.Err() != nil {
+			return nil, warn, ctx.Err()
+		}
 		// If we don't have any list, parse, or type errors, run analyses.
 		if err := analyses(ctx, snapshot, reports, ph, snapshot.View().Options().DisabledAnalyses); err != nil {
-			// Exit early if the context has been canceled.
 			if ctx.Err() != nil {
 				return nil, warn, ctx.Err()
 			}
@@ -112,7 +149,7 @@
 	if err != nil {
 		return FileIdentity{}, nil, err
 	}
-	reports, _, err := Diagnostics(ctx, snapshot, ph, true)
+	reports, _, err := Diagnostics(ctx, snapshot, ph, nil, true)
 	if err != nil {
 		return FileIdentity{}, nil, err
 	}
@@ -178,6 +215,62 @@
 	return nonEmptyDiagnostics, nil
 }
 
+func missingModulesDiagnostics(ctx context.Context, snapshot Snapshot, reports map[FileIdentity][]Diagnostic, missingModules map[string]*modfile.Require, uri span.URI) error {
+	if snapshot.View().Ignore(uri) || len(missingModules) == 0 {
+		return nil
+	}
+	fh, err := snapshot.GetFile(uri)
+	if err != nil {
+		return err
+	}
+	file, _, m, _, err := snapshot.View().Session().Cache().ParseGoHandle(fh, ParseHeader).Parse(ctx)
+	if err != nil {
+		log.Error(ctx, "could not parse go file when checking for missing modules", err)
+		return err
+	}
+	// Make a dependency->import map to improve performance when finding missing dependencies.
+	imports := make(map[string]*ast.ImportSpec)
+	for _, imp := range file.Imports {
+		if imp.Path == nil {
+			continue
+		}
+		if target, err := strconv.Unquote(imp.Path.Value); err == nil {
+			imports[target] = imp
+		}
+	}
+	// If the go file has 0 imports, then we do not need to check for missing dependencies.
+	if len(imports) == 0 {
+		return nil
+	}
+	if reports[fh.Identity()] == nil {
+		reports[fh.Identity()] = []Diagnostic{}
+	}
+	for mod, req := range missingModules {
+		if req.Syntax == nil {
+			continue
+		}
+		imp, ok := imports[mod]
+		if !ok {
+			continue
+		}
+		spn, err := span.NewRange(snapshot.View().Session().Cache().FileSet(), imp.Path.Pos(), imp.Path.End()).Span()
+		if err != nil {
+			return err
+		}
+		rng, err := m.Range(spn)
+		if err != nil {
+			return err
+		}
+		reports[fh.Identity()] = append(reports[fh.Identity()], Diagnostic{
+			Message:  fmt.Sprintf("%s is not in your go.mod file.", req.Mod.Path),
+			Range:    rng,
+			Source:   "go mod tidy",
+			Severity: protocol.SeverityWarning,
+		})
+	}
+	return nil
+}
+
 func analyses(ctx context.Context, snapshot Snapshot, reports map[FileIdentity][]Diagnostic, ph PackageHandle, disabledAnalyses map[string]struct{}) error {
 	var analyzers []*analysis.Analyzer
 	for _, a := range snapshot.View().Options().Analyzers {
@@ -262,7 +355,7 @@
 			}
 		}
 	}
-	return true
+	return len(fixes) > 0
 }
 
 // hasUndeclaredErrors returns true if a package has a type error
diff --git a/internal/lsp/source/errors.go b/internal/lsp/source/errors.go
deleted file mode 100644
index 5858603..0000000
--- a/internal/lsp/source/errors.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package source
-
-import (
-	"bytes"
-	"context"
-	"fmt"
-	"os/exec"
-
-	errors "golang.org/x/xerrors"
-)
-
-// InvokeGo returns the output of a go command invocation.
-// It does not try to recover from errors.
-func InvokeGo(ctx context.Context, dir string, env []string, args ...string) (*bytes.Buffer, error) {
-	stdout := new(bytes.Buffer)
-	stderr := new(bytes.Buffer)
-	cmd := exec.CommandContext(ctx, "go", args...)
-	// On darwin the cwd gets resolved to the real path, which breaks anything that
-	// expects the working directory to keep the original path, including the
-	// go command when dealing with modules.
-	// The Go stdlib has a special feature where if the cwd and the PWD are the
-	// same node then it trusts the PWD, so by setting it in the env for the child
-	// process we fix up all the paths returned by the go command.
-	cmd.Env = append(append([]string{}, env...), "PWD="+dir)
-	cmd.Dir = dir
-	cmd.Stdout = stdout
-	cmd.Stderr = stderr
-
-	if err := cmd.Run(); err != nil {
-		// Check for 'go' executable not being found.
-		if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
-			return nil, fmt.Errorf("'gopls requires 'go', but %s", exec.ErrNotFound)
-		}
-		if ctx.Err() != nil {
-			return nil, ctx.Err()
-		}
-		return stdout, errors.Errorf("err: %v: stderr: %s", err, stderr)
-	}
-	return stdout, nil
-}
diff --git a/internal/lsp/source/folding_range.go b/internal/lsp/source/folding_range.go
index e7b06e3..21cfda1 100644
--- a/internal/lsp/source/folding_range.go
+++ b/internal/lsp/source/folding_range.go
@@ -19,7 +19,7 @@
 	// TODO(suzmue): consider limiting the number of folding ranges returned, and
 	// implement a way to prioritize folding ranges in that case.
 	pgh := snapshot.View().Session().Cache().ParseGoHandle(fh, ParseFull)
-	file, m, _, err := pgh.Parse(ctx)
+	file, _, m, _, err := pgh.Parse(ctx)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go
index 2149fea..d8efad0 100644
--- a/internal/lsp/source/format.go
+++ b/internal/lsp/source/format.go
@@ -28,7 +28,7 @@
 	defer done()
 
 	pgh := snapshot.View().Session().Cache().ParseGoHandle(fh, ParseFull)
-	file, m, parseErrors, err := pgh.Parse(ctx)
+	file, _, m, parseErrors, err := pgh.Parse(ctx)
 	if err != nil {
 		return nil, err
 	}
@@ -80,13 +80,10 @@
 	ctx, done := trace.StartSpan(ctx, "source.AllImportsFixes")
 	defer done()
 
-	pkg, pgh, err := getParsedFile(ctx, snapshot, fh, NarrowestPackageHandle)
+	_, pgh, err := getParsedFile(ctx, snapshot, fh, NarrowestPackageHandle)
 	if err != nil {
 		return nil, nil, errors.Errorf("getting file for AllImportsFixes: %v", err)
 	}
-	if hasListErrors(pkg) {
-		return nil, nil, errors.Errorf("%s has list errors, not running goimports", fh.Identity().URI)
-	}
 	err = snapshot.View().RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
 		allFixEdits, editsPerFix, err = computeImportEdits(ctx, snapshot.View(), pgh, opts)
 		return err
@@ -108,7 +105,7 @@
 	if err != nil {
 		return nil, nil, err
 	}
-	origAST, origMapper, _, err := ph.Parse(ctx)
+	origAST, _, origMapper, _, err := ph.Parse(ctx)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -144,7 +141,7 @@
 	if err != nil {
 		return nil, err
 	}
-	origAST, origMapper, _, err := ph.Parse(ctx)
+	origAST, _, origMapper, _, err := ph.Parse(ctx)
 	if err != nil {
 		return nil, err
 	}
@@ -297,15 +294,6 @@
 	return src[0:fset.Position(end).Offset], true
 }
 
-func hasListErrors(pkg Package) bool {
-	for _, err := range pkg.GetErrors() {
-		if err.Kind == ListError {
-			return true
-		}
-	}
-	return false
-}
-
 func computeTextEdits(ctx context.Context, view View, fh FileHandle, m *protocol.ColumnMapper, formatted string) ([]protocol.TextEdit, error) {
 	ctx, done := trace.StartSpan(ctx, "source.computeTextEdits")
 	defer done()
diff --git a/internal/lsp/source/highlight.go b/internal/lsp/source/highlight.go
index 07a2ac6..6b78fa4 100644
--- a/internal/lsp/source/highlight.go
+++ b/internal/lsp/source/highlight.go
@@ -27,7 +27,7 @@
 	if err != nil {
 		return nil, fmt.Errorf("getting file for Highlight: %v", err)
 	}
-	file, m, _, err := pgh.Parse(ctx)
+	file, _, m, _, err := pgh.Parse(ctx)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go
index 43bcd1b..2135506 100644
--- a/internal/lsp/source/hover.go
+++ b/internal/lsp/source/hover.go
@@ -44,6 +44,32 @@
 	comment *ast.CommentGroup
 }
 
+func Hover(ctx context.Context, snapshot Snapshot, fh FileHandle, position protocol.Position) (*protocol.Hover, error) {
+	ident, err := Identifier(ctx, snapshot, fh, position)
+	if err != nil {
+		return nil, nil
+	}
+	h, err := ident.Hover(ctx)
+	if err != nil {
+		return nil, err
+	}
+	rng, err := ident.Range()
+	if err != nil {
+		return nil, err
+	}
+	hover, err := FormatHover(h, snapshot.View().Options())
+	if err != nil {
+		return nil, err
+	}
+	return &protocol.Hover{
+		Contents: protocol.MarkupContent{
+			Kind:  snapshot.View().Options().PreferredContentFormat,
+			Value: hover,
+		},
+		Range: rng,
+	}, nil
+}
+
 func (i *IdentifierInfo) Hover(ctx context.Context) (*HoverInformation, error) {
 	ctx, done := trace.StartSpan(ctx, "source.Hover")
 	defer done()
@@ -81,10 +107,22 @@
 	}
 	switch obj := obj.(type) {
 	case *types.PkgName:
-		return obj.Imported().Path(), obj.Name()
+		path := obj.Imported().Path()
+		if mod, version, ok := moduleAtVersion(path, i); ok {
+			path = strings.Replace(path, mod, mod+"@"+version, 1)
+		}
+		return path, obj.Name()
 	case *types.Builtin:
 		return fmt.Sprintf("builtin#%s", obj.Name()), obj.Name()
 	}
+	// Check if the identifier is test-only (and is therefore not part of a
+	// package's API). This is true if the request originated in a test package,
+	// and if the declaration is also found in the same test package.
+	if i.pkg != nil && obj.Pkg() != nil && i.pkg.ForTest() != "" {
+		if _, pkg, _ := FindFileInPackage(i.pkg, i.Declaration.URI()); i.pkg == pkg {
+			return "", ""
+		}
+	}
 	// Don't return links for other unexported types.
 	if !obj.Exported() {
 		return "", ""
@@ -120,13 +158,35 @@
 			}
 		}
 	}
+	path := obj.Pkg().Path()
+	if mod, version, ok := moduleAtVersion(path, i); ok {
+		path = strings.Replace(path, mod, mod+"@"+version, 1)
+	}
 	if rTypeName != "" {
-		link := fmt.Sprintf("%s#%s.%s", obj.Pkg().Path(), rTypeName, obj.Name())
+		link := fmt.Sprintf("%s#%s.%s", path, rTypeName, obj.Name())
 		symbol := fmt.Sprintf("(%s.%s).%s", obj.Pkg().Name(), rTypeName, obj.Name())
 		return link, symbol
 	}
 	// For most cases, the link is "package/path#symbol".
-	return fmt.Sprintf("%s#%s", obj.Pkg().Path(), obj.Name()), fmt.Sprintf("%s.%s", obj.Pkg().Name(), obj.Name())
+	return fmt.Sprintf("%s#%s", path, obj.Name()), fmt.Sprintf("%s.%s", obj.Pkg().Name(), obj.Name())
+}
+
+func moduleAtVersion(path string, i *IdentifierInfo) (string, string, bool) {
+	if strings.ToLower(i.Snapshot.View().Options().LinkTarget) != "pkg.go.dev" {
+		return "", "", false
+	}
+	impPkg, err := i.pkg.GetImport(path)
+	if err != nil {
+		return "", "", false
+	}
+	if impPkg.Module() == nil {
+		return "", "", false
+	}
+	version, modpath := impPkg.Module().Version, impPkg.Module().Path
+	if modpath == "" || version == "" {
+		return "", "", false
+	}
+	return modpath, version, true
 }
 
 // objectString is a wrapper around the types.ObjectString function.
diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go
index 2694304..afd0b3f 100644
--- a/internal/lsp/source/identifier.go
+++ b/internal/lsp/source/identifier.go
@@ -48,15 +48,15 @@
 
 // Identifier returns identifier information for a position
 // in a file, accounting for a potentially incomplete selector.
-func Identifier(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position, selectPackage PackagePolicy) (*IdentifierInfo, error) {
+func Identifier(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) (*IdentifierInfo, error) {
 	ctx, done := trace.StartSpan(ctx, "source.Identifier")
 	defer done()
 
-	pkg, pgh, err := getParsedFile(ctx, snapshot, fh, selectPackage)
+	pkg, pgh, err := getParsedFile(ctx, snapshot, fh, NarrowestPackageHandle)
 	if err != nil {
 		return nil, fmt.Errorf("getting file for Identifier: %v", err)
 	}
-	file, m, _, err := pgh.Cached()
+	file, _, m, _, err := pgh.Cached()
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/lsp/source/implementation.go b/internal/lsp/source/implementation.go
index b2db818..1eceb8a 100644
--- a/internal/lsp/source/implementation.go
+++ b/internal/lsp/source/implementation.go
@@ -39,7 +39,7 @@
 			return nil, err
 		}
 		locations = append(locations, protocol.Location{
-			URI:   protocol.NewURI(rng.URI()),
+			URI:   protocol.URIFromSpanURI(rng.URI()),
 			Range: pr,
 		})
 	}
@@ -241,7 +241,7 @@
 		return nil, 0, err
 	}
 
-	file, m, _, err := pgh.Cached()
+	file, _, m, _, err := pgh.Cached()
 	if err != nil {
 		return nil, 0, err
 	}
@@ -299,7 +299,7 @@
 				}
 			}
 		case *ast.StarExpr:
-			// Follow star expressions to the inner identifer.
+			// Follow star expressions to the inner identifier.
 			if pos == n.Star {
 				pos = n.X.Pos()
 			}
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 61ab76a..cf0783a 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -44,60 +44,55 @@
 	errors "golang.org/x/xerrors"
 )
 
-var (
-	DefaultOptions = Options{
-		ClientOptions:       DefaultClientOptions,
-		ServerOptions:       DefaultServerOptions,
-		UserOptions:         DefaultUserOptions,
-		DebuggingOptions:    DefaultDebuggingOptions,
-		ExperimentalOptions: DefaultExperimentalOptions,
-		Hooks:               DefaultHooks,
-	}
-	DefaultClientOptions = ClientOptions{
-		InsertTextFormat:              protocol.PlainTextTextFormat,
-		PreferredContentFormat:        protocol.Markdown,
-		ConfigurationSupported:        true,
-		DynamicConfigurationSupported: true,
-		DynamicWatchedFilesSupported:  true,
-		LineFoldingOnly:               false,
-	}
-	DefaultServerOptions = ServerOptions{
-		SupportedCodeActions: map[FileKind]map[protocol.CodeActionKind]bool{
-			Go: {
-				protocol.SourceOrganizeImports: true,
-				protocol.QuickFix:              true,
-			},
-			Mod: {
-				protocol.SourceOrganizeImports: true,
-			},
-			Sum: {},
+func DefaultOptions() Options {
+	return Options{
+		ClientOptions: ClientOptions{
+			InsertTextFormat:              protocol.PlainTextTextFormat,
+			PreferredContentFormat:        protocol.Markdown,
+			ConfigurationSupported:        true,
+			DynamicConfigurationSupported: true,
+			DynamicWatchedFilesSupported:  true,
+			LineFoldingOnly:               false,
 		},
-		SupportedCommands: []string{
-			"tidy", // for go.mod files
+		ServerOptions: ServerOptions{
+			SupportedCodeActions: map[FileKind]map[protocol.CodeActionKind]bool{
+				Go: {
+					protocol.SourceOrganizeImports: true,
+					protocol.QuickFix:              true,
+				},
+				Mod: {
+					protocol.SourceOrganizeImports: true,
+				},
+				Sum: {},
+			},
+			SupportedCommands: []string{
+				"tidy",               // for go.mod files
+				"upgrade.dependency", // for go.mod dependency upgrades
+			},
+		},
+		UserOptions: UserOptions{
+			Env:                     os.Environ(),
+			HoverKind:               FullDocumentation,
+			LinkTarget:              "pkg.go.dev",
+			Matcher:                 Fuzzy,
+			DeepCompletion:          true,
+			UnimportedCompletion:    true,
+			CompletionDocumentation: true,
+		},
+		DebuggingOptions: DebuggingOptions{
+			CompletionBudget: 100 * time.Millisecond,
+		},
+		ExperimentalOptions: ExperimentalOptions{
+			TempModfile: true,
+		},
+		Hooks: Hooks{
+			ComputeEdits: myers.ComputeEdits,
+			URLRegexp:    regexp.MustCompile(`(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?`),
+			Analyzers:    defaultAnalyzers(),
+			GoDiff:       true,
 		},
 	}
-	DefaultUserOptions = UserOptions{
-		Env:                     os.Environ(),
-		HoverKind:               SynopsisDocumentation,
-		LinkTarget:              "pkg.go.dev",
-		Matcher:                 Fuzzy,
-		DeepCompletion:          true,
-		UnimportedCompletion:    true,
-		CompletionDocumentation: true,
-	}
-	DefaultHooks = Hooks{
-		ComputeEdits: myers.ComputeEdits,
-		URLRegexp:    regexp.MustCompile(`(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?`),
-		Analyzers:    defaultAnalyzers,
-		GoDiff:       true,
-	}
-	DefaultExperimentalOptions = ExperimentalOptions{
-		TempModfile: false,
-	}
-	DefaultDebuggingOptions = DebuggingOptions{
-		CompletionBudget: 100 * time.Millisecond,
-	}
-)
+}
 
 type Options struct {
 	ClientOptions
@@ -464,34 +459,36 @@
 	}
 }
 
-var defaultAnalyzers = map[string]*analysis.Analyzer{
-	// The traditional vet suite:
-	asmdecl.Analyzer.Name:      asmdecl.Analyzer,
-	assign.Analyzer.Name:       assign.Analyzer,
-	atomic.Analyzer.Name:       atomic.Analyzer,
-	atomicalign.Analyzer.Name:  atomicalign.Analyzer,
-	bools.Analyzer.Name:        bools.Analyzer,
-	buildtag.Analyzer.Name:     buildtag.Analyzer,
-	cgocall.Analyzer.Name:      cgocall.Analyzer,
-	composite.Analyzer.Name:    composite.Analyzer,
-	copylock.Analyzer.Name:     copylock.Analyzer,
-	errorsas.Analyzer.Name:     errorsas.Analyzer,
-	httpresponse.Analyzer.Name: httpresponse.Analyzer,
-	loopclosure.Analyzer.Name:  loopclosure.Analyzer,
-	lostcancel.Analyzer.Name:   lostcancel.Analyzer,
-	nilfunc.Analyzer.Name:      nilfunc.Analyzer,
-	printf.Analyzer.Name:       printf.Analyzer,
-	shift.Analyzer.Name:        shift.Analyzer,
-	stdmethods.Analyzer.Name:   stdmethods.Analyzer,
-	structtag.Analyzer.Name:    structtag.Analyzer,
-	tests.Analyzer.Name:        tests.Analyzer,
-	unmarshal.Analyzer.Name:    unmarshal.Analyzer,
-	unreachable.Analyzer.Name:  unreachable.Analyzer,
-	unsafeptr.Analyzer.Name:    unsafeptr.Analyzer,
-	unusedresult.Analyzer.Name: unusedresult.Analyzer,
+func defaultAnalyzers() map[string]*analysis.Analyzer {
+	return map[string]*analysis.Analyzer{
+		// The traditional vet suite:
+		asmdecl.Analyzer.Name:      asmdecl.Analyzer,
+		assign.Analyzer.Name:       assign.Analyzer,
+		atomic.Analyzer.Name:       atomic.Analyzer,
+		atomicalign.Analyzer.Name:  atomicalign.Analyzer,
+		bools.Analyzer.Name:        bools.Analyzer,
+		buildtag.Analyzer.Name:     buildtag.Analyzer,
+		cgocall.Analyzer.Name:      cgocall.Analyzer,
+		composite.Analyzer.Name:    composite.Analyzer,
+		copylock.Analyzer.Name:     copylock.Analyzer,
+		errorsas.Analyzer.Name:     errorsas.Analyzer,
+		httpresponse.Analyzer.Name: httpresponse.Analyzer,
+		loopclosure.Analyzer.Name:  loopclosure.Analyzer,
+		lostcancel.Analyzer.Name:   lostcancel.Analyzer,
+		nilfunc.Analyzer.Name:      nilfunc.Analyzer,
+		printf.Analyzer.Name:       printf.Analyzer,
+		shift.Analyzer.Name:        shift.Analyzer,
+		stdmethods.Analyzer.Name:   stdmethods.Analyzer,
+		structtag.Analyzer.Name:    structtag.Analyzer,
+		tests.Analyzer.Name:        tests.Analyzer,
+		unmarshal.Analyzer.Name:    unmarshal.Analyzer,
+		unreachable.Analyzer.Name:  unreachable.Analyzer,
+		unsafeptr.Analyzer.Name:    unsafeptr.Analyzer,
+		unusedresult.Analyzer.Name: unusedresult.Analyzer,
 
-	// Non-vet analyzers
-	deepequalerrors.Analyzer.Name:  deepequalerrors.Analyzer,
-	sortslice.Analyzer.Name:        sortslice.Analyzer,
-	testinggoroutine.Analyzer.Name: testinggoroutine.Analyzer,
+		// Non-vet analyzers
+		deepequalerrors.Analyzer.Name:  deepequalerrors.Analyzer,
+		sortslice.Analyzer.Name:        sortslice.Analyzer,
+		testinggoroutine.Analyzer.Name: testinggoroutine.Analyzer,
+	}
 }
diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go
index 5e32188..05cad6a 100644
--- a/internal/lsp/source/signature_help.go
+++ b/internal/lsp/source/signature_help.go
@@ -18,41 +18,31 @@
 	errors "golang.org/x/xerrors"
 )
 
-type SignatureInformation struct {
-	Label, Documentation string
-	Parameters           []ParameterInformation
-	ActiveParameter      int
-}
-
-type ParameterInformation struct {
-	Label string
-}
-
-func SignatureHelp(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) (*SignatureInformation, error) {
+func SignatureHelp(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) (*protocol.SignatureInformation, int, error) {
 	ctx, done := trace.StartSpan(ctx, "source.SignatureHelp")
 	defer done()
 
 	pkg, pgh, err := getParsedFile(ctx, snapshot, fh, NarrowestPackageHandle)
 	if err != nil {
-		return nil, fmt.Errorf("getting file for SignatureHelp: %v", err)
+		return nil, 0, fmt.Errorf("getting file for SignatureHelp: %v", err)
 	}
-	file, m, _, err := pgh.Cached()
+	file, _, m, _, err := pgh.Cached()
 	if err != nil {
-		return nil, err
+		return nil, 0, err
 	}
 	spn, err := m.PointSpan(pos)
 	if err != nil {
-		return nil, err
+		return nil, 0, err
 	}
 	rng, err := spn.Range(m.Converter)
 	if err != nil {
-		return nil, err
+		return nil, 0, err
 	}
 	// Find a call expression surrounding the query position.
 	var callExpr *ast.CallExpr
 	path, _ := astutil.PathEnclosingInterval(file, rng.Start, rng.Start)
 	if path == nil {
-		return nil, errors.Errorf("cannot find node enclosing position")
+		return nil, 0, errors.Errorf("cannot find node enclosing position")
 	}
 FindCall:
 	for _, node := range path {
@@ -66,11 +56,11 @@
 			// The user is within an anonymous function,
 			// which may be the parameter to the *ast.CallExpr.
 			// Don't show signature help in this case.
-			return nil, errors.Errorf("no signature help within a function declaration")
+			return nil, 0, errors.Errorf("no signature help within a function declaration")
 		}
 	}
 	if callExpr == nil || callExpr.Fun == nil {
-		return nil, errors.Errorf("cannot find an enclosing function")
+		return nil, 0, errors.Errorf("cannot find an enclosing function")
 	}
 
 	// Get the object representing the function, if available.
@@ -92,12 +82,12 @@
 	// Get the type information for the function being called.
 	sigType := pkg.GetTypesInfo().TypeOf(callExpr.Fun)
 	if sigType == nil {
-		return nil, errors.Errorf("cannot get type for Fun %[1]T (%[1]v)", callExpr.Fun)
+		return nil, 0, errors.Errorf("cannot get type for Fun %[1]T (%[1]v)", callExpr.Fun)
 	}
 
 	sig, _ := sigType.Underlying().(*types.Signature)
 	if sig == nil {
-		return nil, errors.Errorf("cannot find signature for Fun %[1]T (%[1]v)", callExpr.Fun)
+		return nil, 0, errors.Errorf("cannot find signature for Fun %[1]T (%[1]v)", callExpr.Fun)
 	}
 
 	qf := qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo())
@@ -112,11 +102,11 @@
 	if obj != nil {
 		node, err := objToNode(snapshot.View(), pkg, obj)
 		if err != nil {
-			return nil, err
+			return nil, 0, err
 		}
 		rng, err := objToMappedRange(snapshot.View(), pkg, obj)
 		if err != nil {
-			return nil, err
+			return nil, 0, err
 		}
 		decl := &Declaration{
 			obj:         obj,
@@ -125,24 +115,24 @@
 		}
 		d, err := decl.hover(ctx)
 		if err != nil {
-			return nil, err
+			return nil, 0, err
 		}
 		name = obj.Name()
 		comment = d.comment
 	} else {
 		name = "func"
 	}
-	return signatureInformation(name, comment, params, results, writeResultParens, activeParam), nil
+	return signatureInformation(name, comment, params, results, writeResultParens), activeParam, nil
 }
 
-func builtinSignature(ctx context.Context, v View, callExpr *ast.CallExpr, name string, pos token.Pos) (*SignatureInformation, error) {
+func builtinSignature(ctx context.Context, v View, callExpr *ast.CallExpr, name string, pos token.Pos) (*protocol.SignatureInformation, int, error) {
 	astObj, err := v.LookupBuiltin(ctx, name)
 	if err != nil {
-		return nil, err
+		return nil, 0, err
 	}
 	decl, ok := astObj.Decl.(*ast.FuncDecl)
 	if !ok {
-		return nil, errors.Errorf("no function declaration for builtin: %s", name)
+		return nil, 0, errors.Errorf("no function declaration for builtin: %s", name)
 	}
 	params, _ := formatFieldList(ctx, v, decl.Type.Params)
 	results, writeResultParens := formatFieldList(ctx, v, decl.Type.Results)
@@ -159,24 +149,23 @@
 		}
 	}
 	activeParam := activeParameter(callExpr.Args, numParams, variadic, pos)
-	return signatureInformation(name, nil, params, results, writeResultParens, activeParam), nil
+	return signatureInformation(name, nil, params, results, writeResultParens), activeParam, nil
 }
 
-func signatureInformation(name string, comment *ast.CommentGroup, params, results []string, writeResultParens bool, activeParam int) *SignatureInformation {
-	paramInfo := make([]ParameterInformation, 0, len(params))
+func signatureInformation(name string, comment *ast.CommentGroup, params, results []string, writeResultParens bool) *protocol.SignatureInformation {
+	paramInfo := make([]protocol.ParameterInformation, 0, len(params))
 	for _, p := range params {
-		paramInfo = append(paramInfo, ParameterInformation{Label: p})
+		paramInfo = append(paramInfo, protocol.ParameterInformation{Label: p})
 	}
 	label := name + formatFunction(params, results, writeResultParens)
 	var c string
 	if comment != nil {
 		c = doc.Synopsis(comment.Text())
 	}
-	return &SignatureInformation{
-		Label:           label,
-		Documentation:   c,
-		Parameters:      paramInfo,
-		ActiveParameter: activeParam,
+	return &protocol.SignatureInformation{
+		Label:         label,
+		Documentation: c,
+		Parameters:    paramInfo,
 	}
 }
 
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 2c766b8..d2721ec 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -5,7 +5,6 @@
 package source_test
 
 import (
-	"bytes"
 	"context"
 	"fmt"
 	"os"
@@ -45,39 +44,44 @@
 func testSource(t *testing.T, exporter packagestest.Exporter) {
 	ctx := tests.Context(t)
 	data := tests.Load(t, exporter, "../testdata")
-	defer data.Exported.Cleanup()
+	for _, datum := range data {
+		defer datum.Exported.Cleanup()
 
-	cache := cache.New(nil)
-	session := cache.NewSession()
-	options := tests.DefaultOptions()
-	options.Env = data.Config.Env
-	view, _, err := session.NewView(ctx, "source_test", span.FileURI(data.Config.Dir), options)
-	if err != nil {
-		t.Fatal(err)
-	}
-	r := &runner{
-		view: view,
-		data: data,
-		ctx:  ctx,
-	}
-	var modifications []source.FileModification
-	for filename, content := range data.Config.Overlay {
-		kind := source.DetectLanguage("", filename)
-		if kind != source.Go {
-			continue
+		cache := cache.New(nil, nil)
+		session := cache.NewSession()
+		options := tests.DefaultOptions()
+		options.Env = datum.Config.Env
+		view, _, err := session.NewView(ctx, "source_test", span.URIFromPath(datum.Config.Dir), options)
+		if err != nil {
+			t.Fatal(err)
 		}
-		modifications = append(modifications, source.FileModification{
-			URI:        span.FileURI(filename),
-			Action:     source.Open,
-			Version:    -1,
-			Text:       content,
-			LanguageID: "go",
+		r := &runner{
+			view: view,
+			data: datum,
+			ctx:  ctx,
+		}
+		var modifications []source.FileModification
+		for filename, content := range datum.Config.Overlay {
+			kind := source.DetectLanguage("", filename)
+			if kind != source.Go {
+				continue
+			}
+			modifications = append(modifications, source.FileModification{
+				URI:        span.URIFromPath(filename),
+				Action:     source.Open,
+				Version:    -1,
+				Text:       content,
+				LanguageID: "go",
+			})
+		}
+		if _, err := session.DidModifyFiles(ctx, modifications); err != nil {
+			t.Fatal(err)
+		}
+		t.Run(datum.Folder, func(t *testing.T) {
+			t.Helper()
+			tests.Run(t, r, datum)
 		})
 	}
-	if _, err := session.DidModifyFiles(ctx, modifications); err != nil {
-		t.Fatal(err)
-	}
-	tests.Run(t, r, data)
 }
 
 func (r *runner) Diagnostics(t *testing.T, uri span.URI, want []source.Diagnostic) {
@@ -115,9 +119,7 @@
 			opts.InsertTextFormat = protocol.SnippetTextFormat
 		}
 	})
-	if !strings.Contains(string(src.URI()), "builtins") {
-		got = tests.FilterBuiltins(got)
-	}
+	got = tests.FilterBuiltins(src, got)
 	if diff := tests.DiffCompletionItems(want, got); diff != "" {
 		t.Errorf("%s: %s", src, diff)
 	}
@@ -144,9 +146,7 @@
 		want = append(want, tests.ToProtocolCompletionItem(*items[pos]))
 	}
 	_, got := r.callCompletion(t, src, func(opts *source.Options) {})
-	if !strings.Contains(string(src.URI()), "builtins") {
-		got = tests.FilterBuiltins(got)
-	}
+	got = tests.FilterBuiltins(src, got)
 	if diff := tests.CheckCompletionOrder(want, got, false); diff != "" {
 		t.Errorf("%s: %s", src, diff)
 	}
@@ -162,9 +162,7 @@
 		opts.Matcher = source.CaseInsensitive
 		opts.UnimportedCompletion = false
 	})
-	if !strings.Contains(string(src.URI()), "builtins") {
-		list = tests.FilterBuiltins(list)
-	}
+	list = tests.FilterBuiltins(src, list)
 	fuzzyMatcher := fuzzy.NewMatcher(prefix)
 	var got []protocol.CompletionItem
 	for _, item := range list {
@@ -188,9 +186,7 @@
 		opts.Matcher = source.Fuzzy
 		opts.UnimportedCompletion = false
 	})
-	if !strings.Contains(string(src.URI()), "builtins") {
-		got = tests.FilterBuiltins(got)
-	}
+	got = tests.FilterBuiltins(src, got)
 	if msg := tests.DiffCompletionItems(want, got); msg != "" {
 		t.Errorf("%s: %s", src, msg)
 	}
@@ -205,9 +201,7 @@
 		opts.Matcher = source.CaseSensitive
 		opts.UnimportedCompletion = false
 	})
-	if !strings.Contains(string(src.URI()), "builtins") {
-		list = tests.FilterBuiltins(list)
-	}
+	list = tests.FilterBuiltins(src, list)
 	if diff := tests.DiffCompletionItems(want, list); diff != "" {
 		t.Errorf("%s: %s", src, diff)
 	}
@@ -484,7 +478,7 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	ident, err := source.Identifier(r.ctx, r.view.Snapshot(), fh, srcRng.Start, source.WidestPackageHandle)
+	ident, err := source.Identifier(r.ctx, r.view.Snapshot(), fh, srcRng.Start)
 	if err != nil {
 		t.Fatalf("failed for %v: %v", d.Src, err)
 	}
@@ -553,7 +547,7 @@
 	}
 	var results []span.Span
 	for i := range locs {
-		locURI := span.NewURI(locs[i].URI)
+		locURI := locs[i].URI.SpanURI()
 		lm, err := r.data.Mapper(locURI)
 		if err != nil {
 			t.Fatal(err)
@@ -768,7 +762,7 @@
 		}
 		return
 	}
-	if want.Text == "" && item != nil {
+	if want.Text == "" {
 		t.Errorf("prepare rename failed for %v: expected nil, got %v", src, item)
 		return
 	}
@@ -798,55 +792,73 @@
 		t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols))
 		return
 	}
-	if diff := r.diffSymbols(t, uri, expectedSymbols, symbols); diff != "" {
+	if diff := tests.DiffSymbols(t, uri, expectedSymbols, symbols); diff != "" {
 		t.Error(diff)
 	}
 }
 
-func (r *runner) diffSymbols(t *testing.T, uri span.URI, want, got []protocol.DocumentSymbol) string {
-	sort.Slice(want, func(i, j int) bool { return want[i].Name < want[j].Name })
-	sort.Slice(got, func(i, j int) bool { return got[i].Name < got[j].Name })
-	if len(got) != len(want) {
-		return summarizeSymbols(t, -1, want, got, "different lengths got %v want %v", len(got), len(want))
+func (r *runner) WorkspaceSymbols(t *testing.T, query string, expectedSymbols []protocol.SymbolInformation, dirs map[string]struct{}) {
+	got := r.callWorkspaceSymbols(t, query, func(opts *source.Options) {
+		opts.Matcher = source.CaseInsensitive
+	})
+	got = tests.FilterWorkspaceSymbols(got, dirs)
+	if len(got) != len(expectedSymbols) {
+		t.Errorf("want %d symbols, got %d", len(expectedSymbols), len(got))
+		return
 	}
-	for i, w := range want {
-		g := got[i]
-		if w.Name != g.Name {
-			return summarizeSymbols(t, i, want, got, "incorrect name got %v want %v", g.Name, w.Name)
-		}
-		if w.Kind != g.Kind {
-			return summarizeSymbols(t, i, want, got, "incorrect kind got %v want %v", g.Kind, w.Kind)
-		}
-		if protocol.CompareRange(w.SelectionRange, g.SelectionRange) != 0 {
-			return summarizeSymbols(t, i, want, got, "incorrect span got %v want %v", g.SelectionRange, w.SelectionRange)
-		}
-		if msg := r.diffSymbols(t, uri, w.Children, g.Children); msg != "" {
-			return fmt.Sprintf("children of %s: %s", w.Name, msg)
-		}
+	if diff := tests.DiffWorkspaceSymbols(expectedSymbols, got); diff != "" {
+		t.Error(diff)
 	}
-	return ""
 }
 
-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 {
-		fmt.Fprintf(msg, " at %d", i)
+func (r *runner) FuzzyWorkspaceSymbols(t *testing.T, query string, expectedSymbols []protocol.SymbolInformation, dirs map[string]struct{}) {
+	got := r.callWorkspaceSymbols(t, query, func(opts *source.Options) {
+		opts.Matcher = source.Fuzzy
+	})
+	got = tests.FilterWorkspaceSymbols(got, dirs)
+	if len(got) != len(expectedSymbols) {
+		t.Errorf("want %d symbols, got %d", len(expectedSymbols), len(got))
+		return
 	}
-	fmt.Fprint(msg, " because of ")
-	fmt.Fprintf(msg, reason, args...)
-	fmt.Fprint(msg, ":\nexpected:\n")
-	for _, s := range want {
-		fmt.Fprintf(msg, "  %v %v %v\n", s.Name, s.Kind, s.SelectionRange)
+	if diff := tests.DiffWorkspaceSymbols(expectedSymbols, got); diff != "" {
+		t.Error(diff)
 	}
-	fmt.Fprintf(msg, "got:\n")
-	for _, s := range got {
-		fmt.Fprintf(msg, "  %v %v %v\n", s.Name, s.Kind, s.SelectionRange)
-	}
-	return msg.String()
 }
 
-func (r *runner) SignatureHelp(t *testing.T, spn span.Span, expectedSignature *source.SignatureInformation) {
+func (r *runner) CaseSensitiveWorkspaceSymbols(t *testing.T, query string, expectedSymbols []protocol.SymbolInformation, dirs map[string]struct{}) {
+	got := r.callWorkspaceSymbols(t, query, func(opts *source.Options) {
+		opts.Matcher = source.CaseSensitive
+	})
+	got = tests.FilterWorkspaceSymbols(got, dirs)
+	if len(got) != len(expectedSymbols) {
+		t.Errorf("want %d symbols, got %d", len(expectedSymbols), len(got))
+		return
+	}
+	if diff := tests.DiffWorkspaceSymbols(expectedSymbols, got); diff != "" {
+		t.Error(diff)
+	}
+}
+
+func (r *runner) callWorkspaceSymbols(t *testing.T, query string, options func(*source.Options)) []protocol.SymbolInformation {
+	t.Helper()
+
+	original := r.view.Options()
+	modified := original
+	options(&modified)
+	view, err := r.view.SetOptions(r.ctx, modified)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer r.view.SetOptions(r.ctx, original)
+
+	got, err := source.WorkspaceSymbols(r.ctx, []source.View{view}, query)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return got
+}
+
+func (r *runner) SignatureHelp(t *testing.T, spn span.Span, want *protocol.SignatureHelp) {
 	_, rng, err := spanToRange(r.data, spn)
 	if err != nil {
 		t.Fatal(err)
@@ -855,46 +867,34 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	gotSignature, err := source.SignatureHelp(r.ctx, r.view.Snapshot(), fh, rng.Start)
+	gotSignature, gotActiveParameter, err := source.SignatureHelp(r.ctx, r.view.Snapshot(), fh, rng.Start)
 	if err != nil {
 		// Only fail if we got an error we did not expect.
-		if expectedSignature != nil {
+		if want != nil {
 			t.Fatalf("failed for %v: %v", spn, err)
 		}
-	}
-	if expectedSignature == nil {
-		if gotSignature != nil {
-			t.Errorf("expected no signature, got %v", gotSignature)
-		}
 		return
 	}
-	if diff := diffSignatures(spn, expectedSignature, gotSignature); diff != "" {
+	if gotSignature == nil {
+		if want != nil {
+			t.Fatalf("got nil signature, but expected %v", want)
+		}
+		return
+	}
+	got := &protocol.SignatureHelp{
+		Signatures:      []protocol.SignatureInformation{*gotSignature},
+		ActiveParameter: float64(gotActiveParameter),
+	}
+	if diff := tests.DiffSignatures(spn, got, want); diff != "" {
 		t.Error(diff)
 	}
 }
 
-func diffSignatures(spn span.Span, want *source.SignatureInformation, got *source.SignatureInformation) string {
-	decorate := func(f string, args ...interface{}) string {
-		return fmt.Sprintf("Invalid signature at %s: %s", spn, fmt.Sprintf(f, args...))
-	}
-	if want.ActiveParameter != got.ActiveParameter {
-		return decorate("wanted active parameter of %d, got %d", want.ActiveParameter, got.ActiveParameter)
-	}
-	if want.Label != got.Label {
-		return decorate("wanted label %q, got %q", want.Label, got.Label)
-	}
-	var paramParts []string
-	for _, p := range got.Parameters {
-		paramParts = append(paramParts, p.Label)
-	}
-	paramsStr := strings.Join(paramParts, ", ")
-	if !strings.Contains(got.Label, paramsStr) {
-		return decorate("expected signature %q to contain params %q", got.Label, paramsStr)
-	}
-	return ""
+func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
+	// This is a pure LSP feature, no source level functionality to be tested.
 }
 
-func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) {
+func (r *runner) CodeLens(t *testing.T, spn span.Span, want []protocol.CodeLens) {
 	// This is a pure LSP feature, no source level functionality to be tested.
 }
 
diff --git a/internal/lsp/source/symbols.go b/internal/lsp/source/symbols.go
index 989492c..12afcb3 100644
--- a/internal/lsp/source/symbols.go
+++ b/internal/lsp/source/symbols.go
@@ -22,7 +22,7 @@
 	if err != nil {
 		return nil, fmt.Errorf("getting file for DocumentSymbols: %v", err)
 	}
-	file, _, _, err := pgh.Cached()
+	file, _, _, _, err := pgh.Cached()
 	if err != nil {
 		return nil, err
 	}
@@ -129,40 +129,12 @@
 	return s, nil
 }
 
-func setKind(s *protocol.DocumentSymbol, typ types.Type, q types.Qualifier) {
-	switch typ := typ.Underlying().(type) {
-	case *types.Interface:
-		s.Kind = protocol.Interface
-	case *types.Struct:
-		s.Kind = protocol.Struct
-	case *types.Signature:
-		s.Kind = protocol.Function
-		if typ.Recv() != nil {
-			s.Kind = protocol.Method
-		}
-	case *types.Named:
-		setKind(s, typ.Underlying(), q)
-	case *types.Basic:
-		i := typ.Info()
-		switch {
-		case i&types.IsNumeric != 0:
-			s.Kind = protocol.Number
-		case i&types.IsBoolean != 0:
-			s.Kind = protocol.Boolean
-		case i&types.IsString != 0:
-			s.Kind = protocol.String
-		}
-	default:
-		s.Kind = protocol.Variable
-	}
-}
-
 func typeSymbol(ctx context.Context, view View, pkg Package, info *types.Info, spec *ast.TypeSpec, obj types.Object, q types.Qualifier) (protocol.DocumentSymbol, error) {
 	s := protocol.DocumentSymbol{
 		Name: obj.Name(),
 	}
 	s.Detail, _ = formatType(obj.Type(), q)
-	setKind(&s, obj.Type(), q)
+	s.Kind = typeToKind(obj.Type())
 
 	var err error
 	s.Range, err = nodeToProtocolRange(view, pkg, spec)
@@ -236,7 +208,7 @@
 			child := protocol.DocumentSymbol{
 				Name: types.TypeString(embedded, q),
 			}
-			setKind(&child, embedded, q)
+			child.Kind = typeToKind(embedded)
 			var spanNode, selectionNode ast.Node
 		Embeddeds:
 			for _, f := range ai.Methods.List {
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
index 34e6317..86b017f 100644
--- a/internal/lsp/source/util.go
+++ b/internal/lsp/source/util.go
@@ -147,7 +147,7 @@
 		return false
 	}
 	ph := snapshot.View().Session().Cache().ParseGoHandle(fh, ParseHeader)
-	parsed, _, _, err := ph.Parse(ctx)
+	parsed, _, _, _, err := ph.Parse(ctx)
 	if err != nil {
 		return false
 	}
@@ -201,7 +201,7 @@
 
 func posToMappedRange(v View, pkg Package, pos, end token.Pos) (mappedRange, error) {
 	logicalFilename := v.Session().Cache().FileSet().File(pos).Position(pos).Filename
-	m, err := findMapperInPackage(v, pkg, span.FileURI(logicalFilename))
+	m, err := findMapperInPackage(v, pkg, span.URIFromPath(logicalFilename))
 	if err != nil {
 		return mappedRange{}, err
 	}
@@ -415,6 +415,23 @@
 	return false
 }
 
+func isPkgName(obj types.Object) bool {
+	_, ok := obj.(*types.PkgName)
+	return ok
+}
+
+func isASTFile(n ast.Node) bool {
+	_, ok := n.(*ast.File)
+	return ok
+}
+
+func deslice(T types.Type) types.Type {
+	if slice, ok := T.Underlying().(*types.Slice); ok {
+		return slice.Elem()
+	}
+	return nil
+}
+
 // isSelector returns the enclosing *ast.SelectorExpr when pos is in the
 // selector.
 func enclosingSelector(path []ast.Node, pos token.Pos) *ast.SelectorExpr {
@@ -626,7 +643,7 @@
 	if tok == nil {
 		return nil, nil, errors.Errorf("no file for pos in package %s", searchpkg.ID())
 	}
-	uri := span.FileURI(tok.Name())
+	uri := span.URIFromPath(tok.Name())
 
 	var (
 		ph  ParseGoHandle
@@ -637,12 +654,12 @@
 	if v.Ignore(uri) {
 		ph, err = findIgnoredFile(v, uri)
 	} else {
-		ph, pkg, err = findFileInPackage(searchpkg, uri)
+		ph, pkg, err = FindFileInPackage(searchpkg, uri)
 	}
 	if err != nil {
 		return nil, nil, err
 	}
-	file, _, _, err := ph.Cached()
+	file, _, _, _, err := ph.Cached()
 	if err != nil {
 		return nil, nil, err
 	}
@@ -661,12 +678,12 @@
 	if v.Ignore(uri) {
 		ph, err = findIgnoredFile(v, uri)
 	} else {
-		ph, _, err = findFileInPackage(searchpkg, uri)
+		ph, _, err = FindFileInPackage(searchpkg, uri)
 	}
 	if err != nil {
 		return nil, err
 	}
-	_, m, _, err := ph.Cached()
+	_, _, m, _, err := ph.Cached()
 	if err != nil {
 		return nil, err
 	}
@@ -681,7 +698,8 @@
 	return v.Session().Cache().ParseGoHandle(fh, ParseFull), nil
 }
 
-func findFileInPackage(pkg Package, uri span.URI) (ParseGoHandle, Package, error) {
+// FindFileInPackage finds uri in pkg or its dependencies.
+func FindFileInPackage(pkg Package, uri span.URI) (ParseGoHandle, Package, error) {
 	queue := []Package{pkg}
 	seen := make(map[string]bool)
 
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 053ae79..0c0f5f5 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -16,6 +16,7 @@
 	"golang.org/x/tools/go/packages"
 	"golang.org/x/tools/internal/imports"
 	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/packagesinternal"
 	"golang.org/x/tools/internal/span"
 )
 
@@ -26,10 +27,19 @@
 	// View returns the View associated with this snapshot.
 	View() View
 
+	// Config returns the configuration for the view.
+	Config(ctx context.Context) *packages.Config
+
 	// GetFile returns the file object for a given URI, initializing it
 	// if it is not already part of the view.
 	GetFile(uri span.URI) (FileHandle, error)
 
+	// IsOpen returns whether the editor currently has a file open.
+	IsOpen(uri span.URI) bool
+
+	// IsSaved returns whether the contents are saved on disk or not.
+	IsSaved(uri span.URI) bool
+
 	// Analyze runs the analyses for the given package at this snapshot.
 	Analyze(ctx context.Context, id string, analyzers []*analysis.Analyzer) ([]*Error, error)
 
@@ -39,7 +49,11 @@
 
 	// ModTidyHandle returns a ModTidyHandle for the given go.mod file handle.
 	// This function can have no data or error if there is no modfile detected.
-	ModTidyHandle(ctx context.Context, fh FileHandle) ModTidyHandle
+	ModTidyHandle(ctx context.Context, fh FileHandle) (ModTidyHandle, error)
+
+	// ModHandle returns a ModHandle for the passed in go.mod file handle.
+	// This function can have no data if there is no modfile detected.
+	ModHandle(ctx context.Context, fh FileHandle) ModHandle
 
 	// PackageHandles returns the PackageHandles for the packages that this file
 	// belongs to.
@@ -54,6 +68,8 @@
 	CachedImportPaths(ctx context.Context) (map[string]Package, error)
 
 	// KnownPackages returns all the packages loaded in this snapshot.
+	// Workspace packages may be parsed in ParseFull mode, whereas transitive
+	// dependencies will be in ParseExported mode.
 	KnownPackages(ctx context.Context) ([]PackageHandle, error)
 
 	// WorkspacePackages returns the PackageHandles for the snapshot's
@@ -109,10 +125,7 @@
 	// Ignore returns true if this file should be ignored by this view.
 	Ignore(span.URI) bool
 
-	// Config returns the configuration for the view.
-	Config(ctx context.Context) *packages.Config
-
-	// RunProcessEnvFunc runs fn with the process env for this view.
+	// RunProcessEnvFunc runs fn with the process env for this snapshot's view.
 	// Note: the process env contains cached module and filesystem state.
 	RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error) error
 
@@ -164,10 +177,6 @@
 	// content from the underlying cache if no overlay is present.
 	FileSystem
 
-	// IsOpen returns whether the editor currently has a file open,
-	// and if its contents are saved on disk or not.
-	IsOpen(uri span.URI) bool
-
 	// DidModifyFile reports a file modification to the session.
 	// It returns the resulting snapshots, a guaranteed one per view.
 	DidModifyFiles(ctx context.Context, changes []FileModification) ([]Snapshot, error)
@@ -220,9 +229,6 @@
 	// A FileSystem that reads file contents from external storage.
 	FileSystem
 
-	// NewSession creates a new Session manager and returns it.
-	NewSession() Session
-
 	// FileSet returns the shared fileset used by all files in the system.
 	FileSet() *token.FileSet
 
@@ -246,13 +252,34 @@
 
 	// Parse returns the parsed AST for the file.
 	// If the file is not available, returns nil and an error.
-	Parse(ctx context.Context) (*ast.File, *protocol.ColumnMapper, error, error)
+	Parse(ctx context.Context) (file *ast.File, src []byte, m *protocol.ColumnMapper, parseErr error, err error)
 
 	// Cached returns the AST for this handle, if it has already been stored.
-	Cached() (*ast.File, *protocol.ColumnMapper, error, error)
+	Cached() (file *ast.File, src []byte, m *protocol.ColumnMapper, parseErr error, err error)
 }
 
-// ModTidyHandle represents a handle to the modfile for a go.mod.
+// ModHandle represents a handle to the modfile for a go.mod.
+type ModHandle interface {
+	// File returns a file handle for which to get the modfile.
+	File() FileHandle
+
+	// Parse returns the parsed modfile and a mapper for the go.mod file.
+	// If the file is not available, returns nil and an error.
+	Parse(ctx context.Context) (*modfile.File, *protocol.ColumnMapper, error)
+
+	// Upgrades returns the parsed modfile, a mapper, and any dependency upgrades
+	// for the go.mod file. Note that this will only work if the go.mod is the view's go.mod.
+	// If the file is not available, returns nil and an error.
+	Upgrades(ctx context.Context) (*modfile.File, *protocol.ColumnMapper, map[string]string, error)
+
+	// Why returns the parsed modfile, a mapper, and any explanations why a dependency should be
+	// in the go.mod file. Note that this will only work if the go.mod is the view's go.mod.
+	// If the file is not available, returns nil and an error.
+	Why(ctx context.Context) (*modfile.File, *protocol.ColumnMapper, map[string]string, error)
+}
+
+// ModTidyHandle represents a handle to the modfile for the view.
+// Specifically for the purpose of getting diagnostics by running "go mod tidy".
 type ModTidyHandle interface {
 	// File returns a file handle for which to get the modfile.
 	File() FileHandle
@@ -341,8 +368,10 @@
 	GetTypesInfo() *types.Info
 	GetTypesSizes() types.Sizes
 	IsIllTyped() bool
+	ForTest() string
 	GetImport(pkgPath string) (Package, error)
 	Imports() []Package
+	Module() *packagesinternal.Module
 }
 
 type Error struct {
diff --git a/internal/lsp/source/workspace_symbol.go b/internal/lsp/source/workspace_symbol.go
new file mode 100644
index 0000000..fdeef1d
--- /dev/null
+++ b/internal/lsp/source/workspace_symbol.go
@@ -0,0 +1,215 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package source
+
+import (
+	"context"
+	"go/ast"
+	"go/token"
+	"go/types"
+	"strings"
+
+	"golang.org/x/tools/internal/lsp/fuzzy"
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/telemetry/log"
+	"golang.org/x/tools/internal/telemetry/trace"
+)
+
+const maxSymbols = 100
+
+func WorkspaceSymbols(ctx context.Context, views []View, query string) ([]protocol.SymbolInformation, error) {
+	ctx, done := trace.StartSpan(ctx, "source.WorkspaceSymbols")
+	defer done()
+
+	seen := make(map[string]struct{})
+	var symbols []protocol.SymbolInformation
+outer:
+	for _, view := range views {
+		knownPkgs, err := view.Snapshot().KnownPackages(ctx)
+		if err != nil {
+			return nil, err
+		}
+		matcher := makeMatcher(view.Options().Matcher, query)
+		for _, ph := range knownPkgs {
+			pkg, err := ph.Check(ctx)
+			if err != nil {
+				return nil, err
+			}
+			if _, ok := seen[pkg.PkgPath()]; ok {
+				continue
+			}
+			seen[pkg.PkgPath()] = struct{}{}
+			for _, fh := range pkg.CompiledGoFiles() {
+				file, _, _, _, err := fh.Cached()
+				if err != nil {
+					return nil, err
+				}
+				for _, si := range findSymbol(file.Decls, pkg.GetTypesInfo(), matcher) {
+					rng, err := nodeToProtocolRange(view, pkg, si.node)
+					if err != nil {
+						log.Error(ctx, "Error getting range for node", err)
+						continue
+					}
+					symbols = append(symbols, protocol.SymbolInformation{
+						Name: si.name,
+						Kind: si.kind,
+						Location: protocol.Location{
+							URI:   protocol.URIFromSpanURI(fh.File().Identity().URI),
+							Range: rng,
+						},
+					})
+					if len(symbols) > maxSymbols {
+						break outer
+					}
+				}
+			}
+		}
+	}
+	return symbols, nil
+}
+
+type symbolInformation struct {
+	name string
+	kind protocol.SymbolKind
+	node ast.Node
+}
+
+type matcherFunc func(string) bool
+
+func makeMatcher(m Matcher, query string) matcherFunc {
+	switch m {
+	case Fuzzy:
+		fm := fuzzy.NewMatcher(query)
+		return func(s string) bool {
+			return fm.Score(s) > 0
+		}
+	case CaseSensitive:
+		return func(s string) bool {
+			return strings.Contains(s, query)
+		}
+	default:
+		q := strings.ToLower(query)
+		return func(s string) bool {
+			return strings.Contains(strings.ToLower(s), q)
+		}
+	}
+}
+
+func findSymbol(decls []ast.Decl, info *types.Info, matcher matcherFunc) []symbolInformation {
+	var result []symbolInformation
+	for _, decl := range decls {
+		switch decl := decl.(type) {
+		case *ast.FuncDecl:
+			if matcher(decl.Name.Name) {
+				kind := protocol.Function
+				if decl.Recv != nil {
+					kind = protocol.Method
+				}
+				result = append(result, symbolInformation{
+					name: decl.Name.Name,
+					kind: kind,
+					node: decl.Name,
+				})
+			}
+		case *ast.GenDecl:
+			for _, spec := range decl.Specs {
+				switch spec := spec.(type) {
+				case *ast.TypeSpec:
+					if matcher(spec.Name.Name) {
+						result = append(result, symbolInformation{
+							name: spec.Name.Name,
+							kind: typeToKind(info.TypeOf(spec.Type)),
+							node: spec.Name,
+						})
+					}
+					switch st := spec.Type.(type) {
+					case *ast.StructType:
+						for _, field := range st.Fields.List {
+							result = append(result, findFieldSymbol(field, protocol.Field, matcher)...)
+						}
+					case *ast.InterfaceType:
+						for _, field := range st.Methods.List {
+							kind := protocol.Method
+							if len(field.Names) == 0 {
+								kind = protocol.Interface
+							}
+							result = append(result, findFieldSymbol(field, kind, matcher)...)
+						}
+					}
+				case *ast.ValueSpec:
+					for _, name := range spec.Names {
+						if matcher(name.Name) {
+							kind := protocol.Variable
+							if decl.Tok == token.CONST {
+								kind = protocol.Constant
+							}
+							result = append(result, symbolInformation{
+								name: name.Name,
+								kind: kind,
+								node: name,
+							})
+						}
+					}
+				}
+			}
+		}
+	}
+	return result
+}
+
+func typeToKind(typ types.Type) protocol.SymbolKind {
+	switch typ := typ.Underlying().(type) {
+	case *types.Interface:
+		return protocol.Interface
+	case *types.Struct:
+		return protocol.Struct
+	case *types.Signature:
+		if typ.Recv() != nil {
+			return protocol.Method
+		}
+		return protocol.Function
+	case *types.Named:
+		return typeToKind(typ.Underlying())
+	case *types.Basic:
+		i := typ.Info()
+		switch {
+		case i&types.IsNumeric != 0:
+			return protocol.Number
+		case i&types.IsBoolean != 0:
+			return protocol.Boolean
+		case i&types.IsString != 0:
+			return protocol.String
+		}
+	}
+	return protocol.Variable
+}
+
+func findFieldSymbol(field *ast.Field, kind protocol.SymbolKind, matcher matcherFunc) []symbolInformation {
+	var result []symbolInformation
+
+	if len(field.Names) == 0 {
+		name := types.ExprString(field.Type)
+		if matcher(name) {
+			result = append(result, symbolInformation{
+				name: name,
+				kind: kind,
+				node: field,
+			})
+		}
+		return result
+	}
+
+	for _, name := range field.Names {
+		if matcher(name.Name) {
+			result = append(result, symbolInformation{
+				name: name.Name,
+				kind: kind,
+				node: name,
+			})
+		}
+	}
+
+	return result
+}
diff --git a/internal/lsp/symbols.go b/internal/lsp/symbols.go
index 0e167d3..b9f0b75 100644
--- a/internal/lsp/symbols.go
+++ b/internal/lsp/symbols.go
@@ -10,7 +10,6 @@
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/telemetry"
-	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/telemetry/log"
 	"golang.org/x/tools/internal/telemetry/trace"
 )
@@ -19,26 +18,13 @@
 	ctx, done := trace.StartSpan(ctx, "lsp.Server.documentSymbol")
 	defer done()
 
-	uri := span.NewURI(params.TextDocument.URI)
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
-		return nil, err
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
+		return []protocol.DocumentSymbol{}, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
+	symbols, err := source.DocumentSymbols(ctx, snapshot, fh)
 	if err != nil {
-		return nil, err
-	}
-	var symbols []protocol.DocumentSymbol
-	switch fh.Identity().Kind {
-	case source.Go:
-		symbols, err = source.DocumentSymbols(ctx, snapshot, fh)
-	case source.Mod:
-		return []protocol.DocumentSymbol{}, nil
-	}
-
-	if err != nil {
-		log.Error(ctx, "DocumentSymbols failed", err, telemetry.URI.Of(uri))
+		log.Error(ctx, "DocumentSymbols failed", err, telemetry.URI.Of(fh.Identity().URI))
 		return []protocol.DocumentSymbol{}, nil
 	}
 	return symbols, nil
diff --git a/internal/lsp/testdata/%percent/perc%ent.go b/internal/lsp/testdata/%percent/perc%ent.go
deleted file mode 100644
index f993da8..0000000
--- a/internal/lsp/testdata/%percent/perc%ent.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package percent
-
-import (
-)
-
-func _() {
-	var x int //@diag("x", "compiler", "x declared but not used")
-}
\ No newline at end of file
diff --git a/internal/lsp/testdata/bad/bad0.go b/internal/lsp/testdata/bad/bad0.go
deleted file mode 100644
index 5802ac4..0000000
--- a/internal/lsp/testdata/bad/bad0.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// +build go1.11
-
-package bad
-
-func stuff() { //@item(stuff, "stuff", "func()", "func")
-	x := "heeeeyyyy"
-	random2(x) //@diag("x", "compiler", "cannot use x (variable of type string) as int value in argument to random2")
-	random2(1) //@complete("dom", random, random2, random3)
-	y := 3     //@diag("y", "compiler", "y declared but not used")
-}
-
-type bob struct { //@item(bob, "bob", "struct{...}", "struct")
-	x int
-}
-
-func _() {
-	var q int
-	_ = &bob{
-		f: q, //@diag("f", "compiler", "unknown field f in struct literal")
-	}
-}
diff --git a/internal/lsp/testdata/cgoimport/usecgo.go.golden b/internal/lsp/testdata/cgoimport/usecgo.go.golden
deleted file mode 100644
index 14e2077..0000000
--- a/internal/lsp/testdata/cgoimport/usecgo.go.golden
+++ /dev/null
@@ -1,30 +0,0 @@
--- funccgoexample-definition --
-cgo/declarecgo.go:17:6-13: defined here as [`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)
-
-```go
-func cgo.Example()
-```
--- funccgoexample-definition-json --
-{
-	"span": {
-		"uri": "file://cgo/declarecgo.go",
-		"start": {
-			"line": 17,
-			"column": 6,
-			"offset": 153
-		},
-		"end": {
-			"line": 17,
-			"column": 13,
-			"offset": 160
-		}
-	},
-	"description": "[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)\n\n```go\nfunc cgo.Example()\n```"
-}
-
--- funccgoexample-hover --
-[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)
-
-```go
-func cgo.Example()
-```
diff --git a/internal/lsp/testdata/cgoimport/usegco.go.golden b/internal/lsp/testdata/cgoimport/usegco.go.golden
deleted file mode 100644
index 8d7131b..0000000
--- a/internal/lsp/testdata/cgoimport/usegco.go.golden
+++ /dev/null
@@ -1,2 +0,0 @@
--- funcexample-hover --
-func cgo.Example()
diff --git a/internal/lsp/testdata/circular/double/b/b.go b/internal/lsp/testdata/circular/double/b/b.go
deleted file mode 100644
index b99c92d..0000000
--- a/internal/lsp/testdata/circular/double/b/b.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package b
-
-import (
-	_ "golang.org/x/tools/internal/lsp/circular/double/one" //@diag("_ \"golang.org/x/tools/internal/lsp/circular/double/one\"", "compiler", "import cycle not allowed"),diag("\"golang.org/x/tools/internal/lsp/circular/double/one\"", "compiler", "could not import golang.org/x/tools/internal/lsp/circular/double/one (no package for import golang.org/x/tools/internal/lsp/circular/double/one)")
-)
diff --git a/internal/lsp/testdata/circular/self.go b/internal/lsp/testdata/circular/self.go
deleted file mode 100644
index d0cb5b6..0000000
--- a/internal/lsp/testdata/circular/self.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package circular
-
-import (
-	_ "golang.org/x/tools/internal/lsp/circular" //@diag("_ \"golang.org/x/tools/internal/lsp/circular\"", "compiler", "import cycle not allowed"),diag("\"golang.org/x/tools/internal/lsp/circular\"", "compiler", "could not import golang.org/x/tools/internal/lsp/circular (no package for import golang.org/x/tools/internal/lsp/circular)")
-)
diff --git a/internal/lsp/testdata/fieldlist/field_list.go b/internal/lsp/testdata/fieldlist/field_list.go
deleted file mode 100644
index c70530a..0000000
--- a/internal/lsp/testdata/fieldlist/field_list.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package fieldlist
-
-var myInt int   //@item(flVar, "myInt", "int", "var")
-type myType int //@item(flType, "myType", "int", "type")
-
-func (my) _()    {} //@complete(") _", flType, flVar)
-func (my my) _() {} //@complete(" my)"),complete(") _", flType, flVar)
-
-func (myType) _() {} //@complete(") {", flType, flVar)
-
-func (myType) _(my my) {} //@complete(" my)"),complete(") {", flType, flVar)
-
-func (myType) _() my {} //@complete(" {", flType, flVar)
-
-func (myType) _() (my my) {} //@complete(" my"),complete(") {", flType, flVar)
-
-func _() {
-	var _ struct {
-		//@complete("", flType, flVar)
-		m my //@complete(" my"),complete(" //", flType, flVar)
-	}
-
-	var _ interface {
-		//@complete("", flType, flVar)
-		m() my //@complete("("),complete(" //", flType, flVar)
-	}
-}
diff --git a/internal/lsp/testdata/generated/generated.go b/internal/lsp/testdata/generated/generated.go
deleted file mode 100644
index 27bc69b..0000000
--- a/internal/lsp/testdata/generated/generated.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package generated
-
-// Code generated by generator.go. DO NOT EDIT.
-
-func _() {
-	var y int //@diag("y", "compiler", "y declared but not used")
-}
diff --git a/internal/lsp/testdata/generated/generator.go b/internal/lsp/testdata/generated/generator.go
deleted file mode 100644
index 721703a..0000000
--- a/internal/lsp/testdata/generated/generator.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package generated
-
-func _() {
-	var x int //@diag("x", "compiler", "x declared but not used")
-}
diff --git a/internal/lsp/testdata/godef/b/b.go.golden b/internal/lsp/testdata/godef/b/b.go.golden
deleted file mode 100644
index 2d4837f..0000000
--- a/internal/lsp/testdata/godef/b/b.go.golden
+++ /dev/null
@@ -1,409 +0,0 @@
--- A-definition --
-godef/a/a.go:16:6-7: defined here as [`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)
-
-```go
-A string //@A
-
-```
--- A-definition-json --
-{
-	"span": {
-		"uri": "file://godef/a/a.go",
-		"start": {
-			"line": 16,
-			"column": 6,
-			"offset": 159
-		},
-		"end": {
-			"line": 16,
-			"column": 7,
-			"offset": 160
-		}
-	},
-	"description": "[`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)\n\n```go\nA string //@A\n\n```"
-}
-
--- A-hover --
-[`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)
-
-```go
-A string //@A
-
-```
--- AImport-definition --
-godef/b/b.go:5:2-43: defined here as [`a` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a)
-
-```go
-package a ("golang.org/x/tools/internal/lsp/godef/a")
-```
--- AImport-definition-json --
-{
-	"span": {
-		"uri": "file://godef/b/b.go",
-		"start": {
-			"line": 5,
-			"column": 2,
-			"offset": 112
-		},
-		"end": {
-			"line": 5,
-			"column": 43,
-			"offset": 153
-		}
-	},
-	"description": "[`a` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a)\n\n```go\npackage a (\"golang.org/x/tools/internal/lsp/godef/a\")\n```"
-}
-
--- AImport-hover --
-[`a` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a)
-
-```go
-package a ("golang.org/x/tools/internal/lsp/godef/a")
-```
--- AStuff-definition --
-godef/a/a.go:18:6-12: defined here as [`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)
-
-```go
-func a.AStuff()
-```
--- AStuff-definition-json --
-{
-	"span": {
-		"uri": "file://godef/a/a.go",
-		"start": {
-			"line": 18,
-			"column": 6,
-			"offset": 179
-		},
-		"end": {
-			"line": 18,
-			"column": 12,
-			"offset": 185
-		}
-	},
-	"description": "[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)\n\n```go\nfunc a.AStuff()\n```"
-}
-
--- AStuff-hover --
-[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)
-
-```go
-func a.AStuff()
-```
--- PackageFoo-definition --
-foo/foo.go:1:1-30:16: defined here as myFoo "golang.org/x/tools/internal/lsp/foo" //@mark(myFoo, "myFoo"),godef("foo", PackageFoo),godef("myFoo", myFoo)
--- PackageFoo-definition-json --
-{
-	"span": {
-		"uri": "file://foo/foo.go",
-		"start": {
-			"line": 1,
-			"column": 1,
-			"offset": 0
-		},
-		"end": {
-			"line": 30,
-			"column": 16,
-			"offset": 922
-		}
-	},
-	"description": "myFoo \"golang.org/x/tools/internal/lsp/foo\" //@mark(myFoo, \"myFoo\"),godef(\"foo\", PackageFoo),godef(\"myFoo\", myFoo)"
-}
-
--- PackageFoo-hover --
-myFoo "golang.org/x/tools/internal/lsp/foo" //@mark(myFoo, "myFoo"),godef("foo", PackageFoo),godef("myFoo", myFoo)
-
--- S1-definition --
-godef/b/b.go:8:6-8: defined here as [`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
-
-```go
-S1 struct {
-	F1  int //@mark(S1F1, "F1")
-	S2      //@godef("S2", S2), mark(S1S2, "S2")
-	a.A     //@godef("A", A)
-}
-```
--- S1-definition-json --
-{
-	"span": {
-		"uri": "file://godef/b/b.go",
-		"start": {
-			"line": 8,
-			"column": 6,
-			"offset": 193
-		},
-		"end": {
-			"line": 8,
-			"column": 8,
-			"offset": 195
-		}
-	},
-	"description": "[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)\n\n```go\nS1 struct {\n\tF1  int //@mark(S1F1, \"F1\")\n\tS2      //@godef(\"S2\", S2), mark(S1S2, \"S2\")\n\ta.A     //@godef(\"A\", A)\n}\n```"
-}
-
--- S1-hover --
-[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
-
-```go
-S1 struct {
-	F1  int //@mark(S1F1, "F1")
-	S2      //@godef("S2", S2), mark(S1S2, "S2")
-	a.A     //@godef("A", A)
-}
-```
--- S1F1-definition --
-godef/b/b.go:9:2-4: defined here as \@mark\(S1F1, \"F1\"\)
-
-[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)
-
-```go
-field F1 int
-```
--- S1F1-definition-json --
-{
-	"span": {
-		"uri": "file://godef/b/b.go",
-		"start": {
-			"line": 9,
-			"column": 2,
-			"offset": 212
-		},
-		"end": {
-			"line": 9,
-			"column": 4,
-			"offset": 214
-		}
-	},
-	"description": "\\@mark\\(S1F1, \\\"F1\\\"\\)\n\n[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)\n\n```go\nfield F1 int\n```"
-}
-
--- S1F1-hover --
-\@mark\(S1F1, \"F1\"\)
-
-[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)
-
-```go
-field F1 int
-```
--- S1S2-definition --
-godef/b/b.go:10:2-4: defined here as \@godef\(\"S2\", S2\), mark\(S1S2, \"S2\"\)
-
-[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.S2)
-
-```go
-field S2 S2
-```
--- S1S2-definition-json --
-{
-	"span": {
-		"uri": "file://godef/b/b.go",
-		"start": {
-			"line": 10,
-			"column": 2,
-			"offset": 241
-		},
-		"end": {
-			"line": 10,
-			"column": 4,
-			"offset": 243
-		}
-	},
-	"description": "\\@godef\\(\\\"S2\\\", S2\\), mark\\(S1S2, \\\"S2\\\"\\)\n\n[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.S2)\n\n```go\nfield S2 S2\n```"
-}
-
--- S1S2-hover --
-\@godef\(\"S2\", S2\), mark\(S1S2, \"S2\"\)
-
-[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.S2)
-
-```go
-field S2 S2
-```
--- S2-definition --
-godef/b/b.go:14:6-8: defined here as [`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)
-
-```go
-S2 struct {
-	F1   string //@mark(S2F1, "F1")
-	F2   int    //@mark(S2F2, "F2")
-	*a.A        //@godef("A", A),godef("a",AImport)
-}
-```
--- S2-definition-json --
-{
-	"span": {
-		"uri": "file://godef/b/b.go",
-		"start": {
-			"line": 14,
-			"column": 6,
-			"offset": 320
-		},
-		"end": {
-			"line": 14,
-			"column": 8,
-			"offset": 322
-		}
-	},
-	"description": "[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)\n\n```go\nS2 struct {\n\tF1   string //@mark(S2F1, \"F1\")\n\tF2   int    //@mark(S2F2, \"F2\")\n\t*a.A        //@godef(\"A\", A),godef(\"a\",AImport)\n}\n```"
-}
-
--- S2-hover --
-[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)
-
-```go
-S2 struct {
-	F1   string //@mark(S2F1, "F1")
-	F2   int    //@mark(S2F2, "F2")
-	*a.A        //@godef("A", A),godef("a",AImport)
-}
-```
--- S2F1-definition --
-godef/b/b.go:15:2-4: defined here as \@mark\(S2F1, \"F1\"\)
-
-[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2.F1)
-
-```go
-field F1 string
-```
--- S2F1-definition-json --
-{
-	"span": {
-		"uri": "file://godef/b/b.go",
-		"start": {
-			"line": 15,
-			"column": 2,
-			"offset": 339
-		},
-		"end": {
-			"line": 15,
-			"column": 4,
-			"offset": 341
-		}
-	},
-	"description": "\\@mark\\(S2F1, \\\"F1\\\"\\)\n\n[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2.F1)\n\n```go\nfield F1 string\n```"
-}
-
--- S2F1-hover --
-\@mark\(S2F1, \"F1\"\)
-
-[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2.F1)
-
-```go
-field F1 string
-```
--- S2F2-definition --
-godef/b/b.go:16:2-4: defined here as \@mark\(S2F2, \"F2\"\)
-
-[`(b.S1).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F2)
-
-```go
-field F2 int
-```
--- S2F2-definition-json --
-{
-	"span": {
-		"uri": "file://godef/b/b.go",
-		"start": {
-			"line": 16,
-			"column": 2,
-			"offset": 372
-		},
-		"end": {
-			"line": 16,
-			"column": 4,
-			"offset": 374
-		}
-	},
-	"description": "\\@mark\\(S2F2, \\\"F2\\\"\\)\n\n[`(b.S1).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F2)\n\n```go\nfield F2 int\n```"
-}
-
--- S2F2-hover --
-\@mark\(S2F2, \"F2\"\)
-
-[`(b.S1).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F2)
-
-```go
-field F2 int
-```
--- Stuff-definition --
-godef/a/a.go:9:6-11: defined here as func a.Stuff()
--- Stuff-definition-json --
-{
-	"span": {
-		"uri": "file://godef/a/a.go",
-		"start": {
-			"line": 9,
-			"column": 6,
-			"offset": 95
-		},
-		"end": {
-			"line": 9,
-			"column": 11,
-			"offset": 100
-		}
-	},
-	"description": "func a.Stuff()"
-}
-
--- Stuff-hover --
-func a.Stuff()
--- X-definition --
-godef/b/b.go:37:7-8: defined here as [`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)
-
-```go
-const X untyped int = 0
-```
--- X-definition-json --
-{
-	"span": {
-		"uri": "file://godef/b/b.go",
-		"start": {
-			"line": 37,
-			"column": 7,
-			"offset": 795
-		},
-		"end": {
-			"line": 37,
-			"column": 8,
-			"offset": 796
-		}
-	},
-	"description": "[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)\n\n```go\nconst X untyped int = 0\n```"
-}
-
--- X-hover --
-[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)
-
-```go
-const X untyped int = 0
-```
--- myFoo-definition --
-godef/b/b.go:4:2-7: defined here as [`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/foo)
-
-```go
-package myFoo ("golang.org/x/tools/internal/lsp/foo")
-```
--- myFoo-definition-json --
-{
-	"span": {
-		"uri": "file://godef/b/b.go",
-		"start": {
-			"line": 4,
-			"column": 2,
-			"offset": 21
-		},
-		"end": {
-			"line": 4,
-			"column": 7,
-			"offset": 26
-		}
-	},
-	"description": "[`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/foo)\n\n```go\npackage myFoo (\"golang.org/x/tools/internal/lsp/foo\")\n```"
-}
-
--- myFoo-hover --
-[`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/foo)
-
-```go
-package myFoo ("golang.org/x/tools/internal/lsp/foo")
-```
diff --git a/internal/lsp/testdata/godef/b/c.go.golden b/internal/lsp/testdata/godef/b/c.go.golden
deleted file mode 100644
index cf7e753..0000000
--- a/internal/lsp/testdata/godef/b/c.go.golden
+++ /dev/null
@@ -1,72 +0,0 @@
--- S1-definition --
-godef/b/b.go:8:6-8: defined here as [`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
-
-```go
-S1 struct {
-	F1  int //@mark(S1F1, "F1")
-	S2      //@godef("S2", S2), mark(S1S2, "S2")
-	a.A     //@godef("A", A)
-}
-```
--- S1-definition-json --
-{
-	"span": {
-		"uri": "file://godef/b/b.go",
-		"start": {
-			"line": 8,
-			"column": 6,
-			"offset": 193
-		},
-		"end": {
-			"line": 8,
-			"column": 8,
-			"offset": 195
-		}
-	},
-	"description": "[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)\n\n```go\nS1 struct {\n\tF1  int //@mark(S1F1, \"F1\")\n\tS2      //@godef(\"S2\", S2), mark(S1S2, \"S2\")\n\ta.A     //@godef(\"A\", A)\n}\n```"
-}
-
--- S1-hover --
-[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
-
-```go
-S1 struct {
-	F1  int //@mark(S1F1, "F1")
-	S2      //@godef("S2", S2), mark(S1S2, "S2")
-	a.A     //@godef("A", A)
-}
-```
--- S1F1-definition --
-godef/b/b.go:9:2-4: defined here as \@mark\(S1F1, \"F1\"\)
-
-[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)
-
-```go
-field F1 int
-```
--- S1F1-definition-json --
-{
-	"span": {
-		"uri": "file://godef/b/b.go",
-		"start": {
-			"line": 9,
-			"column": 2,
-			"offset": 212
-		},
-		"end": {
-			"line": 9,
-			"column": 4,
-			"offset": 214
-		}
-	},
-	"description": "\\@mark\\(S1F1, \\\"F1\\\"\\)\n\n[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)\n\n```go\nfield F1 int\n```"
-}
-
--- S1F1-hover --
-\@mark\(S1F1, \"F1\"\)
-
-[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)
-
-```go
-field F1 int
-```
diff --git a/internal/lsp/testdata/indirect/modules/example.com/extramodule/pkg/x.go b/internal/lsp/testdata/indirect/modules/example.com/extramodule/pkg/x.go
new file mode 100644
index 0000000..cf7fc67
--- /dev/null
+++ b/internal/lsp/testdata/indirect/modules/example.com/extramodule/pkg/x.go
@@ -0,0 +1,3 @@
+package pkg
+
+const Test = 1
\ No newline at end of file
diff --git a/internal/lsp/testdata/indirect/primarymod/go.mod b/internal/lsp/testdata/indirect/primarymod/go.mod
new file mode 100644
index 0000000..cfc9f72
--- /dev/null
+++ b/internal/lsp/testdata/indirect/primarymod/go.mod
@@ -0,0 +1,5 @@
+module indirect
+
+go 1.12
+//@diag("// indirect", "go mod tidy", "example.com/extramodule should be a direct dependency.", "warning"),suggestedfix("// indirect")
+require example.com/extramodule v1.0.0 // indirect
diff --git a/internal/lsp/testdata/indirect/primarymod/go.mod.golden b/internal/lsp/testdata/indirect/primarymod/go.mod.golden
new file mode 100644
index 0000000..5bc2879
--- /dev/null
+++ b/internal/lsp/testdata/indirect/primarymod/go.mod.golden
@@ -0,0 +1,8 @@
+-- suggestedfix_go.mod_5_40 --
+module indirect
+
+go 1.12
+
+//@diag("// indirect", "go mod tidy", "example.com/extramodule should be a direct dependency.", "warning"),suggestedfix("// indirect")
+require example.com/extramodule v1.0.0
+
diff --git a/internal/lsp/testdata/indirect/primarymod/main.go b/internal/lsp/testdata/indirect/primarymod/main.go
new file mode 100644
index 0000000..2fb1cc8
--- /dev/null
+++ b/internal/lsp/testdata/indirect/primarymod/main.go
@@ -0,0 +1,10 @@
+// Package indirect does something
+package indirect
+
+import (
+	"example.com/extramodule/pkg"
+)
+
+func Yo() {
+	var _ pkg.Test
+}
diff --git a/internal/lsp/testdata/indirect/summary.txt.golden b/internal/lsp/testdata/indirect/summary.txt.golden
new file mode 100644
index 0000000..5c4f74a
--- /dev/null
+++ b/internal/lsp/testdata/indirect/summary.txt.golden
@@ -0,0 +1,28 @@
+-- summary --
+CodeLensCount = 0
+CompletionsCount = 0
+CompletionSnippetCount = 0
+UnimportedCompletionsCount = 0
+DeepCompletionsCount = 0
+FuzzyCompletionsCount = 0
+RankedCompletionsCount = 0
+CaseSensitiveCompletionsCount = 0
+DiagnosticsCount = 1
+FoldingRangesCount = 0
+FormatCount = 0
+ImportCount = 0
+SuggestedFixCount = 1
+DefinitionsCount = 0
+TypeDefinitionsCount = 0
+HighlightsCount = 0
+ReferencesCount = 0
+RenamesCount = 0
+PrepareRenamesCount = 0
+SymbolsCount = 0
+WorkspaceSymbolsCount = 0
+FuzzyWorkspaceSymbolsCount = 0
+CaseSensitiveWorkspaceSymbolsCount = 0
+SignaturesCount = 0
+LinksCount = 0
+ImplementationsCount = 0
+
diff --git a/internal/lsp/testdata/lsp/modules/example.com/extramodule/pkg/x.go b/internal/lsp/testdata/lsp/modules/example.com/extramodule/pkg/x.go
new file mode 100644
index 0000000..cf7fc67
--- /dev/null
+++ b/internal/lsp/testdata/lsp/modules/example.com/extramodule/pkg/x.go
@@ -0,0 +1,3 @@
+package pkg
+
+const Test = 1
\ No newline at end of file
diff --git a/internal/lsp/testdata/lsp/primarymod/%percent/perc%ent.go b/internal/lsp/testdata/lsp/primarymod/%percent/perc%ent.go
new file mode 100644
index 0000000..4fe88d0
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/%percent/perc%ent.go
@@ -0,0 +1,8 @@
+package percent
+
+import (
+)
+
+func _() {
+	var x int //@diag("x", "compiler", "x declared but not used", "error")
+}
\ No newline at end of file
diff --git a/internal/lsp/testdata/address/address.go b/internal/lsp/testdata/lsp/primarymod/address/address.go
similarity index 93%
rename from internal/lsp/testdata/address/address.go
rename to internal/lsp/testdata/lsp/primarymod/address/address.go
index f1c9528..59d5d4c 100644
--- a/internal/lsp/testdata/address/address.go
+++ b/internal/lsp/testdata/lsp/primarymod/address/address.go
@@ -37,6 +37,10 @@
 
 	wantsVariadic() //@rank(")", addrCPtr, addrA),snippet(")", addrCPtr, "*c", "*c")
 
+	var d **int
+	**d           //@item(addrDPtr, "**d", "**int", "var")
+	var _ int = _ //@rank("_ //", addrDPtr, addrA),snippet("_ //", addrDPtr, "**d", "**d")
+
 	type namedPtr *int
 	var np namedPtr
 	*np           //@item(addrNamedPtr, "*np", "namedPtr", "var")
diff --git a/internal/lsp/testdata/analyzer/bad_test.go b/internal/lsp/testdata/lsp/primarymod/analyzer/bad_test.go
similarity index 66%
rename from internal/lsp/testdata/analyzer/bad_test.go
rename to internal/lsp/testdata/lsp/primarymod/analyzer/bad_test.go
index 9e54056..3c57cd0 100644
--- a/internal/lsp/testdata/analyzer/bad_test.go
+++ b/internal/lsp/testdata/lsp/primarymod/analyzer/bad_test.go
@@ -6,11 +6,11 @@
 	"testing"
 )
 
-func Testbad(t *testing.T) { //@diag("", "tests", "Testbad has malformed name: first letter after 'Test' must not be lowercase")
+func Testbad(t *testing.T) { //@diag("", "tests", "Testbad has malformed name: first letter after 'Test' must not be lowercase", "warning")
 	var x sync.Mutex
-	_ = x //@diag("x", "copylocks", "assignment copies lock value to _: sync.Mutex")
+	_ = x //@diag("x", "copylocks", "assignment copies lock value to _: sync.Mutex", "warning")
 
-	printfWrapper("%s") //@diag(re`printfWrapper\(.*\)`, "printf", "printfWrapper format %s reads arg #1, but call has 0 args")
+	printfWrapper("%s") //@diag(re`printfWrapper\(.*\)`, "printf", "printfWrapper format %s reads arg #1, but call has 0 args", "warning")
 }
 
 func printfWrapper(format string, args ...interface{}) {
diff --git a/internal/lsp/testdata/anon/anon.go.in b/internal/lsp/testdata/lsp/primarymod/anon/anon.go.in
similarity index 100%
rename from internal/lsp/testdata/anon/anon.go.in
rename to internal/lsp/testdata/lsp/primarymod/anon/anon.go.in
diff --git a/internal/lsp/testdata/append/append.go b/internal/lsp/testdata/lsp/primarymod/append/append.go
similarity index 100%
rename from internal/lsp/testdata/append/append.go
rename to internal/lsp/testdata/lsp/primarymod/append/append.go
diff --git a/internal/lsp/testdata/arraytype/array_type.go.in b/internal/lsp/testdata/lsp/primarymod/arraytype/array_type.go.in
similarity index 71%
rename from internal/lsp/testdata/arraytype/array_type.go.in
rename to internal/lsp/testdata/lsp/primarymod/arraytype/array_type.go.in
index fc05268..a53ee74 100644
--- a/internal/lsp/testdata/arraytype/array_type.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/arraytype/array_type.go.in
@@ -9,14 +9,14 @@
 		val string //@item(atVal, "val", "string", "var")
 	)
 
-	[] //@complete(" //", atVal, PackageFoo)
+	[] //@complete(" //", PackageFoo)
 
-	[]val //@complete(" //", atVal)
+	[]val //@complete(" //")
 
 	[]foo.StructFoo //@complete(" //", StructFoo)
 
 	[]foo.StructFoo(nil) //@complete("(", StructFoo)
-	
+
 	[]*foo.StructFoo //@complete(" //", StructFoo)
 
 	[...]foo.StructFoo //@complete(" //", StructFoo)
@@ -32,12 +32,12 @@
 	var mark []myInt //@item(atMark, "mark", "[]myInt", "var")
 
 	var s []myInt //@item(atS, "s", "[]myInt", "var")
-	s = []m //@complete(" //", atMyInt, atMark)
-	s = [] //@complete(" //", atMyInt, atMark, atS, PackageFoo)
+	s = []m //@complete(" //", atMyInt)
+	s = [] //@complete(" //", atMyInt, PackageFoo)
 
 	var a [1]myInt
-	a = [1]m //@complete(" //", atMyInt, atMark)
+	a = [1]m //@complete(" //", atMyInt)
 
 	var ds [][]myInt
-	ds = [][]m //@complete(" //", atMyInt, atMark)
+	ds = [][]m //@complete(" //", atMyInt)
 }
diff --git a/internal/lsp/testdata/assign/assign.go.in b/internal/lsp/testdata/lsp/primarymod/assign/assign.go.in
similarity index 79%
rename from internal/lsp/testdata/assign/assign.go.in
rename to internal/lsp/testdata/lsp/primarymod/assign/assign.go.in
index f07c461..3fe256e 100644
--- a/internal/lsp/testdata/assign/assign.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/assign/assign.go.in
@@ -1,6 +1,9 @@
 package assign
 
+import "golang.org/x/tools/internal/lsp/assign/internal/secret"
+
 func _() {
+	secret.Hello()
 	var (
 		myInt int //@item(assignInt, "myInt", "int", "var")
 		myStr string //@item(assignStr, "myStr", "string", "var")
diff --git a/internal/lsp/testdata/lsp/primarymod/assign/internal/secret/secret.go b/internal/lsp/testdata/lsp/primarymod/assign/internal/secret/secret.go
new file mode 100644
index 0000000..5ee1554
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/assign/internal/secret/secret.go
@@ -0,0 +1,3 @@
+package secret
+
+func Hello() {}
\ No newline at end of file
diff --git a/internal/lsp/testdata/lsp/primarymod/bad/bad0.go b/internal/lsp/testdata/lsp/primarymod/bad/bad0.go
new file mode 100644
index 0000000..cfde87c
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/bad/bad0.go
@@ -0,0 +1,23 @@
+// +build go1.11
+
+package bad
+
+import _ "golang.org/x/tools/internal/lsp/assign/internal/secret" //@diag("\"golang.org/x/tools/internal/lsp/assign/internal/secret\"", "compiler", "could not import golang.org/x/tools/internal/lsp/assign/internal/secret (invalid use of internal package golang.org/x/tools/internal/lsp/assign/internal/secret)", "error")
+
+func stuff() { //@item(stuff, "stuff", "func()", "func")
+	x := "heeeeyyyy"
+	random2(x) //@diag("x", "compiler", "cannot use x (variable of type string) as int value in argument to random2", "error")
+	random2(1) //@complete("dom", random, random2, random3)
+	y := 3     //@diag("y", "compiler", "y declared but not used", "error")
+}
+
+type bob struct { //@item(bob, "bob", "struct{...}", "struct")
+	x int
+}
+
+func _() {
+	var q int
+	_ = &bob{
+		f: q, //@diag("f", "compiler", "unknown field f in struct literal", "error")
+	}
+}
diff --git a/internal/lsp/testdata/bad/bad1.go b/internal/lsp/testdata/lsp/primarymod/bad/bad1.go
similarity index 60%
rename from internal/lsp/testdata/bad/bad1.go
rename to internal/lsp/testdata/lsp/primarymod/bad/bad1.go
index f6ad8c2..512f2d9 100644
--- a/internal/lsp/testdata/bad/bad1.go
+++ b/internal/lsp/testdata/lsp/primarymod/bad/bad1.go
@@ -5,7 +5,7 @@
 // See #36637
 type stateFunc func() stateFunc //@item(stateFunc, "stateFunc", "func() stateFunc", "type")
 
-var a unknown //@item(global_a, "a", "unknown", "var"),diag("unknown", "compiler", "undeclared name: unknown")
+var a unknown //@item(global_a, "a", "unknown", "var"),diag("unknown", "compiler", "undeclared name: unknown", "error")
 
 func random() int { //@item(random, "random", "func() int", "func")
 	//@complete("", global_a, bob, random, random2, random3, stateFunc, stuff)
@@ -13,9 +13,9 @@
 }
 
 func random2(y int) int { //@item(random2, "random2", "func(y int) int", "func"),item(bad_y_param, "y", "int", "var")
-	x := 6       //@item(x, "x", "int", "var"),diag("x", "compiler", "x declared but not used")
-	var q blah   //@item(q, "q", "blah", "var"),diag("q", "compiler", "q declared but not used"),diag("blah", "compiler", "undeclared name: blah")
-	var t **blob //@item(t, "t", "**blob", "var"),diag("t", "compiler", "t declared but not used"),diag("blob", "compiler", "undeclared name: blob")
+	x := 6       //@item(x, "x", "int", "var"),diag("x", "compiler", "x declared but not used", "error")
+	var q blah   //@item(q, "q", "blah", "var"),diag("q", "compiler", "q declared but not used", "error"),diag("blah", "compiler", "undeclared name: blah", "error")
+	var t **blob //@item(t, "t", "**blob", "var"),diag("t", "compiler", "t declared but not used", "error"),diag("blob", "compiler", "undeclared name: blob", "error")
 	//@complete("", q, t, x, bad_y_param, global_a, bob, random, random2, random3, stateFunc, stuff)
 
 	return y
@@ -24,10 +24,10 @@
 func random3(y ...int) { //@item(random3, "random3", "func(y ...int)", "func"),item(y_variadic_param, "y", "[]int", "var")
 	//@complete("", y_variadic_param, global_a, bob, random, random2, random3, stateFunc, stuff)
 
-	var ch chan (favType1)   //@item(ch, "ch", "chan (favType1)", "var"),diag("ch", "compiler", "ch declared but not used"),diag("favType1", "compiler", "undeclared name: favType1")
-	var m map[keyType]int    //@item(m, "m", "map[keyType]int", "var"),diag("m", "compiler", "m declared but not used"),diag("keyType", "compiler", "undeclared name: keyType")
-	var arr []favType2       //@item(arr, "arr", "[]favType2", "var"),diag("arr", "compiler", "arr declared but not used"),diag("favType2", "compiler", "undeclared name: favType2")
-	var fn1 func() badResult //@item(fn1, "fn1", "func() badResult", "var"),diag("fn1", "compiler", "fn1 declared but not used"),diag("badResult", "compiler", "undeclared name: badResult")
-	var fn2 func(badParam)   //@item(fn2, "fn2", "func(badParam)", "var"),diag("fn2", "compiler", "fn2 declared but not used"),diag("badParam", "compiler", "undeclared name: badParam")
+	var ch chan (favType1)   //@item(ch, "ch", "chan (favType1)", "var"),diag("ch", "compiler", "ch declared but not used", "error"),diag("favType1", "compiler", "undeclared name: favType1", "error")
+	var m map[keyType]int    //@item(m, "m", "map[keyType]int", "var"),diag("m", "compiler", "m declared but not used", "error"),diag("keyType", "compiler", "undeclared name: keyType", "error")
+	var arr []favType2       //@item(arr, "arr", "[]favType2", "var"),diag("arr", "compiler", "arr declared but not used", "error"),diag("favType2", "compiler", "undeclared name: favType2", "error")
+	var fn1 func() badResult //@item(fn1, "fn1", "func() badResult", "var"),diag("fn1", "compiler", "fn1 declared but not used", "error"),diag("badResult", "compiler", "undeclared name: badResult", "error")
+	var fn2 func(badParam)   //@item(fn2, "fn2", "func(badParam)", "var"),diag("fn2", "compiler", "fn2 declared but not used", "error"),diag("badParam", "compiler", "undeclared name: badParam", "error")
 	//@complete("", arr, ch, fn1, fn2, m, y_variadic_param, global_a, bob, random, random2, random3, stateFunc, stuff)
 }
diff --git a/internal/lsp/testdata/bad/badimport.go b/internal/lsp/testdata/lsp/primarymod/bad/badimport.go
similarity index 64%
rename from internal/lsp/testdata/bad/badimport.go
rename to internal/lsp/testdata/lsp/primarymod/bad/badimport.go
index f7d8a11..fefc22e 100644
--- a/internal/lsp/testdata/bad/badimport.go
+++ b/internal/lsp/testdata/lsp/primarymod/bad/badimport.go
@@ -1,5 +1,5 @@
 package bad
 
 import (
-	_ "nosuchpkg" //@diag("_", "compiler", "could not import nosuchpkg (no package for import nosuchpkg)")
+	_ "nosuchpkg" //@diag("_", "compiler", "could not import nosuchpkg (no package for import nosuchpkg)", "error")
 )
diff --git a/internal/lsp/testdata/badstmt/badstmt.go.in b/internal/lsp/testdata/lsp/primarymod/badstmt/badstmt.go.in
similarity index 88%
rename from internal/lsp/testdata/badstmt/badstmt.go.in
rename to internal/lsp/testdata/lsp/primarymod/badstmt/badstmt.go.in
index c25b080..35cfa54 100644
--- a/internal/lsp/testdata/badstmt/badstmt.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/badstmt/badstmt.go.in
@@ -5,7 +5,7 @@
 )
 
 func _() {
-	defer foo.F //@complete(" //", Foo),diag(" //", "syntax", "function must be invoked in defer statement")
+	defer foo.F //@complete(" //", Foo),diag(" //", "syntax", "function must be invoked in defer statement", "error")
 	y := 1
 	defer foo.F //@complete(" //", Foo)
 }
diff --git a/internal/lsp/testdata/badstmt/badstmt_2.go.in b/internal/lsp/testdata/lsp/primarymod/badstmt/badstmt_2.go.in
similarity index 100%
rename from internal/lsp/testdata/badstmt/badstmt_2.go.in
rename to internal/lsp/testdata/lsp/primarymod/badstmt/badstmt_2.go.in
diff --git a/internal/lsp/testdata/badstmt/badstmt_3.go.in b/internal/lsp/testdata/lsp/primarymod/badstmt/badstmt_3.go.in
similarity index 100%
rename from internal/lsp/testdata/badstmt/badstmt_3.go.in
rename to internal/lsp/testdata/lsp/primarymod/badstmt/badstmt_3.go.in
diff --git a/internal/lsp/testdata/badstmt/badstmt_4.go.in b/internal/lsp/testdata/lsp/primarymod/badstmt/badstmt_4.go.in
similarity index 100%
rename from internal/lsp/testdata/badstmt/badstmt_4.go.in
rename to internal/lsp/testdata/lsp/primarymod/badstmt/badstmt_4.go.in
diff --git a/internal/lsp/testdata/bar/bar.go.in b/internal/lsp/testdata/lsp/primarymod/bar/bar.go.in
similarity index 89%
rename from internal/lsp/testdata/bar/bar.go.in
rename to internal/lsp/testdata/lsp/primarymod/bar/bar.go.in
index dd80911..70b69b8 100644
--- a/internal/lsp/testdata/bar/bar.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/bar/bar.go.in
@@ -10,13 +10,13 @@
 
 func _() {
 	help //@complete("l", helper)
-	_ = foo.StructFoo{} //@complete("S", IntFoo, StructFoo, Foo)
+	_ = foo.StructFoo{} //@complete("S", IntFoo, StructFoo)
 }
 
 // Bar is a function.
 func Bar() { //@item(Bar, "Bar", "func()", "func", "Bar is a function.")
 	foo.Foo()        //@complete("F", Foo, IntFoo, StructFoo)
-	var _ foo.IntFoo //@complete("I", Foo, IntFoo, StructFoo)
+	var _ foo.IntFoo //@complete("I", IntFoo, StructFoo)
 	foo.()           //@complete("(", Foo, IntFoo, StructFoo)
 }
 
diff --git a/internal/lsp/testdata/basiclit/basiclit.go b/internal/lsp/testdata/lsp/primarymod/basiclit/basiclit.go
similarity index 100%
rename from internal/lsp/testdata/basiclit/basiclit.go
rename to internal/lsp/testdata/lsp/primarymod/basiclit/basiclit.go
diff --git a/internal/lsp/testdata/baz/baz.go.in b/internal/lsp/testdata/lsp/primarymod/baz/baz.go.in
similarity index 77%
rename from internal/lsp/testdata/baz/baz.go.in
rename to internal/lsp/testdata/lsp/primarymod/baz/baz.go.in
index 4652e96..3b74ee5 100644
--- a/internal/lsp/testdata/baz/baz.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/baz/baz.go.in
@@ -20,13 +20,13 @@
 
 func _() {
 	bob := f.StructFoo{Value: 5}
-	if x := bob.           //@complete(" //", Value)
+	if x := bob. //@complete(" //", Value)
 	switch true == false {
 		case true:
-			if x := bob.   //@complete(" //", Value)
+			if x := bob. //@complete(" //", Value)
 		case false:
 	}
-	if x := bob.Va         //@complete("a", Value)
+	if x := bob.Va //@complete("a", Value)
 	switch true == true {
 		default:
 	}
diff --git a/internal/lsp/testdata/builtins/builtin_args.go b/internal/lsp/testdata/lsp/primarymod/builtins/builtin_args.go
similarity index 71%
rename from internal/lsp/testdata/builtins/builtin_args.go
rename to internal/lsp/testdata/lsp/primarymod/builtins/builtin_args.go
index 040d7e3..4556021 100644
--- a/internal/lsp/testdata/builtins/builtin_args.go
+++ b/internal/lsp/testdata/lsp/primarymod/builtins/builtin_args.go
@@ -12,10 +12,18 @@
 		aInt      int            //@item(builtinInt, "aInt", "int", "var")
 	)
 
+	type (
+		aSliceType []int          //@item(builtinSliceType, "aSliceType", "[]int", "type")
+		aChanType  chan int       //@item(builtinChanType, "aChanType", "chan int", "type")
+		aMapType   map[string]int //@item(builtinMapType, "aMapType", "map[string]int", "type")
+	)
+
 	close() //@rank(")", builtinChan, builtinSlice)
 
 	append() //@rank(")", builtinSlice, builtinChan)
 
+	var _ []byte = append([]byte(nil), ""...) //@rank(") //")
+
 	copy()           //@rank(")", builtinSlice, builtinChan)
 	copy(aSlice, aS) //@rank(")", builtinSlice, builtinString)
 	copy(aS, aSlice) //@rank(",", builtinSlice, builtinString)
@@ -23,16 +31,21 @@
 	delete()         //@rank(")", builtinMap, builtinChan)
 	delete(aMap, aS) //@rank(")", builtinString, builtinSlice)
 
+	aMapFunc := func() map[int]int { //@item(builtinMapFunc, "aMapFunc", "func() map[int]int", "var")
+		return nil
+	}
+	delete() //@rank(")", builtinMapFunc, builtinSlice)
+
 	len() //@rank(")", builtinSlice, builtinInt),rank(")", builtinMap, builtinInt),rank(")", builtinString, builtinInt),rank(")", builtinArray, builtinInt),rank(")", builtinArrayPtr, builtinPtr),rank(")", builtinChan, builtinInt)
 
 	cap() //@rank(")", builtinSlice, builtinMap),rank(")", builtinArray, builtinString),rank(")", builtinArrayPtr, builtinPtr),rank(")", builtinChan, builtinInt)
 
-	make() //@rank(")", builtinMap, builtinInt),rank(")", builtinChan, builtinInt),rank(")", builtinSlice, builtinInt)
+	make()              //@rank(")", builtinMapType, int),rank(")", builtinChanType, int),rank(")", builtinSliceType, int),rank(")", builtinMapType, int)
+	make(aSliceType, a) //@rank(")", builtinInt, builtinSlice)
 
-	var _ []int = make() //@rank(")", builtinSlice, builtinMap)
+	var _ []int = make() //@rank(")", builtinSliceType, builtinMapType)
 
 	type myStruct struct{}  //@item(builtinStructType, "myStruct", "struct{...}", "struct")
-	new()                   //@rank(")", builtinStructType, builtinInt)
 	var _ *myStruct = new() //@rank(")", builtinStructType, int)
 
 	for k := range a { //@rank(" {", builtinSlice, builtinInt),rank(" {", builtinString, builtinInt),rank(" {", builtinChan, builtinInt),rank(" {", builtinArray, builtinInt),rank(" {", builtinArrayPtr, builtinInt),rank(" {", builtinMap, builtinInt),
diff --git a/internal/lsp/testdata/builtins/builtins.go b/internal/lsp/testdata/lsp/primarymod/builtins/builtins.go
similarity index 100%
rename from internal/lsp/testdata/builtins/builtins.go
rename to internal/lsp/testdata/lsp/primarymod/builtins/builtins.go
diff --git a/internal/lsp/testdata/builtins/constants.go b/internal/lsp/testdata/lsp/primarymod/builtins/constants.go
similarity index 100%
rename from internal/lsp/testdata/builtins/constants.go
rename to internal/lsp/testdata/lsp/primarymod/builtins/constants.go
diff --git a/internal/lsp/testdata/casesensitive/casesensitive.go b/internal/lsp/testdata/lsp/primarymod/casesensitive/casesensitive.go
similarity index 100%
rename from internal/lsp/testdata/casesensitive/casesensitive.go
rename to internal/lsp/testdata/lsp/primarymod/casesensitive/casesensitive.go
diff --git a/internal/lsp/testdata/cast/cast.go.in b/internal/lsp/testdata/lsp/primarymod/cast/cast.go.in
similarity index 100%
rename from internal/lsp/testdata/cast/cast.go.in
rename to internal/lsp/testdata/lsp/primarymod/cast/cast.go.in
diff --git a/internal/lsp/testdata/cgo/declarecgo.go b/internal/lsp/testdata/lsp/primarymod/cgo/declarecgo.go
similarity index 100%
rename from internal/lsp/testdata/cgo/declarecgo.go
rename to internal/lsp/testdata/lsp/primarymod/cgo/declarecgo.go
diff --git a/internal/lsp/testdata/cgo/declarecgo_nocgo.go b/internal/lsp/testdata/lsp/primarymod/cgo/declarecgo_nocgo.go
similarity index 100%
rename from internal/lsp/testdata/cgo/declarecgo_nocgo.go
rename to internal/lsp/testdata/lsp/primarymod/cgo/declarecgo_nocgo.go
diff --git a/internal/lsp/testdata/lsp/primarymod/cgoimport/usecgo.go.golden b/internal/lsp/testdata/lsp/primarymod/cgoimport/usecgo.go.golden
new file mode 100644
index 0000000..35937f1
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/cgoimport/usecgo.go.golden
@@ -0,0 +1,30 @@
+-- funccgoexample-definition --
+cgo/declarecgo.go:17:6-13: defined here as ```go
+func cgo.Example()
+```
+
+[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)
+-- funccgoexample-definition-json --
+{
+	"span": {
+		"uri": "file://cgo/declarecgo.go",
+		"start": {
+			"line": 17,
+			"column": 6,
+			"offset": 153
+		},
+		"end": {
+			"line": 17,
+			"column": 13,
+			"offset": 160
+		}
+	},
+	"description": "```go\nfunc cgo.Example()\n```\n\n[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)"
+}
+
+-- funccgoexample-hover --
+[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/cgo#Example)
+
+```go
+func cgo.Example()
+```
diff --git a/internal/lsp/testdata/cgoimport/usecgo.go.in b/internal/lsp/testdata/lsp/primarymod/cgoimport/usecgo.go.in
similarity index 100%
rename from internal/lsp/testdata/cgoimport/usecgo.go.in
rename to internal/lsp/testdata/lsp/primarymod/cgoimport/usecgo.go.in
diff --git a/internal/lsp/testdata/channel/channel.go b/internal/lsp/testdata/lsp/primarymod/channel/channel.go
similarity index 100%
rename from internal/lsp/testdata/channel/channel.go
rename to internal/lsp/testdata/lsp/primarymod/channel/channel.go
diff --git a/internal/lsp/testdata/lsp/primarymod/circular/double/b/b.go b/internal/lsp/testdata/lsp/primarymod/circular/double/b/b.go
new file mode 100644
index 0000000..1372522
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/circular/double/b/b.go
@@ -0,0 +1,5 @@
+package b
+
+import (
+	_ "golang.org/x/tools/internal/lsp/circular/double/one" //@diag("_ \"golang.org/x/tools/internal/lsp/circular/double/one\"", "compiler", "import cycle not allowed", "error"),diag("\"golang.org/x/tools/internal/lsp/circular/double/one\"", "compiler", "could not import golang.org/x/tools/internal/lsp/circular/double/one (no package for import golang.org/x/tools/internal/lsp/circular/double/one)", "error")
+)
diff --git a/internal/lsp/testdata/circular/double/one/one.go b/internal/lsp/testdata/lsp/primarymod/circular/double/one/one.go
similarity index 100%
rename from internal/lsp/testdata/circular/double/one/one.go
rename to internal/lsp/testdata/lsp/primarymod/circular/double/one/one.go
diff --git a/internal/lsp/testdata/lsp/primarymod/circular/self.go b/internal/lsp/testdata/lsp/primarymod/circular/self.go
new file mode 100644
index 0000000..5ec8b41
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/circular/self.go
@@ -0,0 +1,5 @@
+package circular
+
+import (
+	_ "golang.org/x/tools/internal/lsp/circular" //@diag("_ \"golang.org/x/tools/internal/lsp/circular\"", "compiler", "import cycle not allowed", "error"),diag("\"golang.org/x/tools/internal/lsp/circular\"", "compiler", "could not import golang.org/x/tools/internal/lsp/circular (no package for import golang.org/x/tools/internal/lsp/circular)", "error")
+)
diff --git a/internal/lsp/testdata/circular/triple/a/a.go b/internal/lsp/testdata/lsp/primarymod/circular/triple/a/a.go
similarity index 77%
rename from internal/lsp/testdata/circular/triple/a/a.go
rename to internal/lsp/testdata/lsp/primarymod/circular/triple/a/a.go
index 137c277..64b6c74 100644
--- a/internal/lsp/testdata/circular/triple/a/a.go
+++ b/internal/lsp/testdata/lsp/primarymod/circular/triple/a/a.go
@@ -1,5 +1,5 @@
 package a
 
 import (
-	_ "golang.org/x/tools/internal/lsp/circular/triple/b" //@diag("_ \"golang.org/x/tools/internal/lsp/circular/triple/b\"", "compiler", "import cycle not allowed")
+	_ "golang.org/x/tools/internal/lsp/circular/triple/b" //@diag("_ \"golang.org/x/tools/internal/lsp/circular/triple/b\"", "compiler", "import cycle not allowed", "error")
 )
diff --git a/internal/lsp/testdata/circular/triple/b/b.go b/internal/lsp/testdata/lsp/primarymod/circular/triple/b/b.go
similarity index 100%
rename from internal/lsp/testdata/circular/triple/b/b.go
rename to internal/lsp/testdata/lsp/primarymod/circular/triple/b/b.go
diff --git a/internal/lsp/testdata/circular/triple/c/c.go b/internal/lsp/testdata/lsp/primarymod/circular/triple/c/c.go
similarity index 100%
rename from internal/lsp/testdata/circular/triple/c/c.go
rename to internal/lsp/testdata/lsp/primarymod/circular/triple/c/c.go
diff --git a/internal/lsp/testdata/comments/comments.go b/internal/lsp/testdata/lsp/primarymod/comments/comments.go
similarity index 100%
rename from internal/lsp/testdata/comments/comments.go
rename to internal/lsp/testdata/lsp/primarymod/comments/comments.go
diff --git a/internal/lsp/testdata/complit/complit.go.in b/internal/lsp/testdata/lsp/primarymod/complit/complit.go.in
similarity index 100%
rename from internal/lsp/testdata/complit/complit.go.in
rename to internal/lsp/testdata/lsp/primarymod/complit/complit.go.in
diff --git a/internal/lsp/testdata/constant/constant.go b/internal/lsp/testdata/lsp/primarymod/constant/constant.go
similarity index 100%
rename from internal/lsp/testdata/constant/constant.go
rename to internal/lsp/testdata/lsp/primarymod/constant/constant.go
diff --git a/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_for.go b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_for.go
new file mode 100644
index 0000000..a16d3bd
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_for.go
@@ -0,0 +1,9 @@
+package danglingstmt
+
+func _() {
+	for bar //@rank(" //", danglingBar)
+}
+
+func bar() bool { //@item(danglingBar, "bar", "func() bool", "func")
+	return true
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_for_init.go b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_for_init.go
new file mode 100644
index 0000000..e1130bc
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_for_init.go
@@ -0,0 +1,9 @@
+package danglingstmt
+
+func _() {
+	for i := bar //@rank(" //", danglingBar2)
+}
+
+func bar2() int { //@item(danglingBar2, "bar2", "func() int", "func")
+	return 0
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_for_init_cond.go b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_for_init_cond.go
new file mode 100644
index 0000000..fb0269f
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_for_init_cond.go
@@ -0,0 +1,9 @@
+package danglingstmt
+
+func _() {
+	for i := bar3(); i > bar //@rank(" //", danglingBar3)
+}
+
+func bar3() int { //@item(danglingBar3, "bar3", "func() int", "func")
+	return 0
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_for_init_cond_post.go b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_for_init_cond_post.go
new file mode 100644
index 0000000..14f78d3
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_for_init_cond_post.go
@@ -0,0 +1,9 @@
+package danglingstmt
+
+func _() {
+	for i := bar4(); i > bar4(); i += bar //@rank(" //", danglingBar4)
+}
+
+func bar4() int { //@item(danglingBar4, "bar4", "func() int", "func")
+	return 0
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_if.go b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_if.go
new file mode 100644
index 0000000..91f145a
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_if.go
@@ -0,0 +1,9 @@
+package danglingstmt
+
+func _() {
+	if foo //@rank(" //", danglingFoo)
+}
+
+func foo() bool { //@item(danglingFoo, "foo", "func() bool", "func")
+	return true
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_if_eof.go b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_if_eof.go
new file mode 100644
index 0000000..3454c9f
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_if_eof.go
@@ -0,0 +1,8 @@
+package danglingstmt
+
+func bar5() bool { //@item(danglingBar5, "bar5", "func() bool", "func")
+	return true
+}
+
+func _() {
+	if b //@rank(" //", danglingBar5)
diff --git a/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_if_init.go b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_if_init.go
new file mode 100644
index 0000000..887c318
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_if_init.go
@@ -0,0 +1,9 @@
+package danglingstmt
+
+func _() {
+	if i := foo //@rank(" //", danglingFoo2)
+}
+
+func foo2() bool { //@item(danglingFoo2, "foo2", "func() bool", "func")
+	return true
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_if_init_cond.go b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_if_init_cond.go
new file mode 100644
index 0000000..5371283
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_if_init_cond.go
@@ -0,0 +1,9 @@
+package danglingstmt
+
+func _() {
+	if i := 123; foo //@rank(" //", danglingFoo3)
+}
+
+func foo3() bool { //@item(danglingFoo3, "foo3", "func() bool", "func")
+	return true
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_multiline_if.go b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_multiline_if.go
new file mode 100644
index 0000000..2213777
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_multiline_if.go
@@ -0,0 +1,10 @@
+package danglingstmt
+
+func walrus() bool { //@item(danglingWalrus, "walrus", "func() bool", "func")
+	return true
+}
+
+func _() {
+	if true &&
+		walrus //@complete(" //", danglingWalrus)
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_selector_1.go b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_selector_1.go
new file mode 100644
index 0000000..772152f
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_selector_1.go
@@ -0,0 +1,7 @@
+package danglingstmt
+
+func _() {
+	x. //@rank(" //", danglingI)
+}
+
+var x struct { i int } //@item(danglingI, "i", "int", "field")
diff --git a/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_selector_2.go b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_selector_2.go
new file mode 100644
index 0000000..a9e75e8
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_selector_2.go
@@ -0,0 +1,8 @@
+package danglingstmt
+
+import "golang.org/x/tools/internal/lsp/foo"
+
+func _() {
+	foo. //@rank(" //", Foo)
+	var _ = []string{foo.} //@rank("}", Foo)
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_switch_init.go b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_switch_init.go
new file mode 100644
index 0000000..15da3ce
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_switch_init.go
@@ -0,0 +1,9 @@
+package danglingstmt
+
+func _() {
+	switch i := baz //@rank(" //", danglingBaz)
+}
+
+func baz() int { //@item(danglingBaz, "baz", "func() int", "func")
+	return 0
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_switch_init_tag.go b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_switch_init_tag.go
new file mode 100644
index 0000000..20b825b
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/danglingstmt/dangling_switch_init_tag.go
@@ -0,0 +1,9 @@
+package danglingstmt
+
+func _() {
+	switch i := 0; baz //@rank(" //", danglingBaz2)
+}
+
+func baz2() int { //@item(danglingBaz2, "baz2", "func() int", "func")
+	return 0
+}
diff --git a/internal/lsp/testdata/deep/deep.go b/internal/lsp/testdata/lsp/primarymod/deep/deep.go
similarity index 100%
rename from internal/lsp/testdata/deep/deep.go
rename to internal/lsp/testdata/lsp/primarymod/deep/deep.go
diff --git a/internal/lsp/testdata/errors/errors.go b/internal/lsp/testdata/lsp/primarymod/errors/errors.go
similarity index 100%
rename from internal/lsp/testdata/errors/errors.go
rename to internal/lsp/testdata/lsp/primarymod/errors/errors.go
diff --git a/internal/lsp/testdata/lsp/primarymod/fieldlist/field_list.go b/internal/lsp/testdata/lsp/primarymod/fieldlist/field_list.go
new file mode 100644
index 0000000..e687def
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/fieldlist/field_list.go
@@ -0,0 +1,27 @@
+package fieldlist
+
+var myInt int   //@item(flVar, "myInt", "int", "var")
+type myType int //@item(flType, "myType", "int", "type")
+
+func (my) _()    {} //@complete(") _", flType)
+func (my my) _() {} //@complete(" my)"),complete(") _", flType)
+
+func (myType) _() {} //@complete(") {", flType)
+
+func (myType) _(my my) {} //@complete(" my)"),complete(") {", flType)
+
+func (myType) _() my {} //@complete(" {", flType)
+
+func (myType) _() (my my) {} //@complete(" my"),complete(") {", flType)
+
+func _() {
+	var _ struct {
+		//@complete("", flType)
+		m my //@complete(" my"),complete(" //", flType)
+	}
+
+	var _ interface {
+		//@complete("", flType)
+		m() my //@complete("("),complete(" //", flType)
+	}
+}
diff --git a/internal/lsp/testdata/folding/a.go b/internal/lsp/testdata/lsp/primarymod/folding/a.go
similarity index 100%
rename from internal/lsp/testdata/folding/a.go
rename to internal/lsp/testdata/lsp/primarymod/folding/a.go
diff --git a/internal/lsp/testdata/folding/a.go.golden b/internal/lsp/testdata/lsp/primarymod/folding/a.go.golden
similarity index 100%
rename from internal/lsp/testdata/folding/a.go.golden
rename to internal/lsp/testdata/lsp/primarymod/folding/a.go.golden
diff --git a/internal/lsp/testdata/folding/bad.go.golden b/internal/lsp/testdata/lsp/primarymod/folding/bad.go.golden
similarity index 100%
rename from internal/lsp/testdata/folding/bad.go.golden
rename to internal/lsp/testdata/lsp/primarymod/folding/bad.go.golden
diff --git a/internal/lsp/testdata/folding/bad.go.in b/internal/lsp/testdata/lsp/primarymod/folding/bad.go.in
similarity index 100%
rename from internal/lsp/testdata/folding/bad.go.in
rename to internal/lsp/testdata/lsp/primarymod/folding/bad.go.in
diff --git a/internal/lsp/testdata/foo/foo.go b/internal/lsp/testdata/lsp/primarymod/foo/foo.go
similarity index 90%
rename from internal/lsp/testdata/foo/foo.go
rename to internal/lsp/testdata/lsp/primarymod/foo/foo.go
index 277ac53..20ea183 100644
--- a/internal/lsp/testdata/foo/foo.go
+++ b/internal/lsp/testdata/lsp/primarymod/foo/foo.go
@@ -27,4 +27,4 @@
 	}
 }
 
-type IntFoo int //@item(IntFoo, "IntFoo", "int", "type"),complete("", Foo, IntFoo, StructFoo)
+type IntFoo int //@item(IntFoo, "IntFoo", "int", "type")
diff --git a/internal/lsp/testdata/format/bad_format.go.golden b/internal/lsp/testdata/lsp/primarymod/format/bad_format.go.golden
similarity index 70%
rename from internal/lsp/testdata/format/bad_format.go.golden
rename to internal/lsp/testdata/lsp/primarymod/format/bad_format.go.golden
index d3a4059..c2ac5a1 100644
--- a/internal/lsp/testdata/format/bad_format.go.golden
+++ b/internal/lsp/testdata/lsp/primarymod/format/bad_format.go.golden
@@ -9,7 +9,7 @@
 
 func hello() {
 
-	var x int //@diag("x", "compiler", "x declared but not used")
+	var x int //@diag("x", "compiler", "x declared but not used", "error")
 }
 
 func hi() {
diff --git a/internal/lsp/testdata/format/bad_format.go.in b/internal/lsp/testdata/lsp/primarymod/format/bad_format.go.in
similarity index 69%
rename from internal/lsp/testdata/format/bad_format.go.in
rename to internal/lsp/testdata/lsp/primarymod/format/bad_format.go.in
index a2da140..0618723 100644
--- a/internal/lsp/testdata/format/bad_format.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/format/bad_format.go.in
@@ -11,7 +11,7 @@
 
 
 
-	var x int //@diag("x", "compiler", "x declared but not used")
+	var x int //@diag("x", "compiler", "x declared but not used", "error")
 }
 
 func hi() {
diff --git a/internal/lsp/testdata/format/good_format.go b/internal/lsp/testdata/lsp/primarymod/format/good_format.go
similarity index 100%
rename from internal/lsp/testdata/format/good_format.go
rename to internal/lsp/testdata/lsp/primarymod/format/good_format.go
diff --git a/internal/lsp/testdata/format/good_format.go.golden b/internal/lsp/testdata/lsp/primarymod/format/good_format.go.golden
similarity index 100%
rename from internal/lsp/testdata/format/good_format.go.golden
rename to internal/lsp/testdata/lsp/primarymod/format/good_format.go.golden
diff --git a/internal/lsp/testdata/format/newline_format.go.golden b/internal/lsp/testdata/lsp/primarymod/format/newline_format.go.golden
similarity index 100%
rename from internal/lsp/testdata/format/newline_format.go.golden
rename to internal/lsp/testdata/lsp/primarymod/format/newline_format.go.golden
diff --git a/internal/lsp/testdata/format/newline_format.go.in b/internal/lsp/testdata/lsp/primarymod/format/newline_format.go.in
similarity index 100%
rename from internal/lsp/testdata/format/newline_format.go.in
rename to internal/lsp/testdata/lsp/primarymod/format/newline_format.go.in
diff --git a/internal/lsp/testdata/format/one_line.go.golden b/internal/lsp/testdata/lsp/primarymod/format/one_line.go.golden
similarity index 100%
rename from internal/lsp/testdata/format/one_line.go.golden
rename to internal/lsp/testdata/lsp/primarymod/format/one_line.go.golden
diff --git a/internal/lsp/testdata/format/one_line.go.in b/internal/lsp/testdata/lsp/primarymod/format/one_line.go.in
similarity index 100%
rename from internal/lsp/testdata/format/one_line.go.in
rename to internal/lsp/testdata/lsp/primarymod/format/one_line.go.in
diff --git a/internal/lsp/testdata/func_rank/func_rank.go.in b/internal/lsp/testdata/lsp/primarymod/func_rank/func_rank.go.in
similarity index 85%
rename from internal/lsp/testdata/func_rank/func_rank.go.in
rename to internal/lsp/testdata/lsp/primarymod/func_rank/func_rank.go.in
index 61ad6e9..f9cc6a1 100644
--- a/internal/lsp/testdata/func_rank/func_rank.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/func_rank/func_rank.go.in
@@ -4,11 +4,11 @@
 func stringBFunc() string { return "str" } //@item(stringBFunc, "stringBFunc", "func() string", "func")
 type stringer struct{}    //@item(stringer, "stringer", "struct{...}", "struct")
 
-func _() stringer //@complete("tr", stringer, stringAVar, stringBFunc)
+func _() stringer //@complete("tr", stringer)
 
-func _(val stringer) {} //@complete("tr", stringer, stringAVar, stringBFunc)
+func _(val stringer) {} //@complete("tr", stringer)
 
-func (stringer) _() {} //@complete("tr", stringer, stringAVar, stringBFunc)
+func (stringer) _() {} //@complete("tr", stringer)
 
 func _() {
 	var s struct {
diff --git a/internal/lsp/testdata/funcsig/func_sig.go b/internal/lsp/testdata/lsp/primarymod/funcsig/func_sig.go
similarity index 100%
rename from internal/lsp/testdata/funcsig/func_sig.go
rename to internal/lsp/testdata/lsp/primarymod/funcsig/func_sig.go
diff --git a/internal/lsp/testdata/funcvalue/func_value.go b/internal/lsp/testdata/lsp/primarymod/funcvalue/func_value.go
similarity index 100%
rename from internal/lsp/testdata/funcvalue/func_value.go
rename to internal/lsp/testdata/lsp/primarymod/funcvalue/func_value.go
diff --git a/internal/lsp/testdata/fuzzymatch/fuzzymatch.go b/internal/lsp/testdata/lsp/primarymod/fuzzymatch/fuzzymatch.go
similarity index 100%
rename from internal/lsp/testdata/fuzzymatch/fuzzymatch.go
rename to internal/lsp/testdata/lsp/primarymod/fuzzymatch/fuzzymatch.go
diff --git a/internal/lsp/testdata/lsp/primarymod/generated/generated.go b/internal/lsp/testdata/lsp/primarymod/generated/generated.go
new file mode 100644
index 0000000..c92bd9e
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/generated/generated.go
@@ -0,0 +1,7 @@
+package generated
+
+// Code generated by generator.go. DO NOT EDIT.
+
+func _() {
+	var y int //@diag("y", "compiler", "y declared but not used", "error")
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/generated/generator.go b/internal/lsp/testdata/lsp/primarymod/generated/generator.go
new file mode 100644
index 0000000..f26e33c
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/generated/generator.go
@@ -0,0 +1,5 @@
+package generated
+
+func _() {
+	var x int //@diag("x", "compiler", "x declared but not used", "error")
+}
diff --git a/internal/lsp/testdata/godef/a/a.go b/internal/lsp/testdata/lsp/primarymod/godef/a/a.go
similarity index 93%
rename from internal/lsp/testdata/godef/a/a.go
rename to internal/lsp/testdata/lsp/primarymod/godef/a/a.go
index 39eb397..d7df63d 100644
--- a/internal/lsp/testdata/godef/a/a.go
+++ b/internal/lsp/testdata/lsp/primarymod/godef/a/a.go
@@ -13,7 +13,7 @@
 	x string //@x,hover("x", x)
 )
 
-type A string //@A
+type A string //@mark(AString, "A")
 
 func AStuff() { //@AStuff
 	x := 5
diff --git a/internal/lsp/testdata/godef/a/a.go.golden b/internal/lsp/testdata/lsp/primarymod/godef/a/a.go.golden
similarity index 74%
rename from internal/lsp/testdata/godef/a/a.go.golden
rename to internal/lsp/testdata/lsp/primarymod/godef/a/a.go.golden
index 70d0c42..f8a398a 100644
--- a/internal/lsp/testdata/godef/a/a.go.golden
+++ b/internal/lsp/testdata/lsp/primarymod/godef/a/a.go.golden
@@ -15,11 +15,11 @@
 func (*types.object).Name() string
 ```
 -- Random-definition --
-godef/a/random.go:3:6-12: defined here as [`a.Random` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random)
-
-```go
+godef/a/random.go:3:6-12: defined here as ```go
 func Random() int
 ```
+
+[`a.Random` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random)
 -- Random-definition-json --
 {
 	"span": {
@@ -35,7 +35,7 @@
 			"offset": 22
 		}
 	},
-	"description": "[`a.Random` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random)\n\n```go\nfunc Random() int\n```"
+	"description": "```go\nfunc Random() int\n```\n\n[`a.Random` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random)"
 }
 
 -- Random-hover --
@@ -45,11 +45,11 @@
 func Random() int
 ```
 -- Random2-definition --
-godef/a/random.go:8:6-13: defined here as [`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random2)
-
-```go
+godef/a/random.go:8:6-13: defined here as ```go
 func Random2(y int) int
 ```
+
+[`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random2)
 -- Random2-definition-json --
 {
 	"span": {
@@ -65,7 +65,7 @@
 			"offset": 78
 		}
 	},
-	"description": "[`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random2)\n\n```go\nfunc Random2(y int) int\n```"
+	"description": "```go\nfunc Random2(y int) int\n```\n\n[`a.Random2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Random2)"
 }
 
 -- Random2-hover --
@@ -85,12 +85,12 @@
 		"start": {
 			"line": 23,
 			"column": 6,
-			"offset": 287
+			"offset": 304
 		},
 		"end": {
 			"line": 23,
 			"column": 9,
-			"offset": 290
+			"offset": 307
 		}
 	},
 	"description": "```go\nvar err error\n```"
diff --git a/internal/lsp/testdata/lsp/primarymod/godef/a/a_test.go b/internal/lsp/testdata/lsp/primarymod/godef/a/a_test.go
new file mode 100644
index 0000000..77bd633
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/godef/a/a_test.go
@@ -0,0 +1,8 @@
+package a
+
+import (
+	"testing"
+)
+
+func TestA(t *testing.T) { //@TestA,godef(TestA, TestA)
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/godef/a/a_test.go.golden b/internal/lsp/testdata/lsp/primarymod/godef/a/a_test.go.golden
new file mode 100644
index 0000000..ac50b90
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/godef/a/a_test.go.golden
@@ -0,0 +1,26 @@
+-- TestA-definition --
+godef/a/a_test.go:7:6-11: defined here as ```go
+func TestA(t *testing.T)
+```
+-- TestA-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/a_test.go",
+		"start": {
+			"line": 7,
+			"column": 6,
+			"offset": 39
+		},
+		"end": {
+			"line": 7,
+			"column": 11,
+			"offset": 44
+		}
+	},
+	"description": "```go\nfunc TestA(t *testing.T)\n```"
+}
+
+-- TestA-hover --
+```go
+func TestA(t *testing.T)
+```
diff --git a/internal/lsp/testdata/lsp/primarymod/godef/a/a_x_test.go b/internal/lsp/testdata/lsp/primarymod/godef/a/a_x_test.go
new file mode 100644
index 0000000..85f21cc
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/godef/a/a_x_test.go
@@ -0,0 +1,8 @@
+package a_test
+
+import (
+	"testing"
+)
+
+func TestA2(t *testing.T) { //@TestA2,godef(TestA2, TestA2)
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/godef/a/a_x_test.go.golden b/internal/lsp/testdata/lsp/primarymod/godef/a/a_x_test.go.golden
new file mode 100644
index 0000000..dd1d740
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/godef/a/a_x_test.go.golden
@@ -0,0 +1,26 @@
+-- TestA2-definition --
+godef/a/a_x_test.go:7:6-12: defined here as ```go
+func TestA2(t *testing.T)
+```
+-- TestA2-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/a_x_test.go",
+		"start": {
+			"line": 7,
+			"column": 6,
+			"offset": 44
+		},
+		"end": {
+			"line": 7,
+			"column": 12,
+			"offset": 50
+		}
+	},
+	"description": "```go\nfunc TestA2(t *testing.T)\n```"
+}
+
+-- TestA2-hover --
+```go
+func TestA2(t *testing.T)
+```
diff --git a/internal/lsp/testdata/godef/a/d.go b/internal/lsp/testdata/lsp/primarymod/godef/a/d.go
similarity index 100%
rename from internal/lsp/testdata/godef/a/d.go
rename to internal/lsp/testdata/lsp/primarymod/godef/a/d.go
diff --git a/internal/lsp/testdata/godef/a/d.go.golden b/internal/lsp/testdata/lsp/primarymod/godef/a/d.go.golden
similarity index 62%
rename from internal/lsp/testdata/godef/a/d.go.golden
rename to internal/lsp/testdata/lsp/primarymod/godef/a/d.go.golden
index 7d2d244..771f98a 100644
--- a/internal/lsp/testdata/godef/a/d.go.golden
+++ b/internal/lsp/testdata/lsp/primarymod/godef/a/d.go.golden
@@ -1,11 +1,11 @@
 -- Member-definition --
-godef/a/d.go:6:2-8: defined here as \@Member
+godef/a/d.go:6:2-8: defined here as ```go
+field Member string
+```
 
 [`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Member)
 
-```go
-field Member string
-```
+\@Member
 -- Member-definition-json --
 {
 	"span": {
@@ -21,7 +21,7 @@
 			"offset": 61
 		}
 	},
-	"description": "\\@Member\n\n[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Member)\n\n```go\nfield Member string\n```"
+	"description": "```go\nfield Member string\n```\n\n[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Member)\n\n\\@Member"
 }
 
 -- Member-hover --
@@ -33,11 +33,11 @@
 field Member string
 ```
 -- Method-definition --
-godef/a/d.go:15:16-22: defined here as [`(a.Thing).Method` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Method)
-
-```go
+godef/a/d.go:15:16-22: defined here as ```go
 func (Thing).Method(i int) string
 ```
+
+[`(a.Thing).Method` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Method)
 -- Method-definition-json --
 {
 	"span": {
@@ -53,7 +53,7 @@
 			"offset": 190
 		}
 	},
-	"description": "[`(a.Thing).Method` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Method)\n\n```go\nfunc (Thing).Method(i int) string\n```"
+	"description": "```go\nfunc (Thing).Method(i int) string\n```\n\n[`(a.Thing).Method` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Method)"
 }
 
 -- Method-hover --
@@ -63,11 +63,11 @@
 func (Thing).Method(i int) string
 ```
 -- Other-definition --
-godef/a/d.go:9:5-10: defined here as [`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
-
-```go
+godef/a/d.go:9:5-10: defined here as ```go
 var Other Thing
 ```
+
+[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
 -- Other-definition-json --
 {
 	"span": {
@@ -83,7 +83,7 @@
 			"offset": 91
 		}
 	},
-	"description": "[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)\n\n```go\nvar Other Thing\n```"
+	"description": "```go\nvar Other Thing\n```\n\n[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)"
 }
 
 -- Other-hover --
@@ -93,13 +93,13 @@
 var Other Thing
 ```
 -- Thing-definition --
-godef/a/d.go:5:6-11: defined here as [`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
-
-```go
+godef/a/d.go:5:6-11: defined here as ```go
 Thing struct {
 	Member string //@Member
 }
 ```
+
+[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
 -- Thing-definition-json --
 {
 	"span": {
@@ -115,7 +115,7 @@
 			"offset": 35
 		}
 	},
-	"description": "[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)\n\n```go\nThing struct {\n\tMember string //@Member\n}\n```"
+	"description": "```go\nThing struct {\n\tMember string //@Member\n}\n```\n\n[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)"
 }
 
 -- Thing-hover --
@@ -127,11 +127,11 @@
 }
 ```
 -- Things-definition --
-godef/a/d.go:11:6-12: defined here as [`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
-
-```go
+godef/a/d.go:11:6-12: defined here as ```go
 func Things(val []string) []Thing
 ```
+
+[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
 -- Things-definition-json --
 {
 	"span": {
@@ -147,7 +147,7 @@
 			"offset": 119
 		}
 	},
-	"description": "[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)\n\n```go\nfunc Things(val []string) []Thing\n```"
+	"description": "```go\nfunc Things(val []string) []Thing\n```\n\n[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)"
 }
 
 -- Things-hover --
diff --git a/internal/lsp/testdata/godef/a/f.go b/internal/lsp/testdata/lsp/primarymod/godef/a/f.go
similarity index 100%
rename from internal/lsp/testdata/godef/a/f.go
rename to internal/lsp/testdata/lsp/primarymod/godef/a/f.go
diff --git a/internal/lsp/testdata/godef/a/f.go.golden b/internal/lsp/testdata/lsp/primarymod/godef/a/f.go.golden
similarity index 100%
rename from internal/lsp/testdata/godef/a/f.go.golden
rename to internal/lsp/testdata/lsp/primarymod/godef/a/f.go.golden
diff --git a/internal/lsp/testdata/godef/a/random.go b/internal/lsp/testdata/lsp/primarymod/godef/a/random.go
similarity index 100%
rename from internal/lsp/testdata/godef/a/random.go
rename to internal/lsp/testdata/lsp/primarymod/godef/a/random.go
diff --git a/internal/lsp/testdata/godef/a/random.go.golden b/internal/lsp/testdata/lsp/primarymod/godef/a/random.go.golden
similarity index 70%
rename from internal/lsp/testdata/godef/a/random.go.golden
rename to internal/lsp/testdata/lsp/primarymod/godef/a/random.go.golden
index 32d7403..ef10f61 100644
--- a/internal/lsp/testdata/godef/a/random.go.golden
+++ b/internal/lsp/testdata/lsp/primarymod/godef/a/random.go.golden
@@ -1,9 +1,9 @@
 -- PosSum-definition --
-godef/a/random.go:16:15-18: defined here as [`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Pos.Sum)
-
-```go
+godef/a/random.go:16:15-18: defined here as ```go
 func (*Pos).Sum() int
 ```
+
+[`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Pos.Sum)
 -- PosSum-definition-json --
 {
 	"span": {
@@ -19,7 +19,7 @@
 			"offset": 251
 		}
 	},
-	"description": "[`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Pos.Sum)\n\n```go\nfunc (*Pos).Sum() int\n```"
+	"description": "```go\nfunc (*Pos).Sum() int\n```\n\n[`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Pos.Sum)"
 }
 
 -- PosSum-hover --
@@ -29,11 +29,11 @@
 func (*Pos).Sum() int
 ```
 -- PosX-definition --
-godef/a/random.go:13:2-3: defined here as \@mark\(PosX, \"x\"\),mark\(PosY, \"y\"\)
-
-```go
+godef/a/random.go:13:2-3: defined here as ```go
 field x int
 ```
+
+\@mark\(PosX, \"x\"\),mark\(PosY, \"y\"\)
 -- PosX-definition-json --
 {
 	"span": {
@@ -49,7 +49,7 @@
 			"offset": 188
 		}
 	},
-	"description": "\\@mark\\(PosX, \\\"x\\\"\\),mark\\(PosY, \\\"y\\\"\\)\n\n```go\nfield x int\n```"
+	"description": "```go\nfield x int\n```\n\n\\@mark\\(PosX, \\\"x\\\"\\),mark\\(PosY, \\\"y\\\"\\)"
 }
 
 -- PosX-hover --
diff --git a/internal/lsp/testdata/godef/b/b.go b/internal/lsp/testdata/lsp/primarymod/godef/b/b.go
similarity index 81%
rename from internal/lsp/testdata/godef/b/b.go
rename to internal/lsp/testdata/lsp/primarymod/godef/b/b.go
index ee3d0f0..ca6c957 100644
--- a/internal/lsp/testdata/godef/b/b.go
+++ b/internal/lsp/testdata/lsp/primarymod/godef/b/b.go
@@ -8,18 +8,18 @@
 type S1 struct { //@S1
 	F1  int //@mark(S1F1, "F1")
 	S2      //@godef("S2", S2), mark(S1S2, "S2")
-	a.A     //@godef("A", A)
+	a.A     //@godef("A", AString)
 }
 
 type S2 struct { //@S2
 	F1   string //@mark(S2F1, "F1")
 	F2   int    //@mark(S2F2, "F2")
-	*a.A        //@godef("A", A),godef("a",AImport)
+	*a.A        //@godef("A", AString),godef("a",AImport)
 }
 
 type S3 struct {
 	F1 struct {
-		a.A //@godef("A", A)
+		a.A //@godef("A", AString)
 	}
 }
 
@@ -34,4 +34,4 @@
 	var _ *myFoo.StructFoo //@godef("myFoo", myFoo)
 }
 
-const X = 0 //@mark(X, "X"),godef("X", X)
+const X = 0 //@mark(bX, "X"),godef("X", bX)
diff --git a/internal/lsp/testdata/lsp/primarymod/godef/b/b.go.golden b/internal/lsp/testdata/lsp/primarymod/godef/b/b.go.golden
new file mode 100644
index 0000000..8d00791
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/godef/b/b.go.golden
@@ -0,0 +1,364 @@
+-- AImport-definition --
+godef/b/b.go:5:2-43: defined here as ```go
+package a ("golang.org/x/tools/internal/lsp/godef/a")
+```
+
+[`a` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a)
+-- AImport-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 5,
+			"column": 2,
+			"offset": 112
+		},
+		"end": {
+			"line": 5,
+			"column": 43,
+			"offset": 153
+		}
+	},
+	"description": "```go\npackage a (\"golang.org/x/tools/internal/lsp/godef/a\")\n```\n\n[`a` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a)"
+}
+
+-- AImport-hover --
+[`a` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a)
+
+```go
+package a ("golang.org/x/tools/internal/lsp/godef/a")
+```
+-- AString-definition --
+godef/a/a.go:16:6-7: defined here as ```go
+A string //@mark(AString, "A")
+
+```
+
+[`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)
+-- AString-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/a.go",
+		"start": {
+			"line": 16,
+			"column": 6,
+			"offset": 159
+		},
+		"end": {
+			"line": 16,
+			"column": 7,
+			"offset": 160
+		}
+	},
+	"description": "```go\nA string //@mark(AString, \"A\")\n\n```\n\n[`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)"
+}
+
+-- AString-hover --
+[`a.A` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#A)
+
+```go
+A string //@mark(AString, "A")
+
+```
+-- AStuff-definition --
+godef/a/a.go:18:6-12: defined here as ```go
+func a.AStuff()
+```
+
+[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)
+-- AStuff-definition-json --
+{
+	"span": {
+		"uri": "file://godef/a/a.go",
+		"start": {
+			"line": 18,
+			"column": 6,
+			"offset": 196
+		},
+		"end": {
+			"line": 18,
+			"column": 12,
+			"offset": 202
+		}
+	},
+	"description": "```go\nfunc a.AStuff()\n```\n\n[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)"
+}
+
+-- AStuff-hover --
+[`a.AStuff` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#AStuff)
+
+```go
+func a.AStuff()
+```
+-- S1-definition --
+godef/b/b.go:8:6-8: defined here as ```go
+S1 struct {
+	F1  int //@mark(S1F1, "F1")
+	S2      //@godef("S2", S2), mark(S1S2, "S2")
+	a.A     //@godef("A", AString)
+}
+```
+
+[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
+-- S1-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 8,
+			"column": 6,
+			"offset": 193
+		},
+		"end": {
+			"line": 8,
+			"column": 8,
+			"offset": 195
+		}
+	},
+	"description": "```go\nS1 struct {\n\tF1  int //@mark(S1F1, \"F1\")\n\tS2      //@godef(\"S2\", S2), mark(S1S2, \"S2\")\n\ta.A     //@godef(\"A\", AString)\n}\n```\n\n[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)"
+}
+
+-- S1-hover --
+[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
+
+```go
+S1 struct {
+	F1  int //@mark(S1F1, "F1")
+	S2      //@godef("S2", S2), mark(S1S2, "S2")
+	a.A     //@godef("A", AString)
+}
+```
+-- S1F1-definition --
+godef/b/b.go:9:2-4: defined here as ```go
+field F1 int
+```
+
+[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)
+
+\@mark\(S1F1, \"F1\"\)
+-- S1F1-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 9,
+			"column": 2,
+			"offset": 212
+		},
+		"end": {
+			"line": 9,
+			"column": 4,
+			"offset": 214
+		}
+	},
+	"description": "```go\nfield F1 int\n```\n\n[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)\n\n\\@mark\\(S1F1, \\\"F1\\\"\\)"
+}
+
+-- S1F1-hover --
+\@mark\(S1F1, \"F1\"\)
+
+[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)
+
+```go
+field F1 int
+```
+-- S1S2-definition --
+godef/b/b.go:10:2-4: defined here as ```go
+field S2 S2
+```
+
+[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.S2)
+
+\@godef\(\"S2\", S2\), mark\(S1S2, \"S2\"\)
+-- S1S2-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 10,
+			"column": 2,
+			"offset": 241
+		},
+		"end": {
+			"line": 10,
+			"column": 4,
+			"offset": 243
+		}
+	},
+	"description": "```go\nfield S2 S2\n```\n\n[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.S2)\n\n\\@godef\\(\\\"S2\\\", S2\\), mark\\(S1S2, \\\"S2\\\"\\)"
+}
+
+-- S1S2-hover --
+\@godef\(\"S2\", S2\), mark\(S1S2, \"S2\"\)
+
+[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.S2)
+
+```go
+field S2 S2
+```
+-- S2-definition --
+godef/b/b.go:14:6-8: defined here as ```go
+S2 struct {
+	F1   string //@mark(S2F1, "F1")
+	F2   int    //@mark(S2F2, "F2")
+	*a.A        //@godef("A", AString),godef("a",AImport)
+}
+```
+
+[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)
+-- S2-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 14,
+			"column": 6,
+			"offset": 326
+		},
+		"end": {
+			"line": 14,
+			"column": 8,
+			"offset": 328
+		}
+	},
+	"description": "```go\nS2 struct {\n\tF1   string //@mark(S2F1, \"F1\")\n\tF2   int    //@mark(S2F2, \"F2\")\n\t*a.A        //@godef(\"A\", AString),godef(\"a\",AImport)\n}\n```\n\n[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)"
+}
+
+-- S2-hover --
+[`b.S2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2)
+
+```go
+S2 struct {
+	F1   string //@mark(S2F1, "F1")
+	F2   int    //@mark(S2F2, "F2")
+	*a.A        //@godef("A", AString),godef("a",AImport)
+}
+```
+-- S2F1-definition --
+godef/b/b.go:15:2-4: defined here as ```go
+field F1 string
+```
+
+[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2.F1)
+
+\@mark\(S2F1, \"F1\"\)
+-- S2F1-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 15,
+			"column": 2,
+			"offset": 345
+		},
+		"end": {
+			"line": 15,
+			"column": 4,
+			"offset": 347
+		}
+	},
+	"description": "```go\nfield F1 string\n```\n\n[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2.F1)\n\n\\@mark\\(S2F1, \\\"F1\\\"\\)"
+}
+
+-- S2F1-hover --
+\@mark\(S2F1, \"F1\"\)
+
+[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S2.F1)
+
+```go
+field F1 string
+```
+-- S2F2-definition --
+godef/b/b.go:16:2-4: defined here as ```go
+field F2 int
+```
+
+[`(b.S1).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F2)
+
+\@mark\(S2F2, \"F2\"\)
+-- S2F2-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 16,
+			"column": 2,
+			"offset": 378
+		},
+		"end": {
+			"line": 16,
+			"column": 4,
+			"offset": 380
+		}
+	},
+	"description": "```go\nfield F2 int\n```\n\n[`(b.S1).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F2)\n\n\\@mark\\(S2F2, \\\"F2\\\"\\)"
+}
+
+-- S2F2-hover --
+\@mark\(S2F2, \"F2\"\)
+
+[`(b.S1).F2` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F2)
+
+```go
+field F2 int
+```
+-- bX-definition --
+godef/b/b.go:37:7-8: defined here as ```go
+const X untyped int = 0
+```
+
+[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)
+-- bX-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 37,
+			"column": 7,
+			"offset": 813
+		},
+		"end": {
+			"line": 37,
+			"column": 8,
+			"offset": 814
+		}
+	},
+	"description": "```go\nconst X untyped int = 0\n```\n\n[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)"
+}
+
+-- bX-hover --
+[`b.X` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#X)
+
+```go
+const X untyped int = 0
+```
+-- myFoo-definition --
+godef/b/b.go:4:2-7: defined here as ```go
+package myFoo ("golang.org/x/tools/internal/lsp/foo")
+```
+
+[`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/foo)
+-- myFoo-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 4,
+			"column": 2,
+			"offset": 21
+		},
+		"end": {
+			"line": 4,
+			"column": 7,
+			"offset": 26
+		}
+	},
+	"description": "```go\npackage myFoo (\"golang.org/x/tools/internal/lsp/foo\")\n```\n\n[`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/foo)"
+}
+
+-- myFoo-hover --
+[`myFoo` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/foo)
+
+```go
+package myFoo ("golang.org/x/tools/internal/lsp/foo")
+```
diff --git a/internal/lsp/testdata/godef/b/c.go b/internal/lsp/testdata/lsp/primarymod/godef/b/c.go
similarity index 100%
rename from internal/lsp/testdata/godef/b/c.go
rename to internal/lsp/testdata/lsp/primarymod/godef/b/c.go
diff --git a/internal/lsp/testdata/lsp/primarymod/godef/b/c.go.golden b/internal/lsp/testdata/lsp/primarymod/godef/b/c.go.golden
new file mode 100644
index 0000000..7711d29
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/godef/b/c.go.golden
@@ -0,0 +1,72 @@
+-- S1-definition --
+godef/b/b.go:8:6-8: defined here as ```go
+S1 struct {
+	F1  int //@mark(S1F1, "F1")
+	S2      //@godef("S2", S2), mark(S1S2, "S2")
+	a.A     //@godef("A", AString)
+}
+```
+
+[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
+-- S1-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 8,
+			"column": 6,
+			"offset": 193
+		},
+		"end": {
+			"line": 8,
+			"column": 8,
+			"offset": 195
+		}
+	},
+	"description": "```go\nS1 struct {\n\tF1  int //@mark(S1F1, \"F1\")\n\tS2      //@godef(\"S2\", S2), mark(S1S2, \"S2\")\n\ta.A     //@godef(\"A\", AString)\n}\n```\n\n[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)"
+}
+
+-- S1-hover --
+[`b.S1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1)
+
+```go
+S1 struct {
+	F1  int //@mark(S1F1, "F1")
+	S2      //@godef("S2", S2), mark(S1S2, "S2")
+	a.A     //@godef("A", AString)
+}
+```
+-- S1F1-definition --
+godef/b/b.go:9:2-4: defined here as ```go
+field F1 int
+```
+
+[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)
+
+\@mark\(S1F1, \"F1\"\)
+-- S1F1-definition-json --
+{
+	"span": {
+		"uri": "file://godef/b/b.go",
+		"start": {
+			"line": 9,
+			"column": 2,
+			"offset": 212
+		},
+		"end": {
+			"line": 9,
+			"column": 4,
+			"offset": 214
+		}
+	},
+	"description": "```go\nfield F1 int\n```\n\n[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)\n\n\\@mark\\(S1F1, \\\"F1\\\"\\)"
+}
+
+-- S1F1-hover --
+\@mark\(S1F1, \"F1\"\)
+
+[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/b#S1.F1)
+
+```go
+field F1 int
+```
diff --git a/internal/lsp/testdata/godef/b/c.go.saved b/internal/lsp/testdata/lsp/primarymod/godef/b/c.go.saved
similarity index 100%
rename from internal/lsp/testdata/godef/b/c.go.saved
rename to internal/lsp/testdata/lsp/primarymod/godef/b/c.go.saved
diff --git a/internal/lsp/testdata/godef/b/e.go b/internal/lsp/testdata/lsp/primarymod/godef/b/e.go
similarity index 100%
rename from internal/lsp/testdata/godef/b/e.go
rename to internal/lsp/testdata/lsp/primarymod/godef/b/e.go
diff --git a/internal/lsp/testdata/godef/b/e.go.golden b/internal/lsp/testdata/lsp/primarymod/godef/b/e.go.golden
similarity index 61%
rename from internal/lsp/testdata/godef/b/e.go.golden
rename to internal/lsp/testdata/lsp/primarymod/godef/b/e.go.golden
index df38081..24026cb 100644
--- a/internal/lsp/testdata/godef/b/e.go.golden
+++ b/internal/lsp/testdata/lsp/primarymod/godef/b/e.go.golden
@@ -1,11 +1,11 @@
 -- Member-definition --
-godef/a/d.go:6:2-8: defined here as \@Member
+godef/a/d.go:6:2-8: defined here as ```go
+field Member string
+```
 
 [`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Member)
 
-```go
-field Member string
-```
+\@Member
 -- Member-definition-json --
 {
 	"span": {
@@ -21,7 +21,7 @@
 			"offset": 61
 		}
 	},
-	"description": "\\@Member\n\n[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Member)\n\n```go\nfield Member string\n```"
+	"description": "```go\nfield Member string\n```\n\n[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing.Member)\n\n\\@Member"
 }
 
 -- Member-hover --
@@ -33,11 +33,11 @@
 field Member string
 ```
 -- Other-definition --
-godef/a/d.go:9:5-10: defined here as [`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
-
-```go
+godef/a/d.go:9:5-10: defined here as ```go
 var a.Other a.Thing
 ```
+
+[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)
 -- Other-definition-json --
 {
 	"span": {
@@ -53,7 +53,7 @@
 			"offset": 91
 		}
 	},
-	"description": "[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)\n\n```go\nvar a.Other a.Thing\n```"
+	"description": "```go\nvar a.Other a.Thing\n```\n\n[`a.Other` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Other)"
 }
 
 -- Other-hover --
@@ -63,13 +63,13 @@
 var a.Other a.Thing
 ```
 -- Thing-definition --
-godef/a/d.go:5:6-11: defined here as [`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
-
-```go
+godef/a/d.go:5:6-11: defined here as ```go
 Thing struct {
 	Member string //@Member
 }
 ```
+
+[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)
 -- Thing-definition-json --
 {
 	"span": {
@@ -85,7 +85,7 @@
 			"offset": 35
 		}
 	},
-	"description": "[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)\n\n```go\nThing struct {\n\tMember string //@Member\n}\n```"
+	"description": "```go\nThing struct {\n\tMember string //@Member\n}\n```\n\n[`a.Thing` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Thing)"
 }
 
 -- Thing-hover --
@@ -97,11 +97,11 @@
 }
 ```
 -- Things-definition --
-godef/a/d.go:11:6-12: defined here as [`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
-
-```go
+godef/a/d.go:11:6-12: defined here as ```go
 func a.Things(val []string) []a.Thing
 ```
+
+[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)
 -- Things-definition-json --
 {
 	"span": {
@@ -117,7 +117,7 @@
 			"offset": 119
 		}
 	},
-	"description": "[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)\n\n```go\nfunc a.Things(val []string) []a.Thing\n```"
+	"description": "```go\nfunc a.Things(val []string) []a.Thing\n```\n\n[`a.Things` on pkg.go.dev](https://pkg.go.dev/golang.org/x/tools/internal/lsp/godef/a#Things)"
 }
 
 -- Things-hover --
diff --git a/internal/lsp/testdata/godef/broken/unclosedIf.go.golden b/internal/lsp/testdata/lsp/primarymod/godef/broken/unclosedIf.go.golden
similarity index 100%
rename from internal/lsp/testdata/godef/broken/unclosedIf.go.golden
rename to internal/lsp/testdata/lsp/primarymod/godef/broken/unclosedIf.go.golden
diff --git a/internal/lsp/testdata/godef/broken/unclosedIf.go.in b/internal/lsp/testdata/lsp/primarymod/godef/broken/unclosedIf.go.in
similarity index 100%
rename from internal/lsp/testdata/godef/broken/unclosedIf.go.in
rename to internal/lsp/testdata/lsp/primarymod/godef/broken/unclosedIf.go.in
diff --git a/internal/lsp/testdata/good/good0.go b/internal/lsp/testdata/lsp/primarymod/good/good0.go
similarity index 71%
rename from internal/lsp/testdata/good/good0.go
rename to internal/lsp/testdata/lsp/primarymod/good/good0.go
index 85c46aa..89450a8 100644
--- a/internal/lsp/testdata/good/good0.go
+++ b/internal/lsp/testdata/lsp/primarymod/good/good0.go
@@ -1,4 +1,4 @@
-package good //@diag("package", "no_diagnostics", "")
+package good //@diag("package", "no_diagnostics", "", "error")
 
 func stuff() { //@item(good_stuff, "stuff", "func()", "func"),prepare("stu", "stuff", "stuff")
 	x := 5
diff --git a/internal/lsp/testdata/good/good1.go b/internal/lsp/testdata/lsp/primarymod/good/good1.go
similarity index 83%
rename from internal/lsp/testdata/good/good1.go
rename to internal/lsp/testdata/lsp/primarymod/good/good1.go
index 826b114..b6180eb 100644
--- a/internal/lsp/testdata/good/good1.go
+++ b/internal/lsp/testdata/lsp/primarymod/good/good1.go
@@ -1,8 +1,8 @@
-package good //@diag("package", "no_diagnostics", "")
+package good //@diag("package", "no_diagnostics", "", "error")
 
 import (
 	_ "go/ast"                              //@prepare("go/ast", "_", "_")
-	"golang.org/x/tools/internal/lsp/types" //@item(types_import, "types", "\"golang.org/x/tools/internal/lsp/types\"", "package"),prepare("types","\"", "types")
+	"golang.org/x/tools/internal/lsp/types" //@item(types_import, "types", "\"golang.org/x/tools/internal/lsp/types\"", "package")
 )
 
 func random() int { //@item(good_random, "random", "func() int", "func")
diff --git a/internal/lsp/testdata/highlights/highlights.go b/internal/lsp/testdata/lsp/primarymod/highlights/highlights.go
similarity index 89%
rename from internal/lsp/testdata/highlights/highlights.go
rename to internal/lsp/testdata/lsp/primarymod/highlights/highlights.go
index de67efe..db09b56 100644
--- a/internal/lsp/testdata/highlights/highlights.go
+++ b/internal/lsp/testdata/lsp/primarymod/highlights/highlights.go
@@ -4,8 +4,6 @@
 	"fmt"         //@mark(fmtImp, "\"fmt\""),highlight(fmtImp, fmtImp, fmt1, fmt2, fmt3, fmt4)
 	h2 "net/http" //@mark(hImp, "h2"),highlight(hImp, hImp, hUse)
 	"sort"
-
-	"golang.org/x/tools/internal/lsp/protocol"
 )
 
 type F struct{ bar int } //@mark(barDeclaration, "bar"),highlight(barDeclaration, barDeclaration, bar1, bar2, bar3)
@@ -35,12 +33,10 @@
 	Print()                 //@mark(printTest, "Print"),highlight(printTest, printFunc, printTest)
 }
 
-func toProtocolHighlight(rngs []protocol.Range) []protocol.DocumentHighlight { //@mark(doc1, "DocumentHighlight"),mark(docRet1, "[]protocol.DocumentHighlight"),highlight(doc1, docRet1, doc1, doc2, doc3, result)
-	result := make([]protocol.DocumentHighlight, 0, len(rngs)) //@mark(doc2, "DocumentHighlight"),highlight(doc2, doc1, doc2, doc3)
-	kind := protocol.Text
+func toProtocolHighlight(rngs []int) []DocumentHighlight { //@mark(doc1, "DocumentHighlight"),mark(docRet1, "[]DocumentHighlight"),highlight(doc1, docRet1, doc1, doc2, doc3, result)
+	result := make([]DocumentHighlight, 0, len(rngs)) //@mark(doc2, "DocumentHighlight"),highlight(doc2, doc1, doc2, doc3)
 	for _, rng := range rngs {
-		result = append(result, protocol.DocumentHighlight{ //@mark(doc3, "DocumentHighlight"),highlight(doc3, doc1, doc2, doc3)
-			Kind:  kind,
+		result = append(result, DocumentHighlight{ //@mark(doc3, "DocumentHighlight"),highlight(doc3, doc1, doc2, doc3)
 			Range: rng,
 		})
 	}
diff --git a/internal/lsp/testdata/implementation/implementation.go b/internal/lsp/testdata/lsp/primarymod/implementation/implementation.go
similarity index 100%
rename from internal/lsp/testdata/implementation/implementation.go
rename to internal/lsp/testdata/lsp/primarymod/implementation/implementation.go
diff --git a/internal/lsp/testdata/implementation/other/other.go b/internal/lsp/testdata/lsp/primarymod/implementation/other/other.go
similarity index 100%
rename from internal/lsp/testdata/implementation/other/other.go
rename to internal/lsp/testdata/lsp/primarymod/implementation/other/other.go
diff --git a/internal/lsp/testdata/implementation/other/other_test.go b/internal/lsp/testdata/lsp/primarymod/implementation/other/other_test.go
similarity index 100%
rename from internal/lsp/testdata/implementation/other/other_test.go
rename to internal/lsp/testdata/lsp/primarymod/implementation/other/other_test.go
diff --git a/internal/lsp/testdata/importedcomplit/imported_complit.go b/internal/lsp/testdata/lsp/primarymod/importedcomplit/imported_complit.go
similarity index 100%
rename from internal/lsp/testdata/importedcomplit/imported_complit.go
rename to internal/lsp/testdata/lsp/primarymod/importedcomplit/imported_complit.go
diff --git a/internal/lsp/testdata/imports/add_import.go.golden b/internal/lsp/testdata/lsp/primarymod/imports/add_import.go.golden
similarity index 100%
rename from internal/lsp/testdata/imports/add_import.go.golden
rename to internal/lsp/testdata/lsp/primarymod/imports/add_import.go.golden
diff --git a/internal/lsp/testdata/imports/add_import.go.in b/internal/lsp/testdata/lsp/primarymod/imports/add_import.go.in
similarity index 100%
rename from internal/lsp/testdata/imports/add_import.go.in
rename to internal/lsp/testdata/lsp/primarymod/imports/add_import.go.in
diff --git a/internal/lsp/testdata/imports/good_imports.go.golden b/internal/lsp/testdata/lsp/primarymod/imports/good_imports.go.golden
similarity index 100%
rename from internal/lsp/testdata/imports/good_imports.go.golden
rename to internal/lsp/testdata/lsp/primarymod/imports/good_imports.go.golden
diff --git a/internal/lsp/testdata/imports/good_imports.go.in b/internal/lsp/testdata/lsp/primarymod/imports/good_imports.go.in
similarity index 100%
rename from internal/lsp/testdata/imports/good_imports.go.in
rename to internal/lsp/testdata/lsp/primarymod/imports/good_imports.go.in
diff --git a/internal/lsp/testdata/imports/issue35458.go.golden b/internal/lsp/testdata/lsp/primarymod/imports/issue35458.go.golden
similarity index 100%
rename from internal/lsp/testdata/imports/issue35458.go.golden
rename to internal/lsp/testdata/lsp/primarymod/imports/issue35458.go.golden
diff --git a/internal/lsp/testdata/imports/issue35458.go.in b/internal/lsp/testdata/lsp/primarymod/imports/issue35458.go.in
similarity index 100%
rename from internal/lsp/testdata/imports/issue35458.go.in
rename to internal/lsp/testdata/lsp/primarymod/imports/issue35458.go.in
diff --git a/internal/lsp/testdata/imports/multiple_blocks.go.golden b/internal/lsp/testdata/lsp/primarymod/imports/multiple_blocks.go.golden
similarity index 100%
rename from internal/lsp/testdata/imports/multiple_blocks.go.golden
rename to internal/lsp/testdata/lsp/primarymod/imports/multiple_blocks.go.golden
diff --git a/internal/lsp/testdata/imports/multiple_blocks.go.in b/internal/lsp/testdata/lsp/primarymod/imports/multiple_blocks.go.in
similarity index 100%
rename from internal/lsp/testdata/imports/multiple_blocks.go.in
rename to internal/lsp/testdata/lsp/primarymod/imports/multiple_blocks.go.in
diff --git a/internal/lsp/testdata/imports/needs_imports.go.golden b/internal/lsp/testdata/lsp/primarymod/imports/needs_imports.go.golden
similarity index 100%
rename from internal/lsp/testdata/imports/needs_imports.go.golden
rename to internal/lsp/testdata/lsp/primarymod/imports/needs_imports.go.golden
diff --git a/internal/lsp/testdata/imports/needs_imports.go.in b/internal/lsp/testdata/lsp/primarymod/imports/needs_imports.go.in
similarity index 100%
rename from internal/lsp/testdata/imports/needs_imports.go.in
rename to internal/lsp/testdata/lsp/primarymod/imports/needs_imports.go.in
diff --git a/internal/lsp/testdata/imports/remove_import.go.golden b/internal/lsp/testdata/lsp/primarymod/imports/remove_import.go.golden
similarity index 100%
rename from internal/lsp/testdata/imports/remove_import.go.golden
rename to internal/lsp/testdata/lsp/primarymod/imports/remove_import.go.golden
diff --git a/internal/lsp/testdata/imports/remove_import.go.in b/internal/lsp/testdata/lsp/primarymod/imports/remove_import.go.in
similarity index 100%
rename from internal/lsp/testdata/imports/remove_import.go.in
rename to internal/lsp/testdata/lsp/primarymod/imports/remove_import.go.in
diff --git a/internal/lsp/testdata/imports/remove_imports.go.golden b/internal/lsp/testdata/lsp/primarymod/imports/remove_imports.go.golden
similarity index 100%
rename from internal/lsp/testdata/imports/remove_imports.go.golden
rename to internal/lsp/testdata/lsp/primarymod/imports/remove_imports.go.golden
diff --git a/internal/lsp/testdata/imports/remove_imports.go.in b/internal/lsp/testdata/lsp/primarymod/imports/remove_imports.go.in
similarity index 100%
rename from internal/lsp/testdata/imports/remove_imports.go.in
rename to internal/lsp/testdata/lsp/primarymod/imports/remove_imports.go.in
diff --git a/internal/lsp/testdata/index/index.go b/internal/lsp/testdata/lsp/primarymod/index/index.go
similarity index 100%
rename from internal/lsp/testdata/index/index.go
rename to internal/lsp/testdata/lsp/primarymod/index/index.go
diff --git a/internal/lsp/testdata/interfacerank/interface_rank.go b/internal/lsp/testdata/lsp/primarymod/interfacerank/interface_rank.go
similarity index 100%
rename from internal/lsp/testdata/interfacerank/interface_rank.go
rename to internal/lsp/testdata/lsp/primarymod/interfacerank/interface_rank.go
diff --git a/internal/lsp/testdata/keywords/accidental_keywords.go.in b/internal/lsp/testdata/lsp/primarymod/keywords/accidental_keywords.go.in
similarity index 83%
rename from internal/lsp/testdata/keywords/accidental_keywords.go.in
rename to internal/lsp/testdata/lsp/primarymod/keywords/accidental_keywords.go.in
index 22ad4e2..711841c 100644
--- a/internal/lsp/testdata/keywords/accidental_keywords.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/keywords/accidental_keywords.go.in
@@ -18,6 +18,12 @@
 }
 
 func _() {
+	channel := 123 //@item(kwChannel, "channel", "int", "var")
+	chan //@complete(" //", kwChannel)
+	foo.bar()
+}
+
+func _() {
 	foo.bar()
 	var typeName string //@item(kwTypeName, "typeName", "string", "var")
 	foo.bar()
diff --git a/internal/lsp/testdata/keywords/keywords.go b/internal/lsp/testdata/lsp/primarymod/keywords/keywords.go
similarity index 92%
rename from internal/lsp/testdata/keywords/keywords.go
rename to internal/lsp/testdata/lsp/primarymod/keywords/keywords.go
index 5fda68d..7e7fd5b 100644
--- a/internal/lsp/testdata/keywords/keywords.go
+++ b/internal/lsp/testdata/lsp/primarymod/keywords/keywords.go
@@ -1,5 +1,7 @@
 package keywords
 
+//@rank("", type),rank("", func),rank("", var),rank("", const),rank("", import)
+
 func _() {
 	var test int
 	var tChan chan int
@@ -42,7 +44,7 @@
 
 	f //@complete(" //", for)
 	d //@complete(" //", defer)
-	g //@complete(" //", go)
+	g //@rank(" //", go),rank(" //", goto)
 	r //@complete(" //", return)
 	i //@complete(" //", if)
 	e //@complete(" //", else)
@@ -72,3 +74,4 @@
 /* return */ //@item(return, "return", "", "keyword")
 /* var */ //@item(var, "var", "", "keyword")
 /* const */ //@item(const, "const", "", "keyword")
+/* goto */ //@item(goto, "goto", "", "keyword")
diff --git a/internal/lsp/testdata/labels/labels.go b/internal/lsp/testdata/lsp/primarymod/labels/labels.go
similarity index 100%
rename from internal/lsp/testdata/labels/labels.go
rename to internal/lsp/testdata/lsp/primarymod/labels/labels.go
diff --git a/internal/lsp/testdata/links/links.go b/internal/lsp/testdata/lsp/primarymod/links/links.go
similarity index 87%
rename from internal/lsp/testdata/links/links.go
rename to internal/lsp/testdata/lsp/primarymod/links/links.go
index 926b603..be15ddb 100644
--- a/internal/lsp/testdata/links/links.go
+++ b/internal/lsp/testdata/lsp/primarymod/links/links.go
@@ -7,7 +7,7 @@
 
 	_ "database/sql" //@link(`database/sql`, `https://pkg.go.dev/database/sql`)
 
-	errors "golang.org/x/xerrors" //@link(`golang.org/x/xerrors`, `https://pkg.go.dev/golang.org/x/xerrors`)
+	_ "example.com/extramodule/pkg" //@link(`example.com/extramodule/pkg`,`https://pkg.go.dev/example.com/extramodule@v1.0.0/pkg`)
 )
 
 var (
diff --git a/internal/lsp/testdata/maps/maps.go.in b/internal/lsp/testdata/lsp/primarymod/maps/maps.go.in
similarity index 63%
rename from internal/lsp/testdata/maps/maps.go.in
rename to internal/lsp/testdata/lsp/primarymod/maps/maps.go.in
index 5c9dedd..b4a4cdd 100644
--- a/internal/lsp/testdata/maps/maps.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/maps/maps.go.in
@@ -11,8 +11,8 @@
 	// comparable
 	type aStruct struct{} //@item(mapStructType, "aStruct", "struct{...}", "struct")
 
-	map[]a{} //@complete("]", mapSliceTypePtr, mapStructType, mapVar)
+	map[]a{} //@complete("]", mapSliceTypePtr, mapStructType)
 
-	map[a]a{} //@complete("]", mapSliceTypePtr, mapStructType, mapVar)
-	map[a]a{} //@complete("{", mapSliceType, mapStructType, mapVar)
+	map[a]a{} //@complete("]", mapSliceTypePtr, mapStructType)
+	map[a]a{} //@complete("{", mapSliceType, mapStructType)
 }
diff --git a/internal/lsp/testdata/lsp/primarymod/multireturn/multi_return.go b/internal/lsp/testdata/lsp/primarymod/multireturn/multi_return.go
new file mode 100644
index 0000000..0da698f
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/multireturn/multi_return.go
@@ -0,0 +1,36 @@
+package multireturn
+
+func f0() {} //@item(multiF0, "f0", "func()", "func")
+
+func f1(int) int { return 0 } //@item(multiF1, "f1", "func(int) int", "func")
+
+func f2(int, int) (int, int) { return 0, 0 } //@item(multiF2, "f2", "func(int, int) (int, int)", "func")
+
+func f2Str(string, string) (string, string) { return "", "" } //@item(multiF2Str, "f2Str", "func(string, string) (string, string)", "func")
+
+func f3(int, int, int) (int, int, int) { return 0, 0, 0 } //@item(multiF3, "f3", "func(int, int, int) (int, int, int)", "func")
+
+func _() {
+	_ := f //@rank(" //", multiF1, multiF2)
+
+	_, _ := f //@rank(" //", multiF2, multiF0),rank(" //", multiF1, multiF0)
+
+	_, _ := _, f //@rank(" //", multiF1, multiF2),rank(" //", multiF1, multiF0)
+
+	_, _ := f, abc //@rank(", abc", multiF1, multiF2)
+
+	f1()     //@rank(")", multiF1, multiF0)
+	f1(f)    //@rank(")", multiF1, multiF2)
+	f2(f)    //@rank(")", multiF2, multiF3),rank(")", multiF1, multiF3)
+	f2(1, f) //@rank(")", multiF1, multiF2),rank(")", multiF1, multiF0)
+	f2Str()  //@rank(")", multiF2Str, multiF2)
+
+	var i int
+	i, _ := f //@rank(" //", multiF2, multiF2Str)
+
+	var s string
+	_, s := f //@rank(" //", multiF2Str, multiF2)
+
+	var variadic func(int, ...int)
+	variadic() //@rank(")", multiF1, multiF0),rank(")", multiF2, multiF0),rank(")", multiF3, multiF0)
+}
diff --git a/internal/lsp/testdata/nested_complit/nested_complit.go.in b/internal/lsp/testdata/lsp/primarymod/nested_complit/nested_complit.go.in
similarity index 100%
rename from internal/lsp/testdata/nested_complit/nested_complit.go.in
rename to internal/lsp/testdata/lsp/primarymod/nested_complit/nested_complit.go.in
diff --git a/internal/lsp/testdata/nodisk/empty b/internal/lsp/testdata/lsp/primarymod/nodisk/empty
similarity index 100%
rename from internal/lsp/testdata/nodisk/empty
rename to internal/lsp/testdata/lsp/primarymod/nodisk/empty
diff --git a/internal/lsp/testdata/nodisk/nodisk.overlay.go b/internal/lsp/testdata/lsp/primarymod/nodisk/nodisk.overlay.go
similarity index 100%
rename from internal/lsp/testdata/nodisk/nodisk.overlay.go
rename to internal/lsp/testdata/lsp/primarymod/nodisk/nodisk.overlay.go
diff --git a/internal/lsp/testdata/noparse/noparse.go.in b/internal/lsp/testdata/lsp/primarymod/noparse/noparse.go.in
similarity index 92%
rename from internal/lsp/testdata/noparse/noparse.go.in
rename to internal/lsp/testdata/lsp/primarymod/noparse/noparse.go.in
index 66a8cce..7dc23e0 100644
--- a/internal/lsp/testdata/noparse/noparse.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/noparse/noparse.go.in
@@ -8,4 +8,4 @@
 	x := 5
 }
 
-func .() {} //@diag(".", "syntax", "expected 'IDENT', found '.'")
+func .() {} //@diag(".", "syntax", "expected 'IDENT', found '.'", "error")
diff --git a/internal/lsp/testdata/noparse_format/noparse_format.go.golden b/internal/lsp/testdata/lsp/primarymod/noparse_format/noparse_format.go.golden
similarity index 100%
rename from internal/lsp/testdata/noparse_format/noparse_format.go.golden
rename to internal/lsp/testdata/lsp/primarymod/noparse_format/noparse_format.go.golden
diff --git a/internal/lsp/testdata/noparse_format/noparse_format.go.in b/internal/lsp/testdata/lsp/primarymod/noparse_format/noparse_format.go.in
similarity index 89%
rename from internal/lsp/testdata/noparse_format/noparse_format.go.in
rename to internal/lsp/testdata/lsp/primarymod/noparse_format/noparse_format.go.in
index f230a69..4fc3824 100644
--- a/internal/lsp/testdata/noparse_format/noparse_format.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/noparse_format/noparse_format.go.in
@@ -4,6 +4,6 @@
 
 func what() {
 	var b int
-	if {		hi() //@diag("{", "syntax", "missing condition in if statement")
+	if {		hi() //@diag("{", "syntax", "missing condition in if statement", "error")
 	}
 }
\ No newline at end of file
diff --git a/internal/lsp/testdata/noparse_format/parse_format.go.golden b/internal/lsp/testdata/lsp/primarymod/noparse_format/parse_format.go.golden
similarity index 100%
rename from internal/lsp/testdata/noparse_format/parse_format.go.golden
rename to internal/lsp/testdata/lsp/primarymod/noparse_format/parse_format.go.golden
diff --git a/internal/lsp/testdata/noparse_format/parse_format.go.in b/internal/lsp/testdata/lsp/primarymod/noparse_format/parse_format.go.in
similarity index 100%
rename from internal/lsp/testdata/noparse_format/parse_format.go.in
rename to internal/lsp/testdata/lsp/primarymod/noparse_format/parse_format.go.in
diff --git a/internal/lsp/testdata/rank/assign_rank.go.in b/internal/lsp/testdata/lsp/primarymod/rank/assign_rank.go.in
similarity index 100%
rename from internal/lsp/testdata/rank/assign_rank.go.in
rename to internal/lsp/testdata/lsp/primarymod/rank/assign_rank.go.in
diff --git a/internal/lsp/testdata/rank/binexpr_rank.go.in b/internal/lsp/testdata/lsp/primarymod/rank/binexpr_rank.go.in
similarity index 100%
rename from internal/lsp/testdata/rank/binexpr_rank.go.in
rename to internal/lsp/testdata/lsp/primarymod/rank/binexpr_rank.go.in
diff --git a/internal/lsp/testdata/rank/convert_rank.go.in b/internal/lsp/testdata/lsp/primarymod/rank/convert_rank.go.in
similarity index 90%
rename from internal/lsp/testdata/rank/convert_rank.go.in
rename to internal/lsp/testdata/lsp/primarymod/rank/convert_rank.go.in
index 77e2d27..77850ef 100644
--- a/internal/lsp/testdata/rank/convert_rank.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/rank/convert_rank.go.in
@@ -39,4 +39,7 @@
 	type myUint uint32
 	var mu myUint
 	mu = conv //@rank(" //", convertD, convertE)
+
+	// don't downrank constants when assigning to interface{}
+	var _ interface{} = c //@rank(" //", convertD, complex)
 }
diff --git a/internal/lsp/testdata/rank/switch_rank.go.in b/internal/lsp/testdata/lsp/primarymod/rank/switch_rank.go.in
similarity index 100%
rename from internal/lsp/testdata/rank/switch_rank.go.in
rename to internal/lsp/testdata/lsp/primarymod/rank/switch_rank.go.in
diff --git a/internal/lsp/testdata/rank/type_assert_rank.go.in b/internal/lsp/testdata/lsp/primarymod/rank/type_assert_rank.go.in
similarity index 70%
rename from internal/lsp/testdata/rank/type_assert_rank.go.in
rename to internal/lsp/testdata/lsp/primarymod/rank/type_assert_rank.go.in
index 3490c85..416541c 100644
--- a/internal/lsp/testdata/rank/type_assert_rank.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/rank/type_assert_rank.go.in
@@ -4,5 +4,5 @@
 	type flower int //@item(flower, "flower", "int", "type")
 	var fig string  //@item(fig, "fig", "string", "var")
 
-	_ = interface{}(nil).(f) //@complete(") //", flower, fig)
+	_ = interface{}(nil).(f) //@complete(") //", flower)
 }
diff --git a/internal/lsp/testdata/rank/type_switch_rank.go.in b/internal/lsp/testdata/lsp/primarymod/rank/type_switch_rank.go.in
similarity index 68%
rename from internal/lsp/testdata/rank/type_switch_rank.go.in
rename to internal/lsp/testdata/lsp/primarymod/rank/type_switch_rank.go.in
index 6cec597..293025f 100644
--- a/internal/lsp/testdata/rank/type_switch_rank.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/rank/type_switch_rank.go.in
@@ -5,7 +5,7 @@
 	var banana string //@item(banana, "banana", "string", "var")
 
 	switch interface{}(pear).(type) {
-	case b: //@complete(":", basket, banana)
-		b //@complete(" //", banana, basket, break)
+	case b: //@complete(":", basket)
+		b //@complete(" //", banana, basket)
 	}
 }
diff --git a/internal/lsp/testdata/references/other/other.go b/internal/lsp/testdata/lsp/primarymod/references/other/other.go
similarity index 100%
rename from internal/lsp/testdata/references/other/other.go
rename to internal/lsp/testdata/lsp/primarymod/references/other/other.go
diff --git a/internal/lsp/testdata/references/refs.go b/internal/lsp/testdata/lsp/primarymod/references/refs.go
similarity index 91%
rename from internal/lsp/testdata/references/refs.go
rename to internal/lsp/testdata/lsp/primarymod/references/refs.go
index 019baf8..6ce4afc 100644
--- a/internal/lsp/testdata/references/refs.go
+++ b/internal/lsp/testdata/lsp/primarymod/references/refs.go
@@ -1,3 +1,4 @@
+// Package refs is a package used to test find references.
 package refs
 
 type i int //@mark(typeI, "i"),refs("i", typeI, argI, returnI, embeddedI)
diff --git a/internal/lsp/testdata/references/refs_test.go b/internal/lsp/testdata/lsp/primarymod/references/refs_test.go
similarity index 100%
rename from internal/lsp/testdata/references/refs_test.go
rename to internal/lsp/testdata/lsp/primarymod/references/refs_test.go
diff --git a/internal/lsp/testdata/rename/a/random.go.golden b/internal/lsp/testdata/lsp/primarymod/rename/a/random.go.golden
similarity index 100%
rename from internal/lsp/testdata/rename/a/random.go.golden
rename to internal/lsp/testdata/lsp/primarymod/rename/a/random.go.golden
diff --git a/internal/lsp/testdata/rename/a/random.go.in b/internal/lsp/testdata/lsp/primarymod/rename/a/random.go.in
similarity index 100%
rename from internal/lsp/testdata/rename/a/random.go.in
rename to internal/lsp/testdata/lsp/primarymod/rename/a/random.go.in
diff --git a/internal/lsp/testdata/rename/b/b.go b/internal/lsp/testdata/lsp/primarymod/rename/b/b.go
similarity index 100%
rename from internal/lsp/testdata/rename/b/b.go
rename to internal/lsp/testdata/lsp/primarymod/rename/b/b.go
diff --git a/internal/lsp/testdata/rename/b/b.go.golden b/internal/lsp/testdata/lsp/primarymod/rename/b/b.go.golden
similarity index 100%
rename from internal/lsp/testdata/rename/b/b.go.golden
rename to internal/lsp/testdata/lsp/primarymod/rename/b/b.go.golden
diff --git a/internal/lsp/testdata/rename/bad/bad.go.golden b/internal/lsp/testdata/lsp/primarymod/rename/bad/bad.go.golden
similarity index 100%
rename from internal/lsp/testdata/rename/bad/bad.go.golden
rename to internal/lsp/testdata/lsp/primarymod/rename/bad/bad.go.golden
diff --git a/internal/lsp/testdata/rename/bad/bad.go.in b/internal/lsp/testdata/lsp/primarymod/rename/bad/bad.go.in
similarity index 100%
rename from internal/lsp/testdata/rename/bad/bad.go.in
rename to internal/lsp/testdata/lsp/primarymod/rename/bad/bad.go.in
diff --git a/internal/lsp/testdata/rename/bad/bad_test.go.in b/internal/lsp/testdata/lsp/primarymod/rename/bad/bad_test.go.in
similarity index 100%
rename from internal/lsp/testdata/rename/bad/bad_test.go.in
rename to internal/lsp/testdata/lsp/primarymod/rename/bad/bad_test.go.in
diff --git a/internal/lsp/testdata/rename/crosspkg/crosspkg.go b/internal/lsp/testdata/lsp/primarymod/rename/crosspkg/crosspkg.go
similarity index 100%
rename from internal/lsp/testdata/rename/crosspkg/crosspkg.go
rename to internal/lsp/testdata/lsp/primarymod/rename/crosspkg/crosspkg.go
diff --git a/internal/lsp/testdata/rename/crosspkg/crosspkg.go.golden b/internal/lsp/testdata/lsp/primarymod/rename/crosspkg/crosspkg.go.golden
similarity index 100%
rename from internal/lsp/testdata/rename/crosspkg/crosspkg.go.golden
rename to internal/lsp/testdata/lsp/primarymod/rename/crosspkg/crosspkg.go.golden
diff --git a/internal/lsp/testdata/rename/crosspkg/other/other.go b/internal/lsp/testdata/lsp/primarymod/rename/crosspkg/other/other.go
similarity index 100%
rename from internal/lsp/testdata/rename/crosspkg/other/other.go
rename to internal/lsp/testdata/lsp/primarymod/rename/crosspkg/other/other.go
diff --git a/internal/lsp/testdata/rename/crosspkg/other/other.go.golden b/internal/lsp/testdata/lsp/primarymod/rename/crosspkg/other/other.go.golden
similarity index 100%
rename from internal/lsp/testdata/rename/crosspkg/other/other.go.golden
rename to internal/lsp/testdata/lsp/primarymod/rename/crosspkg/other/other.go.golden
diff --git a/internal/lsp/testdata/rename/testy/testy.go b/internal/lsp/testdata/lsp/primarymod/rename/testy/testy.go
similarity index 100%
rename from internal/lsp/testdata/rename/testy/testy.go
rename to internal/lsp/testdata/lsp/primarymod/rename/testy/testy.go
diff --git a/internal/lsp/testdata/rename/testy/testy.go.golden b/internal/lsp/testdata/lsp/primarymod/rename/testy/testy.go.golden
similarity index 100%
rename from internal/lsp/testdata/rename/testy/testy.go.golden
rename to internal/lsp/testdata/lsp/primarymod/rename/testy/testy.go.golden
diff --git a/internal/lsp/testdata/rename/testy/testy_test.go b/internal/lsp/testdata/lsp/primarymod/rename/testy/testy_test.go
similarity index 100%
rename from internal/lsp/testdata/rename/testy/testy_test.go
rename to internal/lsp/testdata/lsp/primarymod/rename/testy/testy_test.go
diff --git a/internal/lsp/testdata/rename/testy/testy_test.go.golden b/internal/lsp/testdata/lsp/primarymod/rename/testy/testy_test.go.golden
similarity index 100%
rename from internal/lsp/testdata/rename/testy/testy_test.go.golden
rename to internal/lsp/testdata/lsp/primarymod/rename/testy/testy_test.go.golden
diff --git a/internal/lsp/testdata/selector/selector.go.in b/internal/lsp/testdata/lsp/primarymod/selector/selector.go.in
similarity index 100%
rename from internal/lsp/testdata/selector/selector.go.in
rename to internal/lsp/testdata/lsp/primarymod/selector/selector.go.in
diff --git a/internal/lsp/testdata/signature/signature.go b/internal/lsp/testdata/lsp/primarymod/signature/signature.go
similarity index 100%
rename from internal/lsp/testdata/signature/signature.go
rename to internal/lsp/testdata/lsp/primarymod/signature/signature.go
diff --git a/internal/lsp/testdata/signature/signature.go.golden b/internal/lsp/testdata/lsp/primarymod/signature/signature.go.golden
similarity index 97%
rename from internal/lsp/testdata/signature/signature.go.golden
rename to internal/lsp/testdata/lsp/primarymod/signature/signature.go.golden
index dafd426..22d2a51 100644
--- a/internal/lsp/testdata/signature/signature.go.golden
+++ b/internal/lsp/testdata/lsp/primarymod/signature/signature.go.golden
@@ -1,5 +1,3 @@
--- -signature --
-
 -- Bar(float64, ...byte)-signature --
 Bar(float64, ...byte)
 
diff --git a/internal/lsp/testdata/signature/signature2.go.golden b/internal/lsp/testdata/lsp/primarymod/signature/signature2.go.golden
similarity index 100%
rename from internal/lsp/testdata/signature/signature2.go.golden
rename to internal/lsp/testdata/lsp/primarymod/signature/signature2.go.golden
diff --git a/internal/lsp/testdata/signature/signature2.go.in b/internal/lsp/testdata/lsp/primarymod/signature/signature2.go.in
similarity index 100%
rename from internal/lsp/testdata/signature/signature2.go.in
rename to internal/lsp/testdata/lsp/primarymod/signature/signature2.go.in
diff --git a/internal/lsp/testdata/snippets/literal_snippets.go.in b/internal/lsp/testdata/lsp/primarymod/snippets/literal_snippets.go.in
similarity index 96%
rename from internal/lsp/testdata/snippets/literal_snippets.go.in
rename to internal/lsp/testdata/lsp/primarymod/snippets/literal_snippets.go.in
index 9906a2b..ffaa125 100644
--- a/internal/lsp/testdata/snippets/literal_snippets.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/snippets/literal_snippets.go.in
@@ -180,3 +180,15 @@
 	var mi myInt
 	mi = my //@snippet(" //", litMyInt, "myInt($0)", "myInt($0)")
 }
+
+func _() {
+	type ptrStruct struct {
+		p *ptrStruct
+	}
+
+	ptrStruct{} //@item(litPtrStruct, "ptrStruct{}", "", "var")
+
+	ptrStruct{
+		p: &ptrSt, //@rank(",", litPtrStruct)
+	}
+}
diff --git a/internal/lsp/testdata/snippets/snippets.go.golden b/internal/lsp/testdata/lsp/primarymod/snippets/snippets.go.golden
similarity index 79%
rename from internal/lsp/testdata/snippets/snippets.go.golden
rename to internal/lsp/testdata/lsp/primarymod/snippets/snippets.go.golden
index 34b919e..3f20ba5 100644
--- a/internal/lsp/testdata/snippets/snippets.go.golden
+++ b/internal/lsp/testdata/lsp/primarymod/snippets/snippets.go.golden
@@ -1,5 +1,3 @@
--- -signature --
-
 -- baz(at AliasType, b bool)-signature --
 baz(at AliasType, b bool)
 
diff --git a/internal/lsp/testdata/snippets/snippets.go.in b/internal/lsp/testdata/lsp/primarymod/snippets/snippets.go.in
similarity index 100%
rename from internal/lsp/testdata/snippets/snippets.go.in
rename to internal/lsp/testdata/lsp/primarymod/snippets/snippets.go.in
diff --git a/internal/lsp/testdata/suggestedfix/has_suggested_fix.go b/internal/lsp/testdata/lsp/primarymod/suggestedfix/has_suggested_fix.go
similarity index 87%
rename from internal/lsp/testdata/suggestedfix/has_suggested_fix.go
rename to internal/lsp/testdata/lsp/primarymod/suggestedfix/has_suggested_fix.go
index 9ade674..ccd198c 100644
--- a/internal/lsp/testdata/suggestedfix/has_suggested_fix.go
+++ b/internal/lsp/testdata/lsp/primarymod/suggestedfix/has_suggested_fix.go
@@ -7,5 +7,5 @@
 func goodbye() {
 	s := "hiiiiiii"
 	s = s //@suggestedfix("s = s")
-	log.Printf(s)
+	log.Print(s)
 }
diff --git a/internal/lsp/testdata/suggestedfix/has_suggested_fix.go.golden b/internal/lsp/testdata/lsp/primarymod/suggestedfix/has_suggested_fix.go.golden
similarity index 65%
rename from internal/lsp/testdata/suggestedfix/has_suggested_fix.go.golden
rename to internal/lsp/testdata/lsp/primarymod/suggestedfix/has_suggested_fix.go.golden
index 10ec450..4923ecc 100644
--- a/internal/lsp/testdata/suggestedfix/has_suggested_fix.go.golden
+++ b/internal/lsp/testdata/lsp/primarymod/suggestedfix/has_suggested_fix.go.golden
@@ -1,4 +1,4 @@
--- suggestedfix --
+-- suggestedfix_has_suggested_fix_9_2 --
 package suggestedfix
 
 import (
@@ -8,6 +8,6 @@
 func goodbye() {
 	s := "hiiiiiii"
 	 //@suggestedfix("s = s")
-	log.Printf(s)
+	log.Print(s)
 }
 
diff --git a/internal/lsp/testdata/symbols/main.go b/internal/lsp/testdata/lsp/primarymod/symbols/main.go
similarity index 61%
rename from internal/lsp/testdata/symbols/main.go
rename to internal/lsp/testdata/lsp/primarymod/symbols/main.go
index b9ee77c..0e8d5b6 100644
--- a/internal/lsp/testdata/symbols/main.go
+++ b/internal/lsp/testdata/lsp/primarymod/symbols/main.go
@@ -4,7 +4,7 @@
 	"io"
 )
 
-var x = 42 //@symbol("x", "x", "Variable", "")
+var x = 42 //@mark(symbolsx, "x"), symbol("x", "x", "Variable", "")
 
 const y = 43 //@symbol("y", "y", "Constant", "")
 
@@ -19,22 +19,22 @@
 	BoolAlias = bool //@symbol("BoolAlias", "BoolAlias", "Boolean", "")
 )
 
-type Foo struct { //@symbol("Foo", "Foo", "Struct", "")
-	Quux           //@symbol("Quux", "Quux", "Field", "Foo")
+type Foo struct { //@mark(symbolsFoo, "Foo"), symbol("Foo", "Foo", "Struct", "")
+	Quux           //@mark(fQuux, "Quux"), symbol("Quux", "Quux", "Field", "Foo")
 	W    io.Writer //@symbol("W" , "W", "Field", "Foo")
-	Bar  int       //@symbol("Bar", "Bar", "Field", "Foo")
+	Bar  int       //@mark(fBar, "Bar"), symbol("Bar", "Bar", "Field", "Foo")
 	baz  string    //@symbol("baz", "baz", "Field", "Foo")
 }
 
 type Quux struct { //@symbol("Quux", "Quux", "Struct", "")
-	X, Y float64 //@symbol("X", "X", "Field", "Quux"), symbol("Y", "Y", "Field", "Quux")
+	X, Y float64 //@mark(qX, "X"), symbol("X", "X", "Field", "Quux"), symbol("Y", "Y", "Field", "Quux")
 }
 
 func (f Foo) Baz() string { //@symbol("Baz", "Baz", "Method", "Foo")
 	return f.baz
 }
 
-func (q *Quux) Do() {} //@symbol("Do", "Do", "Method", "Quux")
+func (q *Quux) Do() {} //@mark(qDo, "Do"), symbol("Do", "Do", "Method", "Quux")
 
 func main() { //@symbol("main", "main", "Function", "")
 
@@ -44,13 +44,13 @@
 	String() string //@symbol("String", "String", "Method", "Stringer")
 }
 
-type ABer interface { //@symbol("ABer", "ABer", "Interface", "")
+type ABer interface { //@mark(ABerInterface, "ABer"), symbol("ABer", "ABer", "Interface", "")
 	B()        //@symbol("B", "B", "Method", "ABer")
-	A() string //@symbol("A", "A", "Method", "ABer")
+	A() string //@mark(ABerA, "A"), symbol("A", "A", "Method", "ABer")
 }
 
 type WithEmbeddeds interface { //@symbol("WithEmbeddeds", "WithEmbeddeds", "Interface", "")
 	Do()      //@symbol("Do", "Do", "Method", "WithEmbeddeds")
 	ABer      //@symbol("ABer", "ABer", "Interface", "WithEmbeddeds")
-	io.Writer //@symbol("io.Writer", "io.Writer", "Interface", "WithEmbeddeds")
+	io.Writer //@mark(ioWriter, "io.Writer"), symbol("io.Writer", "io.Writer", "Interface", "WithEmbeddeds")
 }
diff --git a/internal/lsp/testdata/symbols/main.go.golden b/internal/lsp/testdata/lsp/primarymod/symbols/main.go.golden
similarity index 100%
rename from internal/lsp/testdata/symbols/main.go.golden
rename to internal/lsp/testdata/lsp/primarymod/symbols/main.go.golden
diff --git a/internal/lsp/testdata/testy/testy.go b/internal/lsp/testdata/lsp/primarymod/testy/testy.go
similarity index 100%
rename from internal/lsp/testdata/testy/testy.go
rename to internal/lsp/testdata/lsp/primarymod/testy/testy.go
diff --git a/internal/lsp/testdata/testy/testy_test.go b/internal/lsp/testdata/lsp/primarymod/testy/testy_test.go
similarity index 83%
rename from internal/lsp/testdata/testy/testy_test.go
rename to internal/lsp/testdata/lsp/primarymod/testy/testy_test.go
index 4bc6207..828a494 100644
--- a/internal/lsp/testdata/testy/testy_test.go
+++ b/internal/lsp/testdata/lsp/primarymod/testy/testy_test.go
@@ -3,6 +3,6 @@
 import "testing"
 
 func TestSomething(t *testing.T) { //@item(TestSomething, "TestSomething(t *testing.T)", "", "func")
-	var x int //@mark(testyX, "x"),diag("x", "compiler", "x declared but not used"),refs("x", testyX)
+	var x int //@mark(testyX, "x"),diag("x", "compiler", "x declared but not used", "error"),refs("x", testyX)
 	a()       //@mark(testyA, "a")
 }
diff --git a/internal/lsp/testdata/typeassert/type_assert.go b/internal/lsp/testdata/lsp/primarymod/typeassert/type_assert.go
similarity index 100%
rename from internal/lsp/testdata/typeassert/type_assert.go
rename to internal/lsp/testdata/lsp/primarymod/typeassert/type_assert.go
diff --git a/internal/lsp/testdata/types/types.go b/internal/lsp/testdata/lsp/primarymod/types/types.go
similarity index 100%
rename from internal/lsp/testdata/types/types.go
rename to internal/lsp/testdata/lsp/primarymod/types/types.go
diff --git a/internal/lsp/testdata/unimported/export_test.go b/internal/lsp/testdata/lsp/primarymod/unimported/export_test.go
similarity index 100%
rename from internal/lsp/testdata/unimported/export_test.go
rename to internal/lsp/testdata/lsp/primarymod/unimported/export_test.go
diff --git a/internal/lsp/testdata/lsp/primarymod/unimported/unimported.go.in b/internal/lsp/testdata/lsp/primarymod/unimported/unimported.go.in
new file mode 100644
index 0000000..d9f109c
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/unimported/unimported.go.in
@@ -0,0 +1,22 @@
+package unimported
+
+func _() {
+	http //@unimported("p", nethttp, nethttptest)
+	pkg  //@unimported("g", externalpackage)
+	// container/ring is extremely unlikely to be imported by anything, so shouldn't have type information.
+	ring.Ring     //@unimported("Ring", ringring)
+	signature.Foo //@unimported("Foo", signaturefoo)
+	context.Bac   //@unimported("Bac", contextBackground, contextBackgroundErr)
+}
+
+// Create markers for unimported std lib packages. Only for use by this test.
+/* http */ //@item(nethttp, "http", "\"net/http\"", "package")
+/* httptest */ //@item(nethttptest, "httptest", "\"net/http/httptest\"", "package")
+/* pkg */ //@item(externalpackage, "pkg", "\"example.com/extramodule/pkg\"", "package")
+
+/* ring.Ring */ //@item(ringring, "Ring", "(from \"container/ring\")", "var")
+
+/* signature.Foo */ //@item(signaturefoo, "Foo", "func(a string, b int) (c bool) (from \"golang.org/x/tools/internal/lsp/signature\")", "func")
+
+/* context.Background */ //@item(contextBackground, "Background", "func() context.Context (from \"context\")", "func")
+/* context.Background().Err */ //@item(contextBackgroundErr, "Background().Err", "func() error (from \"context\")", "method")
diff --git a/internal/lsp/testdata/unimported/unimported_cand_type.go b/internal/lsp/testdata/lsp/primarymod/unimported/unimported_cand_type.go
similarity index 73%
rename from internal/lsp/testdata/unimported/unimported_cand_type.go
rename to internal/lsp/testdata/lsp/primarymod/unimported/unimported_cand_type.go
index 2690c1a..531aa2d 100644
--- a/internal/lsp/testdata/unimported/unimported_cand_type.go
+++ b/internal/lsp/testdata/lsp/primarymod/unimported/unimported_cand_type.go
@@ -1,8 +1,10 @@
 package unimported
 
 import (
+	_ "context"
+
 	"golang.org/x/tools/internal/lsp/baz"
-	"golang.org/x/tools/internal/lsp/signature" // provide type information for unimported completions in the other file
+	_ "golang.org/x/tools/internal/lsp/signature" // provide type information for unimported completions in the other file
 )
 
 func _() {
diff --git a/internal/lsp/testdata/unimported/x_test.go b/internal/lsp/testdata/lsp/primarymod/unimported/x_test.go
similarity index 100%
rename from internal/lsp/testdata/unimported/x_test.go
rename to internal/lsp/testdata/lsp/primarymod/unimported/x_test.go
diff --git a/internal/lsp/testdata/lsp/primarymod/unresolved/unresolved.go.in b/internal/lsp/testdata/lsp/primarymod/unresolved/unresolved.go.in
new file mode 100644
index 0000000..e1daecc
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/unresolved/unresolved.go.in
@@ -0,0 +1,6 @@
+package unresolved
+
+func foo(interface{}) {
+	// don't crash on fake "resolved" type
+	foo(func(i, j f //@complete(" //")
+}
diff --git a/internal/lsp/testdata/unsafe/unsafe.go b/internal/lsp/testdata/lsp/primarymod/unsafe/unsafe.go
similarity index 100%
rename from internal/lsp/testdata/unsafe/unsafe.go
rename to internal/lsp/testdata/lsp/primarymod/unsafe/unsafe.go
diff --git a/internal/lsp/testdata/variadic/variadic.go.in b/internal/lsp/testdata/lsp/primarymod/variadic/variadic.go.in
similarity index 83%
rename from internal/lsp/testdata/variadic/variadic.go.in
rename to internal/lsp/testdata/lsp/primarymod/variadic/variadic.go.in
index 8a7fa24..f715719 100644
--- a/internal/lsp/testdata/variadic/variadic.go.in
+++ b/internal/lsp/testdata/lsp/primarymod/variadic/variadic.go.in
@@ -21,3 +21,10 @@
   // snippet will add the "..." for you
 	foo(123, ) //@snippet(")", vStrSlice, "ss...", "ss..."),snippet(")", vFunc, "bar()...", "bar()..."),snippet(")", vStr, "s", "s")
 }
+
+func qux(...func()) {}
+func f()            {} //@item(vVarArg, "f", "func()", "func")
+
+func _() {
+	qux(f) //@snippet(")", vVarArg, "f", "f")
+}
diff --git a/internal/lsp/testdata/variadic/variadic_intf.go b/internal/lsp/testdata/lsp/primarymod/variadic/variadic_intf.go
similarity index 100%
rename from internal/lsp/testdata/variadic/variadic_intf.go
rename to internal/lsp/testdata/lsp/primarymod/variadic/variadic_intf.go
diff --git a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a.go b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a.go
new file mode 100644
index 0000000..040b49e
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a.go
@@ -0,0 +1,9 @@
+package a
+
+var WorkspaceSymbolVariableA = "a" //@symbol("WorkspaceSymbolVariableA", "WorkspaceSymbolVariableA", "Variable", "")
+
+const WorkspaceSymbolConstantA = "a" //@symbol("WorkspaceSymbolConstantA", "WorkspaceSymbolConstantA", "Constant", "")
+
+const (
+	workspacesymbolinvariable = iota //@symbol("workspacesymbolinvariable", "workspacesymbolinvariable", "Constant", "")
+)
diff --git a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a.go.golden b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a.go.golden
new file mode 100644
index 0000000..2a8788b
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/a/a.go.golden
@@ -0,0 +1,5 @@
+-- symbols --
+WorkspaceSymbolVariableA Variable 3:5-3:29
+WorkspaceSymbolConstantA Constant 5:7-5:31
+workspacesymbolinvariable Constant 8:2-8:27
+
diff --git a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/b/b.go b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/b/b.go
new file mode 100644
index 0000000..d7469af
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/b/b.go
@@ -0,0 +1,7 @@
+package b
+
+var WorkspaceSymbolVariableB = "b" //@symbol("WorkspaceSymbolVariableB", "WorkspaceSymbolVariableB", "Variable", "")
+
+type WorkspaceSymbolStructB struct { //@symbol("WorkspaceSymbolStructB", "WorkspaceSymbolStructB", "Struct", "")
+	Bar int //@mark(bBar, "Bar"), symbol("Bar", "Bar", "Field", "WorkspaceSymbolStructB")
+}
diff --git a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/b/b.go.golden b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/b/b.go.golden
new file mode 100644
index 0000000..ecc8781
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/b/b.go.golden
@@ -0,0 +1,5 @@
+-- symbols --
+WorkspaceSymbolVariableB Variable 3:5-3:29
+WorkspaceSymbolStructB Struct 5:6-5:28
+	Bar Field 6:2-6:5
+
diff --git a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/casesensitive/casesensitive.go b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/casesensitive/casesensitive.go
new file mode 100644
index 0000000..9b60651
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/casesensitive/casesensitive.go
@@ -0,0 +1,6 @@
+package casesensitive
+
+/*@
+workspacesymbolcasesensitive("baz", baz)
+workspacesymbolcasesensitive("Baz", Baz)
+*/
diff --git a/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/fuzzy.go b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/fuzzy.go
new file mode 100644
index 0000000..de03d36
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/workspacesymbol/fuzzy/fuzzy.go
@@ -0,0 +1,23 @@
+package fuzzy
+
+/*@
+workspacesymbolfuzzy("wsym",
+	WorkspaceSymbolVariableA,
+	WorkspaceSymbolConstantA,
+	workspacesymbolinvariable,
+	WorkspaceSymbolVariableB,
+	WorkspaceSymbolStructB,
+)
+workspacesymbolfuzzy("symbola",
+	WorkspaceSymbolVariableA,
+	WorkspaceSymbolConstantA,
+	workspacesymbolinvariable,
+	WorkspaceSymbolVariableB,
+)
+workspacesymbolfuzzy("symbolb",
+	WorkspaceSymbolVariableA,
+	workspacesymbolinvariable,
+	WorkspaceSymbolVariableB,
+	WorkspaceSymbolStructB,
+)
+*/
diff --git a/internal/lsp/testdata/lsp/summary.txt.golden b/internal/lsp/testdata/lsp/summary.txt.golden
new file mode 100644
index 0000000..f1ef410
--- /dev/null
+++ b/internal/lsp/testdata/lsp/summary.txt.golden
@@ -0,0 +1,28 @@
+-- summary --
+CodeLensCount = 0
+CompletionsCount = 226
+CompletionSnippetCount = 68
+UnimportedCompletionsCount = 11
+DeepCompletionsCount = 5
+FuzzyCompletionsCount = 8
+RankedCompletionsCount = 111
+CaseSensitiveCompletionsCount = 4
+DiagnosticsCount = 39
+FoldingRangesCount = 2
+FormatCount = 6
+ImportCount = 7
+SuggestedFixCount = 1
+DefinitionsCount = 45
+TypeDefinitionsCount = 2
+HighlightsCount = 52
+ReferencesCount = 9
+RenamesCount = 23
+PrepareRenamesCount = 7
+SymbolsCount = 3
+WorkspaceSymbolsCount = 0
+FuzzyWorkspaceSymbolsCount = 3
+CaseSensitiveWorkspaceSymbolsCount = 2
+SignaturesCount = 23
+LinksCount = 8
+ImplementationsCount = 5
+
diff --git a/internal/lsp/testdata/missingdep/modules/example.com/extramodule/pkg/x.go b/internal/lsp/testdata/missingdep/modules/example.com/extramodule/pkg/x.go
new file mode 100644
index 0000000..cf7fc67
--- /dev/null
+++ b/internal/lsp/testdata/missingdep/modules/example.com/extramodule/pkg/x.go
@@ -0,0 +1,3 @@
+package pkg
+
+const Test = 1
\ No newline at end of file
diff --git a/internal/lsp/testdata/missingdep/primarymod/go.mod b/internal/lsp/testdata/missingdep/primarymod/go.mod
new file mode 100644
index 0000000..3c64aee
--- /dev/null
+++ b/internal/lsp/testdata/missingdep/primarymod/go.mod
@@ -0,0 +1,3 @@
+module missingdep
+
+go 1.12
diff --git a/internal/lsp/testdata/missingdep/primarymod/go.mod.golden b/internal/lsp/testdata/missingdep/primarymod/go.mod.golden
new file mode 100644
index 0000000..862c051
--- /dev/null
+++ b/internal/lsp/testdata/missingdep/primarymod/go.mod.golden
@@ -0,0 +1,7 @@
+-- suggestedfix_main_5_2 --
+module missingdep
+
+go 1.12
+
+require example.com/extramodule v1.0.0
+
diff --git a/internal/lsp/testdata/missingdep/primarymod/main.go b/internal/lsp/testdata/missingdep/primarymod/main.go
new file mode 100644
index 0000000..b22d1fd
--- /dev/null
+++ b/internal/lsp/testdata/missingdep/primarymod/main.go
@@ -0,0 +1,10 @@
+// Package missingdep does something
+package missingdep
+
+import (
+	"example.com/extramodule/pkg" //@diag("\"example.com/extramodule/pkg\"", "go mod tidy", "example.com/extramodule is not in your go.mod file.", "warning"),suggestedfix("\"example.com/extramodule/pkg\"")
+)
+
+func Yo() {
+	_ = pkg.Test
+}
diff --git a/internal/lsp/testdata/missingdep/summary.txt.golden b/internal/lsp/testdata/missingdep/summary.txt.golden
new file mode 100644
index 0000000..5c4f74a
--- /dev/null
+++ b/internal/lsp/testdata/missingdep/summary.txt.golden
@@ -0,0 +1,28 @@
+-- summary --
+CodeLensCount = 0
+CompletionsCount = 0
+CompletionSnippetCount = 0
+UnimportedCompletionsCount = 0
+DeepCompletionsCount = 0
+FuzzyCompletionsCount = 0
+RankedCompletionsCount = 0
+CaseSensitiveCompletionsCount = 0
+DiagnosticsCount = 1
+FoldingRangesCount = 0
+FormatCount = 0
+ImportCount = 0
+SuggestedFixCount = 1
+DefinitionsCount = 0
+TypeDefinitionsCount = 0
+HighlightsCount = 0
+ReferencesCount = 0
+RenamesCount = 0
+PrepareRenamesCount = 0
+SymbolsCount = 0
+WorkspaceSymbolsCount = 0
+FuzzyWorkspaceSymbolsCount = 0
+CaseSensitiveWorkspaceSymbolsCount = 0
+SignaturesCount = 0
+LinksCount = 0
+ImplementationsCount = 0
+
diff --git a/internal/lsp/testdata/missingtwodep/modules/example.com/anothermodule/hey/y.go b/internal/lsp/testdata/missingtwodep/modules/example.com/anothermodule/hey/y.go
new file mode 100644
index 0000000..1957ebb
--- /dev/null
+++ b/internal/lsp/testdata/missingtwodep/modules/example.com/anothermodule/hey/y.go
@@ -0,0 +1,3 @@
+package hey
+
+const Test = 1
\ No newline at end of file
diff --git a/internal/lsp/testdata/missingtwodep/modules/example.com/extramodule/pkg/x.go b/internal/lsp/testdata/missingtwodep/modules/example.com/extramodule/pkg/x.go
new file mode 100644
index 0000000..cf7fc67
--- /dev/null
+++ b/internal/lsp/testdata/missingtwodep/modules/example.com/extramodule/pkg/x.go
@@ -0,0 +1,3 @@
+package pkg
+
+const Test = 1
\ No newline at end of file
diff --git a/internal/lsp/testdata/missingtwodep/modules/example.com/extramodule/yo/y.go b/internal/lsp/testdata/missingtwodep/modules/example.com/extramodule/yo/y.go
new file mode 100644
index 0000000..f825aa5
--- /dev/null
+++ b/internal/lsp/testdata/missingtwodep/modules/example.com/extramodule/yo/y.go
@@ -0,0 +1,3 @@
+package yo
+
+const Test = 1
\ No newline at end of file
diff --git a/internal/lsp/testdata/missingtwodep/primarymod/go.mod b/internal/lsp/testdata/missingtwodep/primarymod/go.mod
new file mode 100644
index 0000000..eba8c43
--- /dev/null
+++ b/internal/lsp/testdata/missingtwodep/primarymod/go.mod
@@ -0,0 +1,3 @@
+module missingtwodep
+
+go 1.12
diff --git a/internal/lsp/testdata/missingtwodep/primarymod/go.mod.golden b/internal/lsp/testdata/missingtwodep/primarymod/go.mod.golden
new file mode 100644
index 0000000..6be2a4a
--- /dev/null
+++ b/internal/lsp/testdata/missingtwodep/primarymod/go.mod.golden
@@ -0,0 +1,21 @@
+-- suggestedfix_main_5_2 --
+module missingtwodep
+
+go 1.12
+
+require example.com/anothermodule v1.0.0
+
+-- suggestedfix_main_6_2 --
+module missingtwodep
+
+go 1.12
+
+require example.com/extramodule v1.0.0
+
+-- suggestedfix_main_7_2 --
+module missingtwodep
+
+go 1.12
+
+require example.com/extramodule v1.0.0
+
diff --git a/internal/lsp/testdata/missingtwodep/primarymod/main.go b/internal/lsp/testdata/missingtwodep/primarymod/main.go
new file mode 100644
index 0000000..b6cdcdc
--- /dev/null
+++ b/internal/lsp/testdata/missingtwodep/primarymod/main.go
@@ -0,0 +1,14 @@
+// Package missingtwodep does something
+package missingtwodep
+
+import (
+	"example.com/anothermodule/hey" //@diag("\"example.com/anothermodule/hey\"", "go mod tidy", "example.com/anothermodule is not in your go.mod file.", "warning"),suggestedfix("\"example.com/anothermodule/hey\"")
+	"example.com/extramodule/pkg"   //@diag("\"example.com/extramodule/pkg\"", "go mod tidy", "example.com/extramodule is not in your go.mod file.", "warning"),suggestedfix("\"example.com/extramodule/pkg\"")
+	"example.com/extramodule/yo"    //@diag("\"example.com/extramodule/yo\"", "go mod tidy", "example.com/extramodule is not in your go.mod file.", "warning"),suggestedfix("\"example.com/extramodule/yo\"")
+)
+
+func Yo() {
+	_ = pkg.Test
+	_ = yo.Test
+	_ = hey.Test
+}
diff --git a/internal/lsp/testdata/missingtwodep/summary.txt.golden b/internal/lsp/testdata/missingtwodep/summary.txt.golden
new file mode 100644
index 0000000..96ac475
--- /dev/null
+++ b/internal/lsp/testdata/missingtwodep/summary.txt.golden
@@ -0,0 +1,28 @@
+-- summary --
+CodeLensCount = 0
+CompletionsCount = 0
+CompletionSnippetCount = 0
+UnimportedCompletionsCount = 0
+DeepCompletionsCount = 0
+FuzzyCompletionsCount = 0
+RankedCompletionsCount = 0
+CaseSensitiveCompletionsCount = 0
+DiagnosticsCount = 3
+FoldingRangesCount = 0
+FormatCount = 0
+ImportCount = 0
+SuggestedFixCount = 3
+DefinitionsCount = 0
+TypeDefinitionsCount = 0
+HighlightsCount = 0
+ReferencesCount = 0
+RenamesCount = 0
+PrepareRenamesCount = 0
+SymbolsCount = 0
+WorkspaceSymbolsCount = 0
+FuzzyWorkspaceSymbolsCount = 0
+CaseSensitiveWorkspaceSymbolsCount = 0
+SignaturesCount = 0
+LinksCount = 0
+ImplementationsCount = 0
+
diff --git a/internal/lsp/testdata/summary.txt.golden b/internal/lsp/testdata/summary.txt.golden
deleted file mode 100644
index 9efddc4..0000000
--- a/internal/lsp/testdata/summary.txt.golden
+++ /dev/null
@@ -1,24 +0,0 @@
--- summary --
-CompletionsCount = 226
-CompletionSnippetCount = 66
-UnimportedCompletionsCount = 9
-DeepCompletionsCount = 5
-FuzzyCompletionsCount = 8
-RankedCompletionsCount = 67
-CaseSensitiveCompletionsCount = 4
-DiagnosticsCount = 38
-FoldingRangesCount = 2
-FormatCount = 6
-ImportCount = 7
-SuggestedFixCount = 1
-DefinitionsCount = 43
-TypeDefinitionsCount = 2
-HighlightsCount = 52
-ReferencesCount = 9
-RenamesCount = 23
-PrepareRenamesCount = 8
-SymbolsCount = 1
-SignaturesCount = 23
-LinksCount = 8
-ImplementationsCount = 5
-
diff --git a/internal/lsp/testdata/unimported/unimported.go.in b/internal/lsp/testdata/unimported/unimported.go.in
deleted file mode 100644
index d4ce8b3..0000000
--- a/internal/lsp/testdata/unimported/unimported.go.in
+++ /dev/null
@@ -1,20 +0,0 @@
-package unimported
-
-func _() {
-	//@unimported("", hashslashadler32, goslashast, encodingslashbase64, bytes)
-	pkg //@unimported("g", externalpackage)
-	// container/ring is extremely unlikely to be imported by anything, so shouldn't have type information.
-	ring.Ring //@unimported("Ring", ringring)
-	signature.Foo //@unimported("Foo", signaturefoo)
-}
-
-// Create markers for unimported std lib packages. Only for use by this test.
-/* adler32 */ //@item(hashslashadler32, "adler32", "\"hash/adler32\"", "package")
-/* ast */ //@item(goslashast, "ast", "\"go/ast\"", "package")
-/* base64 */ //@item(encodingslashbase64, "base64", "\"encoding/base64\"", "package")
-/* bytes */ //@item(bytes, "bytes", "\"bytes\"", "package")
-/* pkg */ //@item(externalpackage, "pkg", "\"example.com/extramodule/pkg\"", "package")
-
-/* ring.Ring */ //@item(ringring, "Ring", "(from \"container/ring\")", "var")
-
-/* signature.Foo */ //@item(signaturefoo, "Foo", "func(a string, b int) (c bool) (from \"golang.org/x/tools/internal/lsp/signature\")", "func")
\ No newline at end of file
diff --git a/internal/lsp/testdata/unresolved/unresolved.go.in b/internal/lsp/testdata/unresolved/unresolved.go.in
deleted file mode 100644
index ceb7fe2..0000000
--- a/internal/lsp/testdata/unresolved/unresolved.go.in
+++ /dev/null
@@ -1,6 +0,0 @@
-package unresolved
-
-func foo(interface{}) { //@item(unresolvedFoo, "foo", "func(interface{})", "func")
-	// don't crash on fake "resolved" type
-	foo(func(i, j f //@complete(" //", unresolvedFoo)
-}
diff --git a/internal/lsp/testdata/unused/modules/example.com/extramodule/pkg/x.go b/internal/lsp/testdata/unused/modules/example.com/extramodule/pkg/x.go
new file mode 100644
index 0000000..cf7fc67
--- /dev/null
+++ b/internal/lsp/testdata/unused/modules/example.com/extramodule/pkg/x.go
@@ -0,0 +1,3 @@
+package pkg
+
+const Test = 1
\ No newline at end of file
diff --git a/internal/lsp/testdata/unused/primarymod/go.mod b/internal/lsp/testdata/unused/primarymod/go.mod
new file mode 100644
index 0000000..6ff23f0
--- /dev/null
+++ b/internal/lsp/testdata/unused/primarymod/go.mod
@@ -0,0 +1,5 @@
+module unused
+
+go 1.12
+
+require example.com/extramodule v1.0.0 //@diag("require example.com/extramodule v1.0.0", "go mod tidy", "example.com/extramodule is not used in this module.", "warning"),suggestedfix("require example.com/extramodule v1.0.0")
diff --git a/internal/lsp/testdata/unused/primarymod/go.mod.golden b/internal/lsp/testdata/unused/primarymod/go.mod.golden
new file mode 100644
index 0000000..d97d794
--- /dev/null
+++ b/internal/lsp/testdata/unused/primarymod/go.mod.golden
@@ -0,0 +1,5 @@
+-- suggestedfix_go.mod_5_1 --
+module unused
+
+go 1.12
+
diff --git a/internal/lsp/mod/testdata/unused/main.go b/internal/lsp/testdata/unused/primarymod/main.go
similarity index 100%
rename from internal/lsp/mod/testdata/unused/main.go
rename to internal/lsp/testdata/unused/primarymod/main.go
diff --git a/internal/lsp/testdata/unused/summary.txt.golden b/internal/lsp/testdata/unused/summary.txt.golden
new file mode 100644
index 0000000..5c4f74a
--- /dev/null
+++ b/internal/lsp/testdata/unused/summary.txt.golden
@@ -0,0 +1,28 @@
+-- summary --
+CodeLensCount = 0
+CompletionsCount = 0
+CompletionSnippetCount = 0
+UnimportedCompletionsCount = 0
+DeepCompletionsCount = 0
+FuzzyCompletionsCount = 0
+RankedCompletionsCount = 0
+CaseSensitiveCompletionsCount = 0
+DiagnosticsCount = 1
+FoldingRangesCount = 0
+FormatCount = 0
+ImportCount = 0
+SuggestedFixCount = 1
+DefinitionsCount = 0
+TypeDefinitionsCount = 0
+HighlightsCount = 0
+ReferencesCount = 0
+RenamesCount = 0
+PrepareRenamesCount = 0
+SymbolsCount = 0
+WorkspaceSymbolsCount = 0
+FuzzyWorkspaceSymbolsCount = 0
+CaseSensitiveWorkspaceSymbolsCount = 0
+SignaturesCount = 0
+LinksCount = 0
+ImplementationsCount = 0
+
diff --git a/internal/lsp/testdata/upgradedep/modules/example.com/extramodule@v1.0.0/pkg/x.go b/internal/lsp/testdata/upgradedep/modules/example.com/extramodule@v1.0.0/pkg/x.go
new file mode 100644
index 0000000..cf7fc67
--- /dev/null
+++ b/internal/lsp/testdata/upgradedep/modules/example.com/extramodule@v1.0.0/pkg/x.go
@@ -0,0 +1,3 @@
+package pkg
+
+const Test = 1
\ No newline at end of file
diff --git a/internal/lsp/testdata/upgradedep/modules/example.com/extramodule@v1.1.0/pkg/x.go b/internal/lsp/testdata/upgradedep/modules/example.com/extramodule@v1.1.0/pkg/x.go
new file mode 100644
index 0000000..cf7fc67
--- /dev/null
+++ b/internal/lsp/testdata/upgradedep/modules/example.com/extramodule@v1.1.0/pkg/x.go
@@ -0,0 +1,3 @@
+package pkg
+
+const Test = 1
\ No newline at end of file
diff --git a/internal/lsp/testdata/upgradedep/primarymod/go.mod b/internal/lsp/testdata/upgradedep/primarymod/go.mod
new file mode 100644
index 0000000..ac1ed82
--- /dev/null
+++ b/internal/lsp/testdata/upgradedep/primarymod/go.mod
@@ -0,0 +1,11 @@
+module upgradedep
+
+// TODO(microsoft/vscode-go#12): Another issue. //@link(`microsoft/vscode-go#12`, `https://github.com/microsoft/vscode-go/issues/12`)
+
+go 1.12
+
+// TODO(golang/go#1234): Link the relevant issue. //@link(`golang/go#1234`, `https://github.com/golang/go/issues/1234`)
+
+require example.com/extramodule v1.0.0 //@link(`example.com/extramodule`, `https://pkg.go.dev/mod/example.com/extramodule@v1.0.0`),codelens("require example.com/extramodule v1.0.0", "Upgrade dependency to v1.1.0", "upgrade.dependency")
+
+// https://example.com/comment: Another issue. //@link(`https://example.com/comment`,`https://example.com/comment`)
diff --git a/internal/lsp/testdata/upgradedep/primarymod/main.go b/internal/lsp/testdata/upgradedep/primarymod/main.go
new file mode 100644
index 0000000..467cbf3
--- /dev/null
+++ b/internal/lsp/testdata/upgradedep/primarymod/main.go
@@ -0,0 +1,10 @@
+// Package upgradedep does something
+package upgradedep
+
+import (
+	"example.com/extramodule/pkg"
+)
+
+func Yo() {
+	_ = pkg.Test
+}
diff --git a/internal/lsp/testdata/upgradedep/summary.txt.golden b/internal/lsp/testdata/upgradedep/summary.txt.golden
new file mode 100644
index 0000000..7ae33eb
--- /dev/null
+++ b/internal/lsp/testdata/upgradedep/summary.txt.golden
@@ -0,0 +1,28 @@
+-- summary --
+CodeLensCount = 1
+CompletionsCount = 0
+CompletionSnippetCount = 0
+UnimportedCompletionsCount = 0
+DeepCompletionsCount = 0
+FuzzyCompletionsCount = 0
+RankedCompletionsCount = 0
+CaseSensitiveCompletionsCount = 0
+DiagnosticsCount = 0
+FoldingRangesCount = 0
+FormatCount = 0
+ImportCount = 0
+SuggestedFixCount = 0
+DefinitionsCount = 0
+TypeDefinitionsCount = 0
+HighlightsCount = 0
+ReferencesCount = 0
+RenamesCount = 0
+PrepareRenamesCount = 0
+SymbolsCount = 0
+WorkspaceSymbolsCount = 0
+FuzzyWorkspaceSymbolsCount = 0
+CaseSensitiveWorkspaceSymbolsCount = 0
+SignaturesCount = 0
+LinksCount = 4
+ImplementationsCount = 0
+
diff --git a/internal/lsp/tests/completion.go b/internal/lsp/tests/completion.go
deleted file mode 100644
index ba5ec70..0000000
--- a/internal/lsp/tests/completion.go
+++ /dev/null
@@ -1,186 +0,0 @@
-package tests
-
-import (
-	"bytes"
-	"fmt"
-	"sort"
-	"strconv"
-	"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 {
-	pItem := protocol.CompletionItem{
-		Label:         item.Label,
-		Kind:          item.Kind,
-		Detail:        item.Detail,
-		Documentation: item.Documentation,
-		InsertText:    item.InsertText,
-		TextEdit: &protocol.TextEdit{
-			NewText: item.Snippet(),
-		},
-		// Negate score so best score has lowest sort text like real API.
-		SortText: fmt.Sprint(-item.Score),
-	}
-	if pItem.InsertText == "" {
-		pItem.InsertText = pItem.Label
-	}
-	return pItem
-}
-
-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.ClassCompletion {
-		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, strictScores bool) string {
-	var (
-		matchedIdxs []int
-		lastGotIdx  int
-		lastGotSort float64
-		inOrder     = true
-		errorMsg    = "completions out of order"
-	)
-	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
-
-				sort, _ := strconv.ParseFloat(g.SortText, 64)
-				if strictScores && len(matchedIdxs) > 1 && sort <= lastGotSort {
-					inOrder = false
-					errorMsg = "candidate scores not strictly decreasing"
-				}
-				lastGotSort = sort
-
-				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, errorMsg)
-	}
-
-	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
deleted file mode 100644
index 192cde4..0000000
--- a/internal/lsp/tests/diagnostics.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package tests
-
-import (
-	"bytes"
-	"fmt"
-	"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 {
-	source.SortDiagnostics(want)
-	source.SortDiagnostics(got)
-
-	if len(got) != len(want) {
-		return summarizeDiagnostics(-1, uri, 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, uri, want, got, "incorrect Message got %v want %v", g.Message, w.Message)
-		}
-		if w.Severity != g.Severity {
-			return summarizeDiagnostics(i, uri, want, got, "incorrect Severity got %v want %v", g.Severity, w.Severity)
-		}
-		if w.Source != g.Source {
-			return summarizeDiagnostics(i, uri, want, got, "incorrect Source got %v want %v", g.Source, w.Source)
-		}
-		// Don't check the range on the badimport test.
-		if strings.Contains(uri.Filename(), "badimport") {
-			continue
-		}
-		if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
-			return summarizeDiagnostics(i, uri, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
-		}
-		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, uri, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
-			}
-		}
-	}
-	return ""
-}
-
-func summarizeDiagnostics(i int, uri span.URI, 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", uri, d.Range, d.Message)
-	}
-	fmt.Fprintf(msg, "got:\n")
-	for _, d := range got {
-		fmt.Fprintf(msg, "  %s:%v: %s\n", uri, d.Range, d.Message)
-	}
-	return msg.String()
-}
diff --git a/internal/lsp/tests/links.go b/internal/lsp/tests/links.go
deleted file mode 100644
index 07fc3ef..0000000
--- a/internal/lsp/tests/links.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2019 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package tests
-
-import (
-	"fmt"
-	"go/token"
-
-	"golang.org/x/tools/internal/lsp/protocol"
-	"golang.org/x/tools/internal/span"
-)
-
-// DiffLinks takes the links we got and checks if they are located within the source or a Note.
-// If the link is within a Note, the link is removed.
-// Returns an diff comment if there are differences and empty string if no diffs
-func DiffLinks(mapper *protocol.ColumnMapper, wantLinks []Link, gotLinks []protocol.DocumentLink) string {
-	var notePositions []token.Position
-	links := make(map[span.Span]string, len(wantLinks))
-	for _, link := range wantLinks {
-		links[link.Src] = link.Target
-		notePositions = append(notePositions, link.NotePosition)
-	}
-	for _, link := range gotLinks {
-		spn, err := mapper.RangeSpan(link.Range)
-		if err != nil {
-			return fmt.Sprintf("%v", err)
-		}
-		linkInNote := false
-		for _, notePosition := range notePositions {
-			// Drop the links found inside expectation notes arguments as this links are not collected by expect package
-			if notePosition.Line == spn.Start().Line() &&
-				notePosition.Column <= spn.Start().Column() {
-				delete(links, spn)
-				linkInNote = true
-			}
-		}
-		if linkInNote {
-			continue
-		}
-		if target, ok := links[spn]; ok {
-			delete(links, spn)
-			if target != link.Target {
-				return fmt.Sprintf("for %v want %v, got %v\n", spn, link.Target, target)
-			}
-		} else {
-			return fmt.Sprintf("unexpected link %v:%v\n", spn, link.Target)
-		}
-	}
-	for spn, target := range links {
-		return fmt.Sprintf("missing link %v:%v\n", spn, target)
-	}
-	return ""
-}
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index f4331da..6e863a7 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -15,6 +15,7 @@
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"regexp"
 	"runtime"
 	"sort"
 	"strconv"
@@ -36,11 +37,13 @@
 	overlayFileSuffix = ".overlay"
 	goldenFileSuffix  = ".golden"
 	inFileSuffix      = ".in"
+	summaryFile       = "summary.txt"
 	testModule        = "golang.org/x/tools/internal/lsp"
 )
 
 var UpdateGolden = flag.Bool("golden", false, "Update golden files")
 
+type CodeLens map[span.Span][]protocol.CodeLens
 type Diagnostics map[span.URI][]source.Diagnostic
 type CompletionItems map[token.Pos]*source.CompletionItem
 type Completions map[span.Span][]Completion
@@ -62,46 +65,57 @@
 type PrepareRenames map[span.Span]*source.PrepareItem
 type Symbols map[span.URI][]protocol.DocumentSymbol
 type SymbolsChildren map[string][]protocol.DocumentSymbol
-type Signatures map[span.Span]*source.SignatureInformation
+type SymbolInformation map[span.Span]protocol.SymbolInformation
+type WorkspaceSymbols map[string][]protocol.SymbolInformation
+type Signatures map[span.Span]*protocol.SignatureHelp
 type Links map[span.URI][]Link
 
 type Data struct {
-	Config                   packages.Config
-	Exported                 *packagestest.Exported
-	Diagnostics              Diagnostics
-	CompletionItems          CompletionItems
-	Completions              Completions
-	CompletionSnippets       CompletionSnippets
-	UnimportedCompletions    UnimportedCompletions
-	DeepCompletions          DeepCompletions
-	FuzzyCompletions         FuzzyCompletions
-	CaseSensitiveCompletions CaseSensitiveCompletions
-	RankCompletions          RankCompletions
-	FoldingRanges            FoldingRanges
-	Formats                  Formats
-	Imports                  Imports
-	SuggestedFixes           SuggestedFixes
-	Definitions              Definitions
-	Implementations          Implementations
-	Highlights               Highlights
-	References               References
-	Renames                  Renames
-	PrepareRenames           PrepareRenames
-	Symbols                  Symbols
-	symbolsChildren          SymbolsChildren
-	Signatures               Signatures
-	Links                    Links
+	Config                        packages.Config
+	Exported                      *packagestest.Exported
+	CodeLens                      CodeLens
+	Diagnostics                   Diagnostics
+	CompletionItems               CompletionItems
+	Completions                   Completions
+	CompletionSnippets            CompletionSnippets
+	UnimportedCompletions         UnimportedCompletions
+	DeepCompletions               DeepCompletions
+	FuzzyCompletions              FuzzyCompletions
+	CaseSensitiveCompletions      CaseSensitiveCompletions
+	RankCompletions               RankCompletions
+	FoldingRanges                 FoldingRanges
+	Formats                       Formats
+	Imports                       Imports
+	SuggestedFixes                SuggestedFixes
+	Definitions                   Definitions
+	Implementations               Implementations
+	Highlights                    Highlights
+	References                    References
+	Renames                       Renames
+	PrepareRenames                PrepareRenames
+	Symbols                       Symbols
+	symbolsChildren               SymbolsChildren
+	symbolInformation             SymbolInformation
+	WorkspaceSymbols              WorkspaceSymbols
+	FuzzyWorkspaceSymbols         WorkspaceSymbols
+	CaseSensitiveWorkspaceSymbols WorkspaceSymbols
+	Signatures                    Signatures
+	Links                         Links
 
 	t         testing.TB
 	fragments map[string]string
 	dir       string
+	Folder    string
 	golden    map[string]*Golden
 
+	ModfileFlagAvailable bool
+
 	mappersMu sync.Mutex
 	mappers   map[span.URI]*protocol.ColumnMapper
 }
 
 type Tests interface {
+	CodeLens(*testing.T, span.Span, []protocol.CodeLens)
 	Diagnostics(*testing.T, span.URI, []source.Diagnostic)
 	Completion(*testing.T, span.Span, Completion, CompletionItems)
 	CompletionSnippet(*testing.T, span.Span, CompletionSnippet, bool, CompletionItems)
@@ -121,7 +135,10 @@
 	Rename(*testing.T, span.Span, string)
 	PrepareRename(*testing.T, span.Span, *source.PrepareItem)
 	Symbols(*testing.T, span.URI, []protocol.DocumentSymbol)
-	SignatureHelp(*testing.T, span.Span, *source.SignatureInformation)
+	WorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{})
+	FuzzyWorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{})
+	CaseSensitiveWorkspaceSymbols(*testing.T, string, []protocol.SymbolInformation, map[string]struct{})
+	SignatureHelp(*testing.T, span.Span, *protocol.SignatureHelp)
 	Link(*testing.T, span.URI, []Link)
 }
 
@@ -147,13 +164,26 @@
 	// Fuzzy tests deep completion and fuzzy matching.
 	CompletionFuzzy
 
-	// CaseSensitive tests case sensitive completion
+	// CaseSensitive tests case sensitive completion.
 	CompletionCaseSensitive
 
 	// CompletionRank candidates in test must be valid and in the right relative order.
 	CompletionRank
 )
 
+type WorkspaceSymbolsTestType int
+
+const (
+	// Default runs the standard workspace symbols tests.
+	WorkspaceSymbolsDefault = WorkspaceSymbolsTestType(iota)
+
+	// Fuzzy tests workspace symbols with fuzzy matching.
+	WorkspaceSymbolsFuzzy
+
+	// CaseSensitive tests workspace symbols with case sensitive.
+	WorkspaceSymbolsCaseSensitive
+)
+
 type Completion struct {
 	CompletionItems []token.Pos
 }
@@ -181,7 +211,7 @@
 }
 
 func DefaultOptions() source.Options {
-	o := source.DefaultOptions
+	o := source.DefaultOptions()
 	o.SupportedCodeActions = map[source.FileKind]map[protocol.CodeActionKind]bool{
 		source.Go: {
 			protocol.SourceOrganizeImports: true,
@@ -200,145 +230,208 @@
 
 var haveCgo = false
 
-func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
+// For Load() to properly create the folder structure required when testing with modules.
+// The directory structure of a test needs to look like the example below:
+//
+// - dir
+// 	 - primarymod
+// 		 - .go files
+// 		 - packages
+// 		 - go.mod (optional)
+// 	 - modules
+//		 - repoa
+//			 - mod1
+//				 - .go files
+//				 -  packages
+//				 - go.mod (optional)
+//			 - mod2
+//		 - repob
+//			 - mod1
+//
+// All the files that are primarily being tested should be in the primarymod folder,
+// any auxillary packages should be declared in the modules folder.
+// The modules folder requires each module to have the following format: repo/module
+// Then inside each repo/module, there can be any number of packages and files that are
+// needed to test the primarymod.
+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),
-		UnimportedCompletions:    make(UnimportedCompletions),
-		DeepCompletions:          make(DeepCompletions),
-		FuzzyCompletions:         make(FuzzyCompletions),
-		RankCompletions:          make(RankCompletions),
-		CaseSensitiveCompletions: make(CaseSensitiveCompletions),
-		Definitions:              make(Definitions),
-		Implementations:          make(Implementations),
-		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,
-		fragments: map[string]string{},
-		golden:    map[string]*Golden{},
-		mappers:   map[span.URI]*protocol.ColumnMapper{},
+	folders, err := testFolders(dir)
+	if err != nil {
+		t.Fatalf("could not get test folders for %v, %v", dir, err)
 	}
 
-	files := packagestest.MustCopyFileTree(dir)
-	overlays := map[string][]byte{}
-	for fragment, operation := range files {
-		if trimmed := strings.TrimSuffix(fragment, goldenFileSuffix); trimmed != fragment {
-			delete(files, fragment)
-			goldFile := filepath.Join(dir, fragment)
-			archive, err := txtar.ParseFile(goldFile)
-			if err != nil {
-				t.Fatalf("could not read golden file %v: %v", fragment, err)
+	var data []*Data
+	for _, folder := range folders {
+		datum := &Data{
+			CodeLens:                      make(CodeLens),
+			Diagnostics:                   make(Diagnostics),
+			CompletionItems:               make(CompletionItems),
+			Completions:                   make(Completions),
+			CompletionSnippets:            make(CompletionSnippets),
+			UnimportedCompletions:         make(UnimportedCompletions),
+			DeepCompletions:               make(DeepCompletions),
+			FuzzyCompletions:              make(FuzzyCompletions),
+			RankCompletions:               make(RankCompletions),
+			CaseSensitiveCompletions:      make(CaseSensitiveCompletions),
+			Definitions:                   make(Definitions),
+			Implementations:               make(Implementations),
+			Highlights:                    make(Highlights),
+			References:                    make(References),
+			Renames:                       make(Renames),
+			PrepareRenames:                make(PrepareRenames),
+			Symbols:                       make(Symbols),
+			symbolsChildren:               make(SymbolsChildren),
+			symbolInformation:             make(SymbolInformation),
+			WorkspaceSymbols:              make(WorkspaceSymbols),
+			FuzzyWorkspaceSymbols:         make(WorkspaceSymbols),
+			CaseSensitiveWorkspaceSymbols: make(WorkspaceSymbols),
+			Signatures:                    make(Signatures),
+			Links:                         make(Links),
+
+			t:         t,
+			dir:       folder,
+			Folder:    folder,
+			fragments: map[string]string{},
+			golden:    map[string]*Golden{},
+			mappers:   map[span.URI]*protocol.ColumnMapper{},
+		}
+
+		if !*UpdateGolden {
+			summary := filepath.Join(filepath.FromSlash(folder), summaryFile+goldenFileSuffix)
+			if _, err := os.Stat(summary); os.IsNotExist(err) {
+				t.Fatalf("could not find golden file summary.txt in %#v", folder)
 			}
-			data.golden[trimmed] = &Golden{
-				Filename: goldFile,
+			archive, err := txtar.ParseFile(summary)
+			if err != nil {
+				t.Fatalf("could not read golden file %v/%v: %v", folder, summary, err)
+			}
+			datum.golden[summaryFile] = &Golden{
+				Filename: summary,
 				Archive:  archive,
 			}
-		} else if trimmed := strings.TrimSuffix(fragment, inFileSuffix); trimmed != fragment {
-			delete(files, fragment)
-			files[trimmed] = operation
-		} else if index := strings.Index(fragment, overlayFileSuffix); index >= 0 {
-			delete(files, fragment)
-			partial := fragment[:index] + fragment[index+len(overlayFileSuffix):]
-			contents, err := ioutil.ReadFile(filepath.Join(dir, fragment))
-			if err != nil {
-				t.Fatal(err)
+		}
+
+		modules, _ := packagestest.GroupFilesByModules(folder)
+		for i, m := range modules {
+			for fragment, operation := range m.Files {
+				if trimmed := strings.TrimSuffix(fragment, goldenFileSuffix); trimmed != fragment {
+					delete(m.Files, fragment)
+					goldFile := filepath.Join(m.Name, fragment)
+					if i == 0 {
+						goldFile = filepath.Join(m.Name, "primarymod", fragment)
+					}
+					archive, err := txtar.ParseFile(goldFile)
+					if err != nil {
+						t.Fatalf("could not read golden file %v: %v", fragment, err)
+					}
+					datum.golden[trimmed] = &Golden{
+						Filename: goldFile,
+						Archive:  archive,
+					}
+				} else if trimmed := strings.TrimSuffix(fragment, inFileSuffix); trimmed != fragment {
+					delete(m.Files, fragment)
+					m.Files[trimmed] = operation
+				} else if index := strings.Index(fragment, overlayFileSuffix); index >= 0 {
+					delete(m.Files, fragment)
+					partial := fragment[:index] + fragment[index+len(overlayFileSuffix):]
+					overlayFile := filepath.Join(m.Name, fragment)
+					if i == 0 {
+						overlayFile = filepath.Join(m.Name, "primarymod", fragment)
+					}
+					contents, err := ioutil.ReadFile(overlayFile)
+					if err != nil {
+						t.Fatal(err)
+					}
+					m.Overlay[partial] = contents
+				}
 			}
-			overlays[partial] = contents
 		}
-	}
-	modules := []packagestest.Module{
-		{
-			Name:    testModule,
-			Files:   files,
-			Overlay: overlays,
-		},
-		{
-			Name: "example.com/extramodule",
-			Files: map[string]interface{}{
-				"pkg/x.go": "package pkg\n",
+		if len(modules) > 0 {
+			// For certain LSP related tests to run, make sure that the primary
+			// module for the passed in directory is testModule.
+			modules[0].Name = testModule
+		}
+		// Add exampleModule to provide tests with another pkg.
+		datum.Exported = packagestest.Export(t, exporter, modules)
+		for _, m := range modules {
+			for fragment := range m.Files {
+				filename := datum.Exported.File(m.Name, fragment)
+				datum.fragments[filename] = fragment
+			}
+		}
+
+		// Turn off go/packages debug logging.
+		datum.Exported.Config.Logf = nil
+		datum.Config.Logf = nil
+
+		// Merge the exported.Config with the view.Config.
+		datum.Config = *datum.Exported.Config
+		datum.Config.Fset = token.NewFileSet()
+		datum.Config.Context = Context(nil)
+		datum.Config.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
+			panic("ParseFile should not be called")
+		}
+
+		// Do a first pass to collect special markers for completion and workspace symbols.
+		if err := datum.Exported.Expect(map[string]interface{}{
+			"item": func(name string, r packagestest.Range, _ []string) {
+				datum.Exported.Mark(name, r)
 			},
-		},
-	}
-	data.Exported = packagestest.Export(t, exporter, modules)
-	for fragment := range files {
-		filename := data.Exported.File(testModule, fragment)
-		data.fragments[filename] = fragment
-	}
-
-	// Turn off go/packages debug logging.
-	data.Exported.Config.Logf = nil
-	data.Config.Logf = nil
-
-	// Merge the exported.Config with the view.Config.
-	data.Config = *data.Exported.Config
-	data.Config.Fset = token.NewFileSet()
-	data.Config.Context = Context(nil)
-	data.Config.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
-		panic("ParseFile should not be called")
-	}
-
-	// Do a first pass to collect special markers for completion.
-	if err := data.Exported.Expect(map[string]interface{}{
-		"item": func(name string, r packagestest.Range, _ []string) {
-			data.Exported.Mark(name, r)
-		},
-	}); err != nil {
-		t.Fatal(err)
-	}
-
-	// 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(CompletionDefault),
-		"unimported":      data.collectCompletions(CompletionUnimported),
-		"deep":            data.collectCompletions(CompletionDeep),
-		"fuzzy":           data.collectCompletions(CompletionFuzzy),
-		"casesensitive":   data.collectCompletions(CompletionCaseSensitive),
-		"rank":            data.collectCompletions(CompletionRank),
-		"snippet":         data.collectCompletionSnippets,
-		"fold":            data.collectFoldingRanges,
-		"format":          data.collectFormats,
-		"import":          data.collectImports,
-		"godef":           data.collectDefinitions,
-		"implementations": data.collectImplementations,
-		"typdef":          data.collectTypeDefinitions,
-		"hover":           data.collectHoverDefinitions,
-		"highlight":       data.collectHighlights,
-		"refs":            data.collectReferences,
-		"rename":          data.collectRenames,
-		"prepare":         data.collectPrepareRenames,
-		"symbol":          data.collectSymbols,
-		"signature":       data.collectSignatures,
-		"link":            data.collectLinks,
-		"suggestedfix":    data.collectSuggestedFixes,
-	}); err != nil {
-		t.Fatal(err)
-	}
-	for _, symbols := range data.Symbols {
-		for i := range symbols {
-			children := data.symbolsChildren[symbols[i].Name]
-			symbols[i].Children = children
+			"symbol": func(name string, r packagestest.Range, _ []string) {
+				datum.Exported.Mark(name, r)
+			},
+		}); err != nil {
+			t.Fatal(err)
 		}
-	}
-	// Collect names for the entries that require golden files.
-	if err := data.Exported.Expect(map[string]interface{}{
-		"godef": data.collectDefinitionNames,
-		"hover": data.collectDefinitionNames,
-	}); err != nil {
-		t.Fatal(err)
+
+		// Collect any data that needs to be used by subsequent tests.
+		if err := datum.Exported.Expect(map[string]interface{}{
+			"codelens":        datum.collectCodeLens,
+			"diag":            datum.collectDiagnostics,
+			"item":            datum.collectCompletionItems,
+			"complete":        datum.collectCompletions(CompletionDefault),
+			"unimported":      datum.collectCompletions(CompletionUnimported),
+			"deep":            datum.collectCompletions(CompletionDeep),
+			"fuzzy":           datum.collectCompletions(CompletionFuzzy),
+			"casesensitive":   datum.collectCompletions(CompletionCaseSensitive),
+			"rank":            datum.collectCompletions(CompletionRank),
+			"snippet":         datum.collectCompletionSnippets,
+			"fold":            datum.collectFoldingRanges,
+			"format":          datum.collectFormats,
+			"import":          datum.collectImports,
+			"godef":           datum.collectDefinitions,
+			"implementations": datum.collectImplementations,
+			"typdef":          datum.collectTypeDefinitions,
+			"hover":           datum.collectHoverDefinitions,
+			"highlight":       datum.collectHighlights,
+			"refs":            datum.collectReferences,
+			"rename":          datum.collectRenames,
+			"prepare":         datum.collectPrepareRenames,
+			"symbol":          datum.collectSymbols,
+			"signature":       datum.collectSignatures,
+			"link":            datum.collectLinks,
+			"suggestedfix":    datum.collectSuggestedFixes,
+		}); err != nil {
+			t.Fatal(err)
+		}
+		for _, symbols := range datum.Symbols {
+			for i := range symbols {
+				children := datum.symbolsChildren[symbols[i].Name]
+				symbols[i].Children = children
+			}
+		}
+		// Collect names for the entries that require golden files.
+		if err := datum.Exported.Expect(map[string]interface{}{
+			"godef":                        datum.collectDefinitionNames,
+			"hover":                        datum.collectDefinitionNames,
+			"workspacesymbol":              datum.collectWorkspaceSymbols(WorkspaceSymbolsDefault),
+			"workspacesymbolfuzzy":         datum.collectWorkspaceSymbols(WorkspaceSymbolsFuzzy),
+			"workspacesymbolcasesensitive": datum.collectWorkspaceSymbols(WorkspaceSymbolsCaseSensitive),
+		}); err != nil {
+			t.Fatal(err)
+		}
+		data = append(data, datum)
 	}
 	return data
 }
@@ -352,7 +445,7 @@
 
 		for src, exp := range cases {
 			for i, e := range exp {
-				t.Run(spanName(src)+"_"+strconv.Itoa(i), func(t *testing.T) {
+				t.Run(SpanName(src)+"_"+strconv.Itoa(i), func(t *testing.T) {
 					t.Helper()
 					if (!haveCgo || runtime.GOOS == "android") && strings.Contains(t.Name(), "cgo") {
 						t.Skip("test requires cgo, not supported")
@@ -364,6 +457,28 @@
 		}
 	}
 
+	eachWorkspaceSymbols := func(t *testing.T, cases map[string][]protocol.SymbolInformation, test func(*testing.T, string, []protocol.SymbolInformation, map[string]struct{})) {
+		t.Helper()
+
+		for query, expectedSymbols := range cases {
+			name := query
+			if name == "" {
+				name = "EmptyQuery"
+			}
+			t.Run(name, func(t *testing.T) {
+				t.Helper()
+				dirs := make(map[string]struct{})
+				for _, si := range expectedSymbols {
+					d := filepath.Dir(si.Location.URI.SpanURI().Filename())
+					if _, ok := dirs[d]; !ok {
+						dirs[d] = struct{}{}
+					}
+				}
+				test(t, query, expectedSymbols, dirs)
+			})
+		}
+	}
+
 	t.Run("Completion", func(t *testing.T) {
 		t.Helper()
 		eachCompletion(t, data.Completions, tests.Completion)
@@ -374,7 +489,7 @@
 		for _, placeholders := range []bool{true, false} {
 			for src, expecteds := range data.CompletionSnippets {
 				for i, expected := range expecteds {
-					name := spanName(src) + "_" + strconv.Itoa(i+1)
+					name := SpanName(src) + "_" + strconv.Itoa(i+1)
 					if placeholders {
 						name += "_placeholders"
 					}
@@ -413,9 +528,27 @@
 		eachCompletion(t, data.RankCompletions, tests.RankCompletion)
 	})
 
+	t.Run("CodeLens", func(t *testing.T) {
+		t.Helper()
+		for spn, want := range data.CodeLens {
+			// Check if we should skip this URI if the -modfile flag is not available.
+			if shouldSkip(data, spn.URI()) {
+				continue
+			}
+			t.Run(SpanName(spn), func(t *testing.T) {
+				t.Helper()
+				tests.CodeLens(t, spn, want)
+			})
+		}
+	})
+
 	t.Run("Diagnostics", func(t *testing.T) {
 		t.Helper()
 		for uri, want := range data.Diagnostics {
+			// Check if we should skip this URI if the -modfile flag is not available.
+			if shouldSkip(data, uri) {
+				continue
+			}
 			t.Run(uriName(uri), func(t *testing.T) {
 				t.Helper()
 				tests.Diagnostics(t, uri, want)
@@ -456,7 +589,11 @@
 	t.Run("SuggestedFix", func(t *testing.T) {
 		t.Helper()
 		for _, spn := range data.SuggestedFixes {
-			t.Run(spanName(spn), func(t *testing.T) {
+			// Check if we should skip this spn if the -modfile flag is not available.
+			if shouldSkip(data, spn.URI()) {
+				continue
+			}
+			t.Run(SpanName(spn), func(t *testing.T) {
 				t.Helper()
 				tests.SuggestedFix(t, spn)
 			})
@@ -466,7 +603,7 @@
 	t.Run("Definition", func(t *testing.T) {
 		t.Helper()
 		for spn, d := range data.Definitions {
-			t.Run(spanName(spn), func(t *testing.T) {
+			t.Run(SpanName(spn), func(t *testing.T) {
 				t.Helper()
 				if (!haveCgo || runtime.GOOS == "android") && strings.Contains(t.Name(), "cgo") {
 					t.Skip("test requires cgo, not supported")
@@ -479,7 +616,7 @@
 	t.Run("Implementation", func(t *testing.T) {
 		t.Helper()
 		for spn, m := range data.Implementations {
-			t.Run(spanName(spn), func(t *testing.T) {
+			t.Run(SpanName(spn), func(t *testing.T) {
 				t.Helper()
 				tests.Implementation(t, spn, m)
 			})
@@ -489,7 +626,7 @@
 	t.Run("Highlight", func(t *testing.T) {
 		t.Helper()
 		for pos, locations := range data.Highlights {
-			t.Run(spanName(pos), func(t *testing.T) {
+			t.Run(SpanName(pos), func(t *testing.T) {
 				t.Helper()
 				tests.Highlight(t, pos, locations)
 			})
@@ -499,7 +636,7 @@
 	t.Run("References", func(t *testing.T) {
 		t.Helper()
 		for src, itemList := range data.References {
-			t.Run(spanName(src), func(t *testing.T) {
+			t.Run(SpanName(src), func(t *testing.T) {
 				t.Helper()
 				tests.References(t, src, itemList)
 			})
@@ -519,7 +656,7 @@
 	t.Run("PrepareRenames", func(t *testing.T) {
 		t.Helper()
 		for src, want := range data.PrepareRenames {
-			t.Run(spanName(src), func(t *testing.T) {
+			t.Run(SpanName(src), func(t *testing.T) {
 				t.Helper()
 				tests.PrepareRename(t, src, want)
 			})
@@ -536,10 +673,25 @@
 		}
 	})
 
+	t.Run("WorkspaceSymbols", func(t *testing.T) {
+		t.Helper()
+		eachWorkspaceSymbols(t, data.WorkspaceSymbols, tests.WorkspaceSymbols)
+	})
+
+	t.Run("FuzzyWorkspaceSymbols", func(t *testing.T) {
+		t.Helper()
+		eachWorkspaceSymbols(t, data.FuzzyWorkspaceSymbols, tests.FuzzyWorkspaceSymbols)
+	})
+
+	t.Run("CaseSensitiveWorkspaceSymbols", func(t *testing.T) {
+		t.Helper()
+		eachWorkspaceSymbols(t, data.CaseSensitiveWorkspaceSymbols, tests.CaseSensitiveWorkspaceSymbols)
+	})
+
 	t.Run("SignatureHelp", func(t *testing.T) {
 		t.Helper()
 		for spn, expectedSignature := range data.Signatures {
-			t.Run(spanName(spn), func(t *testing.T) {
+			t.Run(SpanName(spn), func(t *testing.T) {
 				t.Helper()
 				tests.SignatureHelp(t, spn, expectedSignature)
 			})
@@ -549,6 +701,18 @@
 	t.Run("Link", func(t *testing.T) {
 		t.Helper()
 		for uri, wantLinks := range data.Links {
+			// If we are testing GOPATH, then we do not want links with
+			// the versions attached (pkg.go.dev/repoa/moda@v1.1.0/pkg),
+			// unless the file is a go.mod, then we can skip it alltogether.
+			if data.Exported.Exporter == packagestest.GOPATH {
+				if strings.HasSuffix(uri.Filename(), ".mod") {
+					continue
+				}
+				re := regexp.MustCompile(`@v\d+\.\d+\.[\w-]+`)
+				for i, link := range wantLinks {
+					wantLinks[i].Target = re.ReplaceAllString(link.Target, "")
+				}
+			}
 			t.Run(uriName(uri), func(t *testing.T) {
 				t.Helper()
 				tests.Link(t, uri, wantLinks)
@@ -603,6 +767,14 @@
 		return count
 	}
 
+	countCodeLens := func(c map[span.Span][]protocol.CodeLens) (count int) {
+		for _, want := range c {
+			count += len(want)
+		}
+		return count
+	}
+
+	fmt.Fprintf(buf, "CodeLensCount = %v\n", countCodeLens(data.CodeLens))
 	fmt.Fprintf(buf, "CompletionsCount = %v\n", countCompletions(data.Completions))
 	fmt.Fprintf(buf, "CompletionSnippetCount = %v\n", snippetCount)
 	fmt.Fprintf(buf, "UnimportedCompletionsCount = %v\n", countCompletions(data.UnimportedCompletions))
@@ -622,11 +794,14 @@
 	fmt.Fprintf(buf, "RenamesCount = %v\n", len(data.Renames))
 	fmt.Fprintf(buf, "PrepareRenamesCount = %v\n", len(data.PrepareRenames))
 	fmt.Fprintf(buf, "SymbolsCount = %v\n", len(data.Symbols))
+	fmt.Fprintf(buf, "WorkspaceSymbolsCount = %v\n", len(data.WorkspaceSymbols))
+	fmt.Fprintf(buf, "FuzzyWorkspaceSymbolsCount = %v\n", len(data.FuzzyWorkspaceSymbols))
+	fmt.Fprintf(buf, "CaseSensitiveWorkspaceSymbolsCount = %v\n", len(data.CaseSensitiveWorkspaceSymbols))
 	fmt.Fprintf(buf, "SignaturesCount = %v\n", len(data.Signatures))
 	fmt.Fprintf(buf, "LinksCount = %v\n", linksCount)
 	fmt.Fprintf(buf, "ImplementationsCount = %v\n", len(data.Implementations))
 
-	want := string(data.Golden("summary", "summary.txt", func() ([]byte, error) {
+	want := string(data.Golden("summary", summaryFile, func() ([]byte, error) {
 		return buf.Bytes(), nil
 	}))
 	got := buf.String()
@@ -668,8 +843,12 @@
 		if !*UpdateGolden {
 			data.t.Fatalf("could not find golden file %v: %v", fragment, tag)
 		}
+		var subdir string
+		if fragment != summaryFile {
+			subdir = "primarymod"
+		}
 		golden = &Golden{
-			Filename: filepath.Join(data.dir, fragment+goldenFileSuffix),
+			Filename: filepath.Join(data.dir, subdir, fragment+goldenFileSuffix),
 			Archive:  &txtar.Archive{},
 			Modified: true,
 		}
@@ -703,27 +882,53 @@
 	return file.Data[:len(file.Data)-1] // drop the trailing \n
 }
 
-func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg string) {
+func (data *Data) collectCodeLens(spn span.Span, title, cmd string) {
+	if _, ok := data.CodeLens[spn]; !ok {
+		data.CodeLens[spn] = []protocol.CodeLens{}
+	}
+	m, err := data.Mapper(spn.URI())
+	if err != nil {
+		return
+	}
+	rng, err := m.Range(spn)
+	if err != nil {
+		return
+	}
+	data.CodeLens[spn] = append(data.CodeLens[spn], protocol.CodeLens{
+		Range: rng,
+		Command: protocol.Command{
+			Title:   title,
+			Command: cmd,
+		},
+	})
+}
+
+func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg, msgSeverity string) {
 	if _, ok := data.Diagnostics[spn.URI()]; !ok {
 		data.Diagnostics[spn.URI()] = []source.Diagnostic{}
 	}
-	severity := protocol.SeverityError
-	if strings.Contains(string(spn.URI()), "analyzer") {
-		severity = protocol.SeverityWarning
+	m, err := data.Mapper(spn.URI())
+	if err != nil {
+		return
 	}
-	// This is not the correct way to do this,
-	// but it seems excessive to do the full conversion here.
+	rng, err := m.Range(spn)
+	if err != nil {
+		return
+	}
+	severity := protocol.SeverityError
+	switch msgSeverity {
+	case "error":
+		severity = protocol.SeverityError
+	case "warning":
+		severity = protocol.SeverityWarning
+	case "hint":
+		severity = protocol.SeverityHint
+	case "information":
+		severity = protocol.SeverityInformation
+	}
+	// This is not the correct way to do this, but it seems excessive to do the full conversion here.
 	want := source.Diagnostic{
-		Range: protocol.Range{
-			Start: protocol.Position{
-				Line:      float64(spn.Start().Line()) - 1,
-				Character: float64(spn.Start().Column()) - 1,
-			},
-			End: protocol.Position{
-				Line:      float64(spn.End().Line()) - 1,
-				Character: float64(spn.End().Column()) - 1,
-			},
-		},
+		Range:    rng,
 		Severity: severity,
 		Source:   msgSource,
 		Message:  msg,
@@ -885,12 +1090,50 @@
 	} else {
 		data.symbolsChildren[parentName] = append(data.symbolsChildren[parentName], sym)
 	}
+
+	// Reuse @symbol in the workspace symbols tests.
+	si := protocol.SymbolInformation{
+		Name: sym.Name,
+		Kind: sym.Kind,
+		Location: protocol.Location{
+			URI:   protocol.URIFromSpanURI(spn.URI()),
+			Range: sym.SelectionRange,
+		},
+	}
+	data.symbolInformation[spn] = si
+}
+
+func (data *Data) collectWorkspaceSymbols(typ WorkspaceSymbolsTestType) func(string, []span.Span) {
+	switch typ {
+	case WorkspaceSymbolsFuzzy:
+		return func(query string, targets []span.Span) {
+			for _, target := range targets {
+				data.FuzzyWorkspaceSymbols[query] = append(data.FuzzyWorkspaceSymbols[query], data.symbolInformation[target])
+			}
+		}
+	case WorkspaceSymbolsCaseSensitive:
+		return func(query string, targets []span.Span) {
+			for _, target := range targets {
+				data.CaseSensitiveWorkspaceSymbols[query] = append(data.CaseSensitiveWorkspaceSymbols[query], data.symbolInformation[target])
+			}
+		}
+	default:
+		return func(query string, targets []span.Span) {
+			for _, target := range targets {
+				data.WorkspaceSymbols[query] = append(data.WorkspaceSymbols[query], data.symbolInformation[target])
+			}
+		}
+	}
 }
 
 func (data *Data) collectSignatures(spn span.Span, signature string, activeParam int64) {
-	data.Signatures[spn] = &source.SignatureInformation{
-		Label:           signature,
-		ActiveParameter: int(activeParam),
+	data.Signatures[spn] = &protocol.SignatureHelp{
+		Signatures: []protocol.SignatureInformation{
+			{
+				Label: signature,
+			},
+		},
+		ActiveParameter: float64(activeParam),
 	}
 	// Hardcode special case to test the lack of a signature.
 	if signature == "" && activeParam == 0 {
@@ -920,7 +1163,7 @@
 	return filepath.Base(strings.TrimSuffix(uri.Filename(), ".go"))
 }
 
-func spanName(spn span.Span) string {
+func SpanName(spn span.Span) string {
 	return fmt.Sprintf("%v_%v_%v", uriName(spn.URI()), spn.Start().Line(), spn.Start().Column())
 }
 
@@ -955,3 +1198,40 @@
 	}
 	return dst, nil
 }
+
+func testFolders(root string) ([]string, error) {
+	// Check if this only has one test directory.
+	if _, err := os.Stat(filepath.Join(filepath.FromSlash(root), "primarymod")); !os.IsNotExist(err) {
+		return []string{root}, nil
+	}
+	folders := []string{}
+	root = filepath.FromSlash(root)
+	// Get all test directories that are one level deeper than root.
+	if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
+		if !info.IsDir() {
+			return nil
+		}
+		if filepath.Dir(path) == root {
+			folders = append(folders, filepath.ToSlash(path))
+		}
+		return nil
+	}); err != nil {
+		return nil, err
+	}
+	return folders, nil
+}
+
+func shouldSkip(data *Data, uri span.URI) bool {
+	if data.ModfileFlagAvailable {
+		return false
+	}
+	// If the -modfile flag is not available, then we do not want to run
+	// any tests on the go.mod file.
+	if strings.HasSuffix(uri.Filename(), ".mod") {
+		return true
+	}
+	// If the -modfile flag is not available, then we do not want to test any
+	// uri that contains "go mod tidy".
+	m, err := data.Mapper(uri)
+	return err == nil && strings.Contains(string(m.Content), ", \"go mod tidy\",")
+}
diff --git a/internal/lsp/tests/util.go b/internal/lsp/tests/util.go
new file mode 100644
index 0000000..b81cfd0
--- /dev/null
+++ b/internal/lsp/tests/util.go
@@ -0,0 +1,496 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tests
+
+import (
+	"bytes"
+	"fmt"
+	"go/token"
+	"path/filepath"
+	"sort"
+	"strconv"
+	"strings"
+	"testing"
+
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
+	"golang.org/x/tools/internal/span"
+)
+
+// DiffLinks takes the links we got and checks if they are located within the source or a Note.
+// If the link is within a Note, the link is removed.
+// Returns an diff comment if there are differences and empty string if no diffs.
+func DiffLinks(mapper *protocol.ColumnMapper, wantLinks []Link, gotLinks []protocol.DocumentLink) string {
+	var notePositions []token.Position
+	links := make(map[span.Span]string, len(wantLinks))
+	for _, link := range wantLinks {
+		links[link.Src] = link.Target
+		notePositions = append(notePositions, link.NotePosition)
+	}
+	for _, link := range gotLinks {
+		spn, err := mapper.RangeSpan(link.Range)
+		if err != nil {
+			return fmt.Sprintf("%v", err)
+		}
+		linkInNote := false
+		for _, notePosition := range notePositions {
+			// Drop the links found inside expectation notes arguments as this links are not collected by expect package.
+			if notePosition.Line == spn.Start().Line() &&
+				notePosition.Column <= spn.Start().Column() {
+				delete(links, spn)
+				linkInNote = true
+			}
+		}
+		if linkInNote {
+			continue
+		}
+		if target, ok := links[spn]; ok {
+			delete(links, spn)
+			if target != link.Target {
+				return fmt.Sprintf("for %v want %v, got %v\n", spn, target, link.Target)
+			}
+		} else {
+			return fmt.Sprintf("unexpected link %v:%v\n", spn, link.Target)
+		}
+	}
+	for spn, target := range links {
+		return fmt.Sprintf("missing link %v:%v\n", spn, target)
+	}
+	return ""
+}
+
+// DiffSymbols prints the diff between expected and actual symbols test results.
+func DiffSymbols(t *testing.T, uri span.URI, want, got []protocol.DocumentSymbol) string {
+	sort.Slice(want, func(i, j int) bool { return want[i].Name < want[j].Name })
+	sort.Slice(got, func(i, j int) bool { return got[i].Name < got[j].Name })
+	if len(got) != len(want) {
+		return summarizeSymbols(t, -1, want, got, "different lengths got %v want %v", len(got), len(want))
+	}
+	for i, w := range want {
+		g := got[i]
+		if w.Name != g.Name {
+			return summarizeSymbols(t, i, want, got, "incorrect name got %v want %v", g.Name, w.Name)
+		}
+		if w.Kind != g.Kind {
+			return summarizeSymbols(t, i, want, got, "incorrect kind got %v want %v", g.Kind, w.Kind)
+		}
+		if protocol.CompareRange(w.SelectionRange, g.SelectionRange) != 0 {
+			return summarizeSymbols(t, i, want, got, "incorrect span got %v want %v", g.SelectionRange, w.SelectionRange)
+		}
+		if msg := DiffSymbols(t, uri, w.Children, g.Children); msg != "" {
+			return fmt.Sprintf("children of %s: %s", w.Name, msg)
+		}
+	}
+	return ""
+}
+
+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 {
+		fmt.Fprintf(msg, " at %d", i)
+	}
+	fmt.Fprint(msg, " because of ")
+	fmt.Fprintf(msg, reason, args...)
+	fmt.Fprint(msg, ":\nexpected:\n")
+	for _, s := range want {
+		fmt.Fprintf(msg, "  %v %v %v\n", s.Name, s.Kind, s.SelectionRange)
+	}
+	fmt.Fprintf(msg, "got:\n")
+	for _, s := range got {
+		fmt.Fprintf(msg, "  %v %v %v\n", s.Name, s.Kind, s.SelectionRange)
+	}
+	return msg.String()
+}
+
+// FilterWorkspaceSymbols filters to got contained in the given dirs.
+func FilterWorkspaceSymbols(got []protocol.SymbolInformation, dirs map[string]struct{}) []protocol.SymbolInformation {
+	var result []protocol.SymbolInformation
+	for _, si := range got {
+		if _, ok := dirs[filepath.Dir(si.Location.URI.SpanURI().Filename())]; ok {
+			result = append(result, si)
+		}
+	}
+	return result
+}
+
+// DiffWorkspaceSymbols prints the diff between expected and actual workspace
+// symbols test results.
+func DiffWorkspaceSymbols(want, got []protocol.SymbolInformation) string {
+	sort.Slice(want, func(i, j int) bool { return fmt.Sprintf("%v", want[i]) < fmt.Sprintf("%v", want[j]) })
+	sort.Slice(got, func(i, j int) bool { return fmt.Sprintf("%v", got[i]) < fmt.Sprintf("%v", got[j]) })
+	if len(got) != len(want) {
+		return summarizeWorkspaceSymbols(-1, want, got, "different lengths got %v want %v", len(got), len(want))
+	}
+	for i, w := range want {
+		g := got[i]
+		if w.Name != g.Name {
+			return summarizeWorkspaceSymbols(i, want, got, "incorrect name got %v want %v", g.Name, w.Name)
+		}
+		if w.Kind != g.Kind {
+			return summarizeWorkspaceSymbols(i, want, got, "incorrect kind got %v want %v", g.Kind, w.Kind)
+		}
+		if w.Location.URI != g.Location.URI {
+			return summarizeWorkspaceSymbols(i, want, got, "incorrect uri got %v want %v", g.Location.URI, w.Location.URI)
+		}
+		if protocol.CompareRange(w.Location.Range, g.Location.Range) != 0 {
+			return summarizeWorkspaceSymbols(i, want, got, "incorrect range got %v want %v", g.Location.Range, w.Location.Range)
+		}
+	}
+	return ""
+}
+
+func summarizeWorkspaceSymbols(i int, want, got []protocol.SymbolInformation, reason string, args ...interface{}) string {
+	msg := &bytes.Buffer{}
+	fmt.Fprint(msg, "workspace symbols 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 _, s := range want {
+		fmt.Fprintf(msg, "  %v %v %v:%v\n", s.Name, s.Kind, s.Location.URI, s.Location.Range)
+	}
+	fmt.Fprintf(msg, "got:\n")
+	for _, s := range got {
+		fmt.Fprintf(msg, "  %v %v %v:%v\n", s.Name, s.Kind, s.Location.URI, s.Location.Range)
+	}
+	return msg.String()
+}
+
+// DiffDiagnostics prints the diff between expected and actual diagnostics test
+// results.
+func DiffDiagnostics(uri span.URI, want, got []source.Diagnostic) string {
+	source.SortDiagnostics(want)
+	source.SortDiagnostics(got)
+
+	if len(got) != len(want) {
+		return summarizeDiagnostics(-1, uri, 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, uri, want, got, "incorrect Message got %v want %v", g.Message, w.Message)
+		}
+		if w.Severity != g.Severity {
+			return summarizeDiagnostics(i, uri, want, got, "incorrect Severity got %v want %v", g.Severity, w.Severity)
+		}
+		if w.Source != g.Source {
+			return summarizeDiagnostics(i, uri, want, got, "incorrect Source got %v want %v", g.Source, w.Source)
+		}
+		// Don't check the range on the badimport test.
+		if strings.Contains(uri.Filename(), "badimport") {
+			continue
+		}
+		if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
+			return summarizeDiagnostics(i, uri, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
+		}
+		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, uri, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
+			}
+		}
+	}
+	return ""
+}
+
+func summarizeDiagnostics(i int, uri span.URI, 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", uri, d.Range, d.Message)
+	}
+	fmt.Fprintf(msg, "got:\n")
+	for _, d := range got {
+		fmt.Fprintf(msg, "  %s:%v: %s\n", uri, d.Range, d.Message)
+	}
+	return msg.String()
+}
+
+func DiffCodeLens(uri span.URI, want, got []protocol.CodeLens) string {
+	sortCodeLens(want)
+	sortCodeLens(got)
+
+	if len(got) != len(want) {
+		return summarizeCodeLens(-1, uri, want, got, "different lengths got %v want %v", len(got), len(want))
+	}
+	for i, w := range want {
+		g := got[i]
+		if w.Command.Title != g.Command.Title {
+			return summarizeCodeLens(i, uri, want, got, "incorrect Command Title got %v want %v", g.Command.Title, w.Command.Title)
+		}
+		if w.Command.Command != g.Command.Command {
+			return summarizeCodeLens(i, uri, want, got, "incorrect Command Title got %v want %v", g.Command.Command, w.Command.Command)
+		}
+		if protocol.ComparePosition(w.Range.Start, g.Range.Start) != 0 {
+			return summarizeCodeLens(i, uri, want, got, "incorrect Start got %v want %v", g.Range.Start, w.Range.Start)
+		}
+		if !protocol.IsPoint(g.Range) { // Accept any 'want' range if the codelens returns a zero-length range.
+			if protocol.ComparePosition(w.Range.End, g.Range.End) != 0 {
+				return summarizeCodeLens(i, uri, want, got, "incorrect End got %v want %v", g.Range.End, w.Range.End)
+			}
+		}
+	}
+	return ""
+}
+
+func sortCodeLens(c []protocol.CodeLens) {
+	sort.Slice(c, func(i int, j int) bool {
+		if r := protocol.CompareRange(c[i].Range, c[j].Range); r != 0 {
+			return r < 0
+		}
+		if c[i].Command.Command < c[j].Command.Command {
+			return true
+		}
+		if c[i].Command.Command == c[j].Command.Command {
+			return true
+		}
+		return c[i].Command.Title <= c[j].Command.Title
+	})
+}
+
+func summarizeCodeLens(i int, uri span.URI, want, got []protocol.CodeLens, reason string, args ...interface{}) string {
+	msg := &bytes.Buffer{}
+	fmt.Fprint(msg, "codelens 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 | %s\n", uri, d.Range, d.Command.Command, d.Command.Title)
+	}
+	fmt.Fprintf(msg, "got:\n")
+	for _, d := range got {
+		fmt.Fprintf(msg, "  %s:%v: %s | %s\n", uri, d.Range, d.Command.Command, d.Command.Title)
+	}
+	return msg.String()
+}
+
+func DiffSignatures(spn span.Span, want, got *protocol.SignatureHelp) string {
+	decorate := func(f string, args ...interface{}) string {
+		return fmt.Sprintf("Invalid signature at %s: %s", spn, fmt.Sprintf(f, args...))
+	}
+
+	if len(got.Signatures) != 1 {
+		return decorate("wanted 1 signature, got %d", len(got.Signatures))
+	}
+
+	if got.ActiveSignature != 0 {
+		return decorate("wanted active signature of 0, got %d", int(got.ActiveSignature))
+	}
+
+	if want.ActiveParameter != got.ActiveParameter {
+		return decorate("wanted active parameter of %d, got %d", want.ActiveParameter, int(got.ActiveParameter))
+	}
+
+	gotSig := got.Signatures[int(got.ActiveSignature)]
+
+	if want.Signatures[0].Label != got.Signatures[0].Label {
+		return decorate("wanted label %q, got %q", want.Signatures[0].Label, got.Signatures[0].Label)
+	}
+
+	var paramParts []string
+	for _, p := range gotSig.Parameters {
+		paramParts = append(paramParts, p.Label)
+	}
+	paramsStr := strings.Join(paramParts, ", ")
+	if !strings.Contains(gotSig.Label, paramsStr) {
+		return decorate("expected signature %q to contain params %q", gotSig.Label, paramsStr)
+	}
+
+	return ""
+}
+
+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 {
+	pItem := protocol.CompletionItem{
+		Label:         item.Label,
+		Kind:          item.Kind,
+		Detail:        item.Detail,
+		Documentation: item.Documentation,
+		InsertText:    item.InsertText,
+		TextEdit: &protocol.TextEdit{
+			NewText: item.Snippet(),
+		},
+		// Negate score so best score has lowest sort text like real API.
+		SortText: fmt.Sprint(-item.Score),
+	}
+	if pItem.InsertText == "" {
+		pItem.InsertText = pItem.Label
+	}
+	return pItem
+}
+
+func FilterBuiltins(src span.Span, items []protocol.CompletionItem) []protocol.CompletionItem {
+	var (
+		got          []protocol.CompletionItem
+		wantBuiltins = strings.Contains(string(src.URI()), "builtins")
+		wantKeywords = strings.Contains(string(src.URI()), "keywords")
+	)
+	for _, item := range items {
+		if !wantBuiltins && isBuiltin(item.Label, item.Detail, item.Kind) {
+			continue
+		}
+
+		if !wantKeywords && token.Lookup(item.Label).IsKeyword() {
+			continue
+		}
+
+		got = append(got, item)
+	}
+	return got
+}
+
+func isBuiltin(label, detail string, kind protocol.CompletionItemKind) bool {
+	if detail == "" && kind == protocol.ClassCompletion {
+		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, strictScores bool) string {
+	var (
+		matchedIdxs []int
+		lastGotIdx  int
+		lastGotSort float64
+		inOrder     = true
+		errorMsg    = "completions out of order"
+	)
+	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
+
+				sort, _ := strconv.ParseFloat(g.SortText, 64)
+				if strictScores && len(matchedIdxs) > 1 && sort <= lastGotSort {
+					inOrder = false
+					errorMsg = "candidate scores not strictly decreasing"
+				}
+				lastGotSort = sort
+
+				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, errorMsg)
+	}
+
+	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/text_synchronization.go b/internal/lsp/text_synchronization.go
index 3a96ceb..1ebe8be 100644
--- a/internal/lsp/text_synchronization.go
+++ b/internal/lsp/text_synchronization.go
@@ -17,9 +17,14 @@
 )
 
 func (s *Server) didOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
+	uri := params.TextDocument.URI.SpanURI()
+	if !uri.IsFile() {
+		return nil
+	}
+
 	_, err := s.didModifyFiles(ctx, []source.FileModification{
 		{
-			URI:        span.NewURI(params.TextDocument.URI),
+			URI:        uri,
 			Action:     source.Open,
 			Version:    params.TextDocument.Version,
 			Text:       []byte(params.TextDocument.Text),
@@ -30,7 +35,11 @@
 }
 
 func (s *Server) didChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error {
-	uri := span.NewURI(params.TextDocument.URI)
+	uri := params.TextDocument.URI.SpanURI()
+	if !uri.IsFile() {
+		return nil
+	}
+
 	text, err := s.changedText(ctx, uri, params.ContentChanges)
 	if err != nil {
 		return err
@@ -64,27 +73,56 @@
 
 func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error {
 	var modifications []source.FileModification
+	deletions := make(map[span.URI]struct{})
 	for _, change := range params.Changes {
-		uri := span.NewURI(change.URI)
-
-		// Do nothing if the file is open in the editor.
-		// The editor is the source of truth.
-		if s.session.IsOpen(uri) {
+		uri := change.URI.SpanURI()
+		if !uri.IsFile() {
 			continue
 		}
+		action := changeTypeToFileAction(change.Type)
 		modifications = append(modifications, source.FileModification{
 			URI:    uri,
-			Action: changeTypeToFileAction(change.Type),
+			Action: action,
 			OnDisk: true,
 		})
+		// Keep track of deleted files so that we can clear their diagnostics.
+		// A file might be re-created after deletion, so only mark files that
+		// have truly been deleted.
+		switch action {
+		case source.Delete:
+			deletions[uri] = struct{}{}
+		case source.Close:
+		default:
+			delete(deletions, uri)
+		}
 	}
-	_, err := s.didModifyFiles(ctx, modifications)
-	return err
+	snapshots, err := s.didModifyFiles(ctx, modifications)
+	if err != nil {
+		return err
+	}
+	// Clear the diagnostics for any deleted files.
+	for uri := range deletions {
+		if snapshot := snapshots[uri]; snapshot == nil || snapshot.IsOpen(uri) {
+			continue
+		}
+		if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
+			URI:         protocol.URIFromSpanURI(uri),
+			Diagnostics: []protocol.Diagnostic{},
+			Version:     0,
+		}); err != nil {
+			return err
+		}
+	}
+	return nil
 }
 
 func (s *Server) didSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error {
+	uri := params.TextDocument.URI.SpanURI()
+	if !uri.IsFile() {
+		return nil
+	}
 	c := source.FileModification{
-		URI:     span.NewURI(params.TextDocument.URI),
+		URI:     uri,
 		Action:  source.Save,
 		Version: params.TextDocument.Version,
 	}
@@ -96,15 +134,38 @@
 }
 
 func (s *Server) didClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
-	_, err := s.didModifyFiles(ctx, []source.FileModification{
+	uri := params.TextDocument.URI.SpanURI()
+	if !uri.IsFile() {
+		return nil
+	}
+	snapshots, err := s.didModifyFiles(ctx, []source.FileModification{
 		{
-			URI:     span.NewURI(params.TextDocument.URI),
+			URI:     uri,
 			Action:  source.Close,
 			Version: -1,
 			Text:    nil,
 		},
 	})
-	return err
+	if err != nil {
+		return err
+	}
+	snapshot := snapshots[uri]
+	if snapshot == nil {
+		return errors.Errorf("no snapshot for %s", uri)
+	}
+	fh, err := snapshot.GetFile(uri)
+	if err != nil {
+		return err
+	}
+	// If a file has been closed and is not on disk, clear its diagnostics.
+	if _, _, err := fh.Read(ctx); err != nil {
+		return s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
+			URI:         protocol.URIFromSpanURI(uri),
+			Diagnostics: []protocol.Diagnostic{},
+			Version:     0,
+		})
+	}
+	return nil
 }
 
 func (s *Server) didModifyFiles(ctx context.Context, modifications []source.FileModification) (map[span.URI]source.Snapshot, error) {
@@ -147,11 +208,15 @@
 			if err != nil {
 				return nil, err
 			}
-			// If a modification comes in for a go.mod file,
-			// and the view was never properly initialized,
-			// try to recreate the associated view.
+			// If a modification comes in for the view's go.mod file and the view
+			// was never properly initialized, or the view does not have
+			// a go.mod file, try to recreate the associated view.
 			switch fh.Identity().Kind {
 			case source.Mod:
+				modfile, _ := snapshot.View().ModFiles()
+				if modfile != "" || fh.Identity().URI != modfile {
+					continue
+				}
 				newSnapshot, err := snapshot.View().Rebuild(ctx)
 				if err != nil {
 					return nil, err
diff --git a/internal/lsp/workspace_symbol.go b/internal/lsp/workspace_symbol.go
new file mode 100644
index 0000000..c752b7f
--- /dev/null
+++ b/internal/lsp/workspace_symbol.go
@@ -0,0 +1,20 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package lsp
+
+import (
+	"context"
+
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
+	"golang.org/x/tools/internal/telemetry/trace"
+)
+
+func (s *Server) symbol(ctx context.Context, params *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) {
+	ctx, done := trace.StartSpan(ctx, "lsp.Server.symbol")
+	defer done()
+
+	return source.WorkspaceSymbols(ctx, s.session.Views(), params.Query)
+}
diff --git a/internal/memoize/memoize.go b/internal/memoize/memoize.go
index 232692c..b05a216 100644
--- a/internal/memoize/memoize.go
+++ b/internal/memoize/memoize.go
@@ -16,6 +16,7 @@
 
 import (
 	"context"
+	"reflect"
 	"runtime"
 	"sync"
 	"unsafe"
@@ -151,6 +152,18 @@
 	return (*Handle)(unsafe.Pointer(e))
 }
 
+// Stats returns the number of each type of value in the store.
+func (s *Store) Stats() map[reflect.Type]int {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	result := map[reflect.Type]int{}
+	for k := range s.entries {
+		result[reflect.TypeOf(k)]++
+	}
+	return result
+}
+
 // Cached returns the value associated with a handle.
 //
 // It will never cause the value to be generated.
diff --git a/internal/module/module.go b/internal/module/module.go
deleted file mode 100644
index 9a4edb9..0000000
--- a/internal/module/module.go
+++ /dev/null
@@ -1,540 +0,0 @@
-// Copyright 2018 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package module defines the module.Version type
-// along with support code.
-package module
-
-// IMPORTANT NOTE
-//
-// This file essentially defines the set of valid import paths for the go command.
-// There are many subtle considerations, including Unicode ambiguity,
-// security, network, and file system representations.
-//
-// This file also defines the set of valid module path and version combinations,
-// another topic with many subtle considerations.
-//
-// Changes to the semantics in this file require approval from rsc.
-
-import (
-	"fmt"
-	"sort"
-	"strings"
-	"unicode"
-	"unicode/utf8"
-
-	"golang.org/x/tools/internal/semver"
-)
-
-// A Version is defined by a module path and version pair.
-type Version struct {
-	Path string
-
-	// Version is usually a semantic version in canonical form.
-	// There are two exceptions to this general rule.
-	// First, the top-level target of a build has no specific version
-	// and uses Version = "".
-	// Second, during MVS calculations the version "none" is used
-	// to represent the decision to take no version of a given module.
-	Version string `json:",omitempty"`
-}
-
-// Check checks that a given module path, version pair is valid.
-// In addition to the path being a valid module path
-// and the version being a valid semantic version,
-// the two must correspond.
-// For example, the path "yaml/v2" only corresponds to
-// semantic versions beginning with "v2.".
-func Check(path, version string) error {
-	if err := CheckPath(path); err != nil {
-		return err
-	}
-	if !semver.IsValid(version) {
-		return fmt.Errorf("malformed semantic version %v", version)
-	}
-	_, pathMajor, _ := SplitPathVersion(path)
-	if !MatchPathMajor(version, pathMajor) {
-		if pathMajor == "" {
-			pathMajor = "v0 or v1"
-		}
-		if pathMajor[0] == '.' { // .v1
-			pathMajor = pathMajor[1:]
-		}
-		return fmt.Errorf("mismatched module path %v and version %v (want %v)", path, version, pathMajor)
-	}
-	return nil
-}
-
-// firstPathOK reports whether r can appear in the first element of a module path.
-// The first element of the path must be an LDH domain name, at least for now.
-// To avoid case ambiguity, the domain name must be entirely lower case.
-func firstPathOK(r rune) bool {
-	return r == '-' || r == '.' ||
-		'0' <= r && r <= '9' ||
-		'a' <= r && r <= 'z'
-}
-
-// pathOK reports whether r can appear in an import path element.
-// Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: + - . _ and ~.
-// This matches what "go get" has historically recognized in import paths.
-// TODO(rsc): We would like to allow Unicode letters, but that requires additional
-// care in the safe encoding (see note below).
-func pathOK(r rune) bool {
-	if r < utf8.RuneSelf {
-		return r == '+' || r == '-' || r == '.' || r == '_' || r == '~' ||
-			'0' <= r && r <= '9' ||
-			'A' <= r && r <= 'Z' ||
-			'a' <= r && r <= 'z'
-	}
-	return false
-}
-
-// fileNameOK reports whether r can appear in a file name.
-// For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters.
-// If we expand the set of allowed characters here, we have to
-// work harder at detecting potential case-folding and normalization collisions.
-// See note about "safe encoding" below.
-func fileNameOK(r rune) bool {
-	if r < utf8.RuneSelf {
-		// Entire set of ASCII punctuation, from which we remove characters:
-		//     ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
-		// We disallow some shell special characters: " ' * < > ? ` |
-		// (Note that some of those are disallowed by the Windows file system as well.)
-		// We also disallow path separators / : and \ (fileNameOK is only called on path element characters).
-		// We allow spaces (U+0020) in file names.
-		const allowed = "!#$%&()+,-.=@[]^_{}~ "
-		if '0' <= r && r <= '9' || 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' {
-			return true
-		}
-		for i := 0; i < len(allowed); i++ {
-			if rune(allowed[i]) == r {
-				return true
-			}
-		}
-		return false
-	}
-	// It may be OK to add more ASCII punctuation here, but only carefully.
-	// For example Windows disallows < > \, and macOS disallows :, so we must not allow those.
-	return unicode.IsLetter(r)
-}
-
-// CheckPath checks that a module path is valid.
-func CheckPath(path string) error {
-	if err := checkPath(path, false); err != nil {
-		return fmt.Errorf("malformed module path %q: %v", path, err)
-	}
-	i := strings.Index(path, "/")
-	if i < 0 {
-		i = len(path)
-	}
-	if i == 0 {
-		return fmt.Errorf("malformed module path %q: leading slash", path)
-	}
-	if !strings.Contains(path[:i], ".") {
-		return fmt.Errorf("malformed module path %q: missing dot in first path element", path)
-	}
-	if path[0] == '-' {
-		return fmt.Errorf("malformed module path %q: leading dash in first path element", path)
-	}
-	for _, r := range path[:i] {
-		if !firstPathOK(r) {
-			return fmt.Errorf("malformed module path %q: invalid char %q in first path element", path, r)
-		}
-	}
-	if _, _, ok := SplitPathVersion(path); !ok {
-		return fmt.Errorf("malformed module path %q: invalid version", path)
-	}
-	return nil
-}
-
-// CheckImportPath checks that an import path is valid.
-func CheckImportPath(path string) error {
-	if err := checkPath(path, false); err != nil {
-		return fmt.Errorf("malformed import path %q: %v", path, err)
-	}
-	return nil
-}
-
-// checkPath checks that a general path is valid.
-// It returns an error describing why but not mentioning path.
-// Because these checks apply to both module paths and import paths,
-// the caller is expected to add the "malformed ___ path %q: " prefix.
-// fileName indicates whether the final element of the path is a file name
-// (as opposed to a directory name).
-func checkPath(path string, fileName bool) error {
-	if !utf8.ValidString(path) {
-		return fmt.Errorf("invalid UTF-8")
-	}
-	if path == "" {
-		return fmt.Errorf("empty string")
-	}
-	if strings.Contains(path, "..") {
-		return fmt.Errorf("double dot")
-	}
-	if strings.Contains(path, "//") {
-		return fmt.Errorf("double slash")
-	}
-	if path[len(path)-1] == '/' {
-		return fmt.Errorf("trailing slash")
-	}
-	elemStart := 0
-	for i, r := range path {
-		if r == '/' {
-			if err := checkElem(path[elemStart:i], fileName); err != nil {
-				return err
-			}
-			elemStart = i + 1
-		}
-	}
-	if err := checkElem(path[elemStart:], fileName); err != nil {
-		return err
-	}
-	return nil
-}
-
-// checkElem checks whether an individual path element is valid.
-// fileName indicates whether the element is a file name (not a directory name).
-func checkElem(elem string, fileName bool) error {
-	if elem == "" {
-		return fmt.Errorf("empty path element")
-	}
-	if strings.Count(elem, ".") == len(elem) {
-		return fmt.Errorf("invalid path element %q", elem)
-	}
-	if elem[0] == '.' && !fileName {
-		return fmt.Errorf("leading dot in path element")
-	}
-	if elem[len(elem)-1] == '.' {
-		return fmt.Errorf("trailing dot in path element")
-	}
-	charOK := pathOK
-	if fileName {
-		charOK = fileNameOK
-	}
-	for _, r := range elem {
-		if !charOK(r) {
-			return fmt.Errorf("invalid char %q", r)
-		}
-	}
-
-	// Windows disallows a bunch of path elements, sadly.
-	// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
-	short := elem
-	if i := strings.Index(short, "."); i >= 0 {
-		short = short[:i]
-	}
-	for _, bad := range badWindowsNames {
-		if strings.EqualFold(bad, short) {
-			return fmt.Errorf("disallowed path element %q", elem)
-		}
-	}
-	return nil
-}
-
-// CheckFilePath checks whether a slash-separated file path is valid.
-func CheckFilePath(path string) error {
-	if err := checkPath(path, true); err != nil {
-		return fmt.Errorf("malformed file path %q: %v", path, err)
-	}
-	return nil
-}
-
-// badWindowsNames are the reserved file path elements on Windows.
-// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
-var badWindowsNames = []string{
-	"CON",
-	"PRN",
-	"AUX",
-	"NUL",
-	"COM1",
-	"COM2",
-	"COM3",
-	"COM4",
-	"COM5",
-	"COM6",
-	"COM7",
-	"COM8",
-	"COM9",
-	"LPT1",
-	"LPT2",
-	"LPT3",
-	"LPT4",
-	"LPT5",
-	"LPT6",
-	"LPT7",
-	"LPT8",
-	"LPT9",
-}
-
-// SplitPathVersion returns prefix and major version such that prefix+pathMajor == path
-// and version is either empty or "/vN" for N >= 2.
-// As a special case, gopkg.in paths are recognized directly;
-// they require ".vN" instead of "/vN", and for all N, not just N >= 2.
-func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) {
-	if strings.HasPrefix(path, "gopkg.in/") {
-		return splitGopkgIn(path)
-	}
-
-	i := len(path)
-	dot := false
-	for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9' || path[i-1] == '.') {
-		if path[i-1] == '.' {
-			dot = true
-		}
-		i--
-	}
-	if i <= 1 || i == len(path) || path[i-1] != 'v' || path[i-2] != '/' {
-		return path, "", true
-	}
-	prefix, pathMajor = path[:i-2], path[i-2:]
-	if dot || len(pathMajor) <= 2 || pathMajor[2] == '0' || pathMajor == "/v1" {
-		return path, "", false
-	}
-	return prefix, pathMajor, true
-}
-
-// splitGopkgIn is like SplitPathVersion but only for gopkg.in paths.
-func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) {
-	if !strings.HasPrefix(path, "gopkg.in/") {
-		return path, "", false
-	}
-	i := len(path)
-	if strings.HasSuffix(path, "-unstable") {
-		i -= len("-unstable")
-	}
-	for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9') {
-		i--
-	}
-	if i <= 1 || path[i-1] != 'v' || path[i-2] != '.' {
-		// All gopkg.in paths must end in vN for some N.
-		return path, "", false
-	}
-	prefix, pathMajor = path[:i-2], path[i-2:]
-	if len(pathMajor) <= 2 || pathMajor[2] == '0' && pathMajor != ".v0" {
-		return path, "", false
-	}
-	return prefix, pathMajor, true
-}
-
-// MatchPathMajor reports whether the semantic version v
-// matches the path major version pathMajor.
-func MatchPathMajor(v, pathMajor string) bool {
-	if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
-		pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
-	}
-	if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" {
-		// Allow old bug in pseudo-versions that generated v0.0.0- pseudoversion for gopkg .v1.
-		// For example, gopkg.in/yaml.v2@v2.2.1's go.mod requires gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405.
-		return true
-	}
-	m := semver.Major(v)
-	if pathMajor == "" {
-		return m == "v0" || m == "v1" || semver.Build(v) == "+incompatible"
-	}
-	return (pathMajor[0] == '/' || pathMajor[0] == '.') && m == pathMajor[1:]
-}
-
-// CanonicalVersion returns the canonical form of the version string v.
-// It is the same as semver.Canonical(v) except that it preserves the special build suffix "+incompatible".
-func CanonicalVersion(v string) string {
-	cv := semver.Canonical(v)
-	if semver.Build(v) == "+incompatible" {
-		cv += "+incompatible"
-	}
-	return cv
-}
-
-// Sort sorts the list by Path, breaking ties by comparing Versions.
-func Sort(list []Version) {
-	sort.Slice(list, func(i, j int) bool {
-		mi := list[i]
-		mj := list[j]
-		if mi.Path != mj.Path {
-			return mi.Path < mj.Path
-		}
-		// To help go.sum formatting, allow version/file.
-		// Compare semver prefix by semver rules,
-		// file by string order.
-		vi := mi.Version
-		vj := mj.Version
-		var fi, fj string
-		if k := strings.Index(vi, "/"); k >= 0 {
-			vi, fi = vi[:k], vi[k:]
-		}
-		if k := strings.Index(vj, "/"); k >= 0 {
-			vj, fj = vj[:k], vj[k:]
-		}
-		if vi != vj {
-			return semver.Compare(vi, vj) < 0
-		}
-		return fi < fj
-	})
-}
-
-// Safe encodings
-//
-// Module paths appear as substrings of file system paths
-// (in the download cache) and of web server URLs in the proxy protocol.
-// In general we cannot rely on file systems to be case-sensitive,
-// nor can we rely on web servers, since they read from file systems.
-// That is, we cannot rely on the file system to keep rsc.io/QUOTE
-// and rsc.io/quote separate. Windows and macOS don't.
-// Instead, we must never require two different casings of a file path.
-// Because we want the download cache to match the proxy protocol,
-// and because we want the proxy protocol to be possible to serve
-// from a tree of static files (which might be stored on a case-insensitive
-// file system), the proxy protocol must never require two different casings
-// of a URL path either.
-//
-// One possibility would be to make the safe encoding be the lowercase
-// hexadecimal encoding of the actual path bytes. This would avoid ever
-// needing different casings of a file path, but it would be fairly illegible
-// to most programmers when those paths appeared in the file system
-// (including in file paths in compiler errors and stack traces)
-// in web server logs, and so on. Instead, we want a safe encoding that
-// leaves most paths unaltered.
-//
-// The safe encoding is this:
-// replace every uppercase letter with an exclamation mark
-// followed by the letter's lowercase equivalent.
-//
-// For example,
-// github.com/Azure/azure-sdk-for-go ->  github.com/!azure/azure-sdk-for-go.
-// github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy
-// github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus.
-//
-// Import paths that avoid upper-case letters are left unchanged.
-// Note that because import paths are ASCII-only and avoid various
-// problematic punctuation (like : < and >), the safe encoding is also ASCII-only
-// and avoids the same problematic punctuation.
-//
-// Import paths have never allowed exclamation marks, so there is no
-// need to define how to encode a literal !.
-//
-// Although paths are disallowed from using Unicode (see pathOK above),
-// the eventual plan is to allow Unicode letters as well, to assume that
-// file systems and URLs are Unicode-safe (storing UTF-8), and apply
-// the !-for-uppercase convention. Note however that not all runes that
-// are different but case-fold equivalent are an upper/lower pair.
-// For example, U+004B ('K'), U+006B ('k'), and U+212A ('K' for Kelvin)
-// are considered to case-fold to each other. When we do add Unicode
-// letters, we must not assume that upper/lower are the only case-equivalent pairs.
-// Perhaps the Kelvin symbol would be disallowed entirely, for example.
-// Or perhaps it would encode as "!!k", or perhaps as "(212A)".
-//
-// Also, it would be nice to allow Unicode marks as well as letters,
-// but marks include combining marks, and then we must deal not
-// only with case folding but also normalization: both U+00E9 ('é')
-// and U+0065 U+0301 ('e' followed by combining acute accent)
-// look the same on the page and are treated by some file systems
-// as the same path. If we do allow Unicode marks in paths, there
-// must be some kind of normalization to allow only one canonical
-// encoding of any character used in an import path.
-
-// EncodePath returns the safe encoding of the given module path.
-// It fails if the module path is invalid.
-func EncodePath(path string) (encoding string, err error) {
-	if err := CheckPath(path); err != nil {
-		return "", err
-	}
-
-	return encodeString(path)
-}
-
-// EncodeVersion returns the safe encoding of the given module version.
-// Versions are allowed to be in non-semver form but must be valid file names
-// and not contain exclamation marks.
-func EncodeVersion(v string) (encoding string, err error) {
-	if err := checkElem(v, true); err != nil || strings.Contains(v, "!") {
-		return "", fmt.Errorf("disallowed version string %q", v)
-	}
-	return encodeString(v)
-}
-
-func encodeString(s string) (encoding string, err error) {
-	haveUpper := false
-	for _, r := range s {
-		if r == '!' || r >= utf8.RuneSelf {
-			// This should be disallowed by CheckPath, but diagnose anyway.
-			// The correctness of the encoding loop below depends on it.
-			return "", fmt.Errorf("internal error: inconsistency in EncodePath")
-		}
-		if 'A' <= r && r <= 'Z' {
-			haveUpper = true
-		}
-	}
-
-	if !haveUpper {
-		return s, nil
-	}
-
-	var buf []byte
-	for _, r := range s {
-		if 'A' <= r && r <= 'Z' {
-			buf = append(buf, '!', byte(r+'a'-'A'))
-		} else {
-			buf = append(buf, byte(r))
-		}
-	}
-	return string(buf), nil
-}
-
-// DecodePath returns the module path of the given safe encoding.
-// It fails if the encoding is invalid or encodes an invalid path.
-func DecodePath(encoding string) (path string, err error) {
-	path, ok := decodeString(encoding)
-	if !ok {
-		return "", fmt.Errorf("invalid module path encoding %q", encoding)
-	}
-	if err := CheckPath(path); err != nil {
-		return "", fmt.Errorf("invalid module path encoding %q: %v", encoding, err)
-	}
-	return path, nil
-}
-
-// DecodeVersion returns the version string for the given safe encoding.
-// It fails if the encoding is invalid or encodes an invalid version.
-// Versions are allowed to be in non-semver form but must be valid file names
-// and not contain exclamation marks.
-func DecodeVersion(encoding string) (v string, err error) {
-	v, ok := decodeString(encoding)
-	if !ok {
-		return "", fmt.Errorf("invalid version encoding %q", encoding)
-	}
-	if err := checkElem(v, true); err != nil {
-		return "", fmt.Errorf("disallowed version string %q", v)
-	}
-	return v, nil
-}
-
-func decodeString(encoding string) (string, bool) {
-	var buf []byte
-
-	bang := false
-	for _, r := range encoding {
-		if r >= utf8.RuneSelf {
-			return "", false
-		}
-		if bang {
-			bang = false
-			if r < 'a' || 'z' < r {
-				return "", false
-			}
-			buf = append(buf, byte(r+'A'-'a'))
-			continue
-		}
-		if r == '!' {
-			bang = true
-			continue
-		}
-		if 'A' <= r && r <= 'Z' {
-			return "", false
-		}
-		buf = append(buf, byte(r))
-	}
-	if bang {
-		return "", false
-	}
-	return string(buf), true
-}
diff --git a/internal/module/module_test.go b/internal/module/module_test.go
deleted file mode 100644
index b40bd03..0000000
--- a/internal/module/module_test.go
+++ /dev/null
@@ -1,319 +0,0 @@
-// Copyright 2018 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package module
-
-import "testing"
-
-var checkTests = []struct {
-	path    string
-	version string
-	ok      bool
-}{
-	{"rsc.io/quote", "0.1.0", false},
-	{"rsc io/quote", "v1.0.0", false},
-
-	{"github.com/go-yaml/yaml", "v0.8.0", true},
-	{"github.com/go-yaml/yaml", "v1.0.0", true},
-	{"github.com/go-yaml/yaml", "v2.0.0", false},
-	{"github.com/go-yaml/yaml", "v2.1.5", false},
-	{"github.com/go-yaml/yaml", "v3.0.0", false},
-
-	{"github.com/go-yaml/yaml/v2", "v1.0.0", false},
-	{"github.com/go-yaml/yaml/v2", "v2.0.0", true},
-	{"github.com/go-yaml/yaml/v2", "v2.1.5", true},
-	{"github.com/go-yaml/yaml/v2", "v3.0.0", false},
-
-	{"gopkg.in/yaml.v0", "v0.8.0", true},
-	{"gopkg.in/yaml.v0", "v1.0.0", false},
-	{"gopkg.in/yaml.v0", "v2.0.0", false},
-	{"gopkg.in/yaml.v0", "v2.1.5", false},
-	{"gopkg.in/yaml.v0", "v3.0.0", false},
-
-	{"gopkg.in/yaml.v1", "v0.8.0", false},
-	{"gopkg.in/yaml.v1", "v1.0.0", true},
-	{"gopkg.in/yaml.v1", "v2.0.0", false},
-	{"gopkg.in/yaml.v1", "v2.1.5", false},
-	{"gopkg.in/yaml.v1", "v3.0.0", false},
-
-	// For gopkg.in, .v1 means v1 only (not v0).
-	// But early versions of vgo still generated v0 pseudo-versions for it.
-	// Even though now we'd generate those as v1 pseudo-versions,
-	// we accept the old pseudo-versions to avoid breaking existing go.mod files.
-	// For example gopkg.in/yaml.v2@v2.2.1's go.mod requires check.v1 at a v0 pseudo-version.
-	{"gopkg.in/check.v1", "v0.0.0", false},
-	{"gopkg.in/check.v1", "v0.0.0-20160102150405-abcdef123456", true},
-
-	{"gopkg.in/yaml.v2", "v1.0.0", false},
-	{"gopkg.in/yaml.v2", "v2.0.0", true},
-	{"gopkg.in/yaml.v2", "v2.1.5", true},
-	{"gopkg.in/yaml.v2", "v3.0.0", false},
-
-	{"rsc.io/quote", "v17.0.0", false},
-	{"rsc.io/quote", "v17.0.0+incompatible", true},
-}
-
-func TestCheck(t *testing.T) {
-	for _, tt := range checkTests {
-		err := Check(tt.path, tt.version)
-		if tt.ok && err != nil {
-			t.Errorf("Check(%q, %q) = %v, wanted nil error", tt.path, tt.version, err)
-		} else if !tt.ok && err == nil {
-			t.Errorf("Check(%q, %q) succeeded, wanted error", tt.path, tt.version)
-		}
-	}
-}
-
-var checkPathTests = []struct {
-	path     string
-	ok       bool
-	importOK bool
-	fileOK   bool
-}{
-	{"x.y/z", true, true, true},
-	{"x.y", true, true, true},
-
-	{"", false, false, false},
-	{"x.y/\xFFz", false, false, false},
-	{"/x.y/z", false, false, false},
-	{"x./z", false, false, false},
-	{".x/z", false, false, true},
-	{"-x/z", false, true, true},
-	{"x..y/z", false, false, false},
-	{"x.y/z/../../w", false, false, false},
-	{"x.y//z", false, false, false},
-	{"x.y/z//w", false, false, false},
-	{"x.y/z/", false, false, false},
-
-	{"x.y/z/v0", false, true, true},
-	{"x.y/z/v1", false, true, true},
-	{"x.y/z/v2", true, true, true},
-	{"x.y/z/v2.0", false, true, true},
-	{"X.y/z", false, true, true},
-
-	{"!x.y/z", false, false, true},
-	{"_x.y/z", false, true, true},
-	{"x.y!/z", false, false, true},
-	{"x.y\"/z", false, false, false},
-	{"x.y#/z", false, false, true},
-	{"x.y$/z", false, false, true},
-	{"x.y%/z", false, false, true},
-	{"x.y&/z", false, false, true},
-	{"x.y'/z", false, false, false},
-	{"x.y(/z", false, false, true},
-	{"x.y)/z", false, false, true},
-	{"x.y*/z", false, false, false},
-	{"x.y+/z", false, true, true},
-	{"x.y,/z", false, false, true},
-	{"x.y-/z", true, true, true},
-	{"x.y./zt", false, false, false},
-	{"x.y:/z", false, false, false},
-	{"x.y;/z", false, false, false},
-	{"x.y</z", false, false, false},
-	{"x.y=/z", false, false, true},
-	{"x.y>/z", false, false, false},
-	{"x.y?/z", false, false, false},
-	{"x.y@/z", false, false, true},
-	{"x.y[/z", false, false, true},
-	{"x.y\\/z", false, false, false},
-	{"x.y]/z", false, false, true},
-	{"x.y^/z", false, false, true},
-	{"x.y_/z", false, true, true},
-	{"x.y`/z", false, false, false},
-	{"x.y{/z", false, false, true},
-	{"x.y}/z", false, false, true},
-	{"x.y~/z", false, true, true},
-	{"x.y/z!", false, false, true},
-	{"x.y/z\"", false, false, false},
-	{"x.y/z#", false, false, true},
-	{"x.y/z$", false, false, true},
-	{"x.y/z%", false, false, true},
-	{"x.y/z&", false, false, true},
-	{"x.y/z'", false, false, false},
-	{"x.y/z(", false, false, true},
-	{"x.y/z)", false, false, true},
-	{"x.y/z*", false, false, false},
-	{"x.y/z+", true, true, true},
-	{"x.y/z,", false, false, true},
-	{"x.y/z-", true, true, true},
-	{"x.y/z.t", true, true, true},
-	{"x.y/z/t", true, true, true},
-	{"x.y/z:", false, false, false},
-	{"x.y/z;", false, false, false},
-	{"x.y/z<", false, false, false},
-	{"x.y/z=", false, false, true},
-	{"x.y/z>", false, false, false},
-	{"x.y/z?", false, false, false},
-	{"x.y/z@", false, false, true},
-	{"x.y/z[", false, false, true},
-	{"x.y/z\\", false, false, false},
-	{"x.y/z]", false, false, true},
-	{"x.y/z^", false, false, true},
-	{"x.y/z_", true, true, true},
-	{"x.y/z`", false, false, false},
-	{"x.y/z{", false, false, true},
-	{"x.y/z}", false, false, true},
-	{"x.y/z~", true, true, true},
-	{"x.y/x.foo", true, true, true},
-	{"x.y/aux.foo", false, false, false},
-	{"x.y/prn", false, false, false},
-	{"x.y/prn2", true, true, true},
-	{"x.y/com", true, true, true},
-	{"x.y/com1", false, false, false},
-	{"x.y/com1.txt", false, false, false},
-	{"x.y/calm1", true, true, true},
-	{"github.com/!123/logrus", false, false, true},
-
-	// TODO: CL 41822 allowed Unicode letters in old "go get"
-	// without due consideration of the implications, and only on github.com (!).
-	// For now, we disallow non-ASCII characters in module mode,
-	// in both module paths and general import paths,
-	// until we can get the implications right.
-	// When we do, we'll enable them everywhere, not just for GitHub.
-	{"github.com/user/unicode/испытание", false, false, true},
-
-	{"../x", false, false, false},
-	{"./y", false, false, false},
-	{"x:y", false, false, false},
-	{`\temp\foo`, false, false, false},
-	{".gitignore", false, false, true},
-	{".github/ISSUE_TEMPLATE", false, false, true},
-	{"x☺y", false, false, false},
-}
-
-func TestCheckPath(t *testing.T) {
-	for _, tt := range checkPathTests {
-		err := CheckPath(tt.path)
-		if tt.ok && err != nil {
-			t.Errorf("CheckPath(%q) = %v, wanted nil error", tt.path, err)
-		} else if !tt.ok && err == nil {
-			t.Errorf("CheckPath(%q) succeeded, wanted error", tt.path)
-		}
-
-		err = CheckImportPath(tt.path)
-		if tt.importOK && err != nil {
-			t.Errorf("CheckImportPath(%q) = %v, wanted nil error", tt.path, err)
-		} else if !tt.importOK && err == nil {
-			t.Errorf("CheckImportPath(%q) succeeded, wanted error", tt.path)
-		}
-
-		err = CheckFilePath(tt.path)
-		if tt.fileOK && err != nil {
-			t.Errorf("CheckFilePath(%q) = %v, wanted nil error", tt.path, err)
-		} else if !tt.fileOK && err == nil {
-			t.Errorf("CheckFilePath(%q) succeeded, wanted error", tt.path)
-		}
-	}
-}
-
-var splitPathVersionTests = []struct {
-	pathPrefix string
-	version    string
-}{
-	{"x.y/z", ""},
-	{"x.y/z", "/v2"},
-	{"x.y/z", "/v3"},
-	{"x.y/v", ""},
-	{"gopkg.in/yaml", ".v0"},
-	{"gopkg.in/yaml", ".v1"},
-	{"gopkg.in/yaml", ".v2"},
-	{"gopkg.in/yaml", ".v3"},
-}
-
-func TestSplitPathVersion(t *testing.T) {
-	for _, tt := range splitPathVersionTests {
-		pathPrefix, version, ok := SplitPathVersion(tt.pathPrefix + tt.version)
-		if pathPrefix != tt.pathPrefix || version != tt.version || !ok {
-			t.Errorf("SplitPathVersion(%q) = %q, %q, %v, want %q, %q, true", tt.pathPrefix+tt.version, pathPrefix, version, ok, tt.pathPrefix, tt.version)
-		}
-	}
-
-	for _, tt := range checkPathTests {
-		pathPrefix, version, ok := SplitPathVersion(tt.path)
-		if pathPrefix+version != tt.path {
-			t.Errorf("SplitPathVersion(%q) = %q, %q, %v, doesn't add to input", tt.path, pathPrefix, version, ok)
-		}
-	}
-}
-
-var encodeTests = []struct {
-	path string
-	enc  string // empty means same as path
-}{
-	{path: "ascii.com/abcdefghijklmnopqrstuvwxyz.-+/~_0123456789"},
-	{path: "github.com/GoogleCloudPlatform/omega", enc: "github.com/!google!cloud!platform/omega"},
-}
-
-func TestEncodePath(t *testing.T) {
-	// Check invalid paths.
-	for _, tt := range checkPathTests {
-		if !tt.ok {
-			_, err := EncodePath(tt.path)
-			if err == nil {
-				t.Errorf("EncodePath(%q): succeeded, want error (invalid path)", tt.path)
-			}
-		}
-	}
-
-	// Check encodings.
-	for _, tt := range encodeTests {
-		enc, err := EncodePath(tt.path)
-		if err != nil {
-			t.Errorf("EncodePath(%q): unexpected error: %v", tt.path, err)
-			continue
-		}
-		want := tt.enc
-		if want == "" {
-			want = tt.path
-		}
-		if enc != want {
-			t.Errorf("EncodePath(%q) = %q, want %q", tt.path, enc, want)
-		}
-	}
-}
-
-var badDecode = []string{
-	"github.com/GoogleCloudPlatform/omega",
-	"github.com/!google!cloud!platform!/omega",
-	"github.com/!0google!cloud!platform/omega",
-	"github.com/!_google!cloud!platform/omega",
-	"github.com/!!google!cloud!platform/omega",
-	"",
-}
-
-func TestDecodePath(t *testing.T) {
-	// Check invalid decodings.
-	for _, bad := range badDecode {
-		_, err := DecodePath(bad)
-		if err == nil {
-			t.Errorf("DecodePath(%q): succeeded, want error (invalid decoding)", bad)
-		}
-	}
-
-	// Check invalid paths (or maybe decodings).
-	for _, tt := range checkPathTests {
-		if !tt.ok {
-			path, err := DecodePath(tt.path)
-			if err == nil {
-				t.Errorf("DecodePath(%q) = %q, want error (invalid path)", tt.path, path)
-			}
-		}
-	}
-
-	// Check encodings.
-	for _, tt := range encodeTests {
-		enc := tt.enc
-		if enc == "" {
-			enc = tt.path
-		}
-		path, err := DecodePath(enc)
-		if err != nil {
-			t.Errorf("DecodePath(%q): unexpected error: %v", enc, err)
-			continue
-		}
-		if path != tt.path {
-			t.Errorf("DecodePath(%q) = %q, want %q", enc, path, tt.path)
-		}
-	}
-}
diff --git a/internal/packagesinternal/packages.go b/internal/packagesinternal/packages.go
index 0c0dbb6..b13ce33 100644
--- a/internal/packagesinternal/packages.go
+++ b/internal/packagesinternal/packages.go
@@ -1,4 +1,27 @@
 // Package packagesinternal exposes internal-only fields from go/packages.
 package packagesinternal
 
+import "time"
+
+// Fields must match go list;
+type Module struct {
+	Path      string       // module path
+	Version   string       // module version
+	Versions  []string     // available module versions (with -versions)
+	Replace   *Module      // replaced by this module
+	Time      *time.Time   // time version was created
+	Update    *Module      // available update, if any (with -u)
+	Main      bool         // is this the main module?
+	Indirect  bool         // is this module only an indirect dependency of main module?
+	Dir       string       // directory holding files for this module, if any
+	GoMod     string       // path to go.mod file used when loading this module, if any
+	GoVersion string       // go version used in module
+	Error     *ModuleError // error loading module
+}
+type ModuleError struct {
+	Err string // the error itself
+}
+
 var GetForTest = func(p interface{}) string { return "" }
+
+var GetModule = func(p interface{}) *Module { return nil }
diff --git a/internal/semver/semver.go b/internal/semver/semver.go
deleted file mode 100644
index 4af7118..0000000
--- a/internal/semver/semver.go
+++ /dev/null
@@ -1,388 +0,0 @@
-// Copyright 2018 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package semver implements comparison of semantic version strings.
-// In this package, semantic version strings must begin with a leading "v",
-// as in "v1.0.0".
-//
-// The general form of a semantic version string accepted by this package is
-//
-//	vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]]
-//
-// where square brackets indicate optional parts of the syntax;
-// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros;
-// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers
-// using only alphanumeric characters and hyphens; and
-// all-numeric PRERELEASE identifiers must not have leading zeros.
-//
-// This package follows Semantic Versioning 2.0.0 (see semver.org)
-// with two exceptions. First, it requires the "v" prefix. Second, it recognizes
-// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes)
-// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
-package semver
-
-// parsed returns the parsed form of a semantic version string.
-type parsed struct {
-	major      string
-	minor      string
-	patch      string
-	short      string
-	prerelease string
-	build      string
-	err        string
-}
-
-// IsValid reports whether v is a valid semantic version string.
-func IsValid(v string) bool {
-	_, ok := parse(v)
-	return ok
-}
-
-// Canonical returns the canonical formatting of the semantic version v.
-// It fills in any missing .MINOR or .PATCH and discards build metadata.
-// Two semantic versions compare equal only if their canonical formattings
-// are identical strings.
-// The canonical invalid semantic version is the empty string.
-func Canonical(v string) string {
-	p, ok := parse(v)
-	if !ok {
-		return ""
-	}
-	if p.build != "" {
-		return v[:len(v)-len(p.build)]
-	}
-	if p.short != "" {
-		return v + p.short
-	}
-	return v
-}
-
-// Major returns the major version prefix of the semantic version v.
-// For example, Major("v2.1.0") == "v2".
-// If v is an invalid semantic version string, Major returns the empty string.
-func Major(v string) string {
-	pv, ok := parse(v)
-	if !ok {
-		return ""
-	}
-	return v[:1+len(pv.major)]
-}
-
-// MajorMinor returns the major.minor version prefix of the semantic version v.
-// For example, MajorMinor("v2.1.0") == "v2.1".
-// If v is an invalid semantic version string, MajorMinor returns the empty string.
-func MajorMinor(v string) string {
-	pv, ok := parse(v)
-	if !ok {
-		return ""
-	}
-	i := 1 + len(pv.major)
-	if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
-		return v[:j]
-	}
-	return v[:i] + "." + pv.minor
-}
-
-// Prerelease returns the prerelease suffix of the semantic version v.
-// For example, Prerelease("v2.1.0-pre+meta") == "-pre".
-// If v is an invalid semantic version string, Prerelease returns the empty string.
-func Prerelease(v string) string {
-	pv, ok := parse(v)
-	if !ok {
-		return ""
-	}
-	return pv.prerelease
-}
-
-// Build returns the build suffix of the semantic version v.
-// For example, Build("v2.1.0+meta") == "+meta".
-// If v is an invalid semantic version string, Build returns the empty string.
-func Build(v string) string {
-	pv, ok := parse(v)
-	if !ok {
-		return ""
-	}
-	return pv.build
-}
-
-// Compare returns an integer comparing two versions according to
-// according to semantic version precedence.
-// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
-//
-// An invalid semantic version string is considered less than a valid one.
-// All invalid semantic version strings compare equal to each other.
-func Compare(v, w string) int {
-	pv, ok1 := parse(v)
-	pw, ok2 := parse(w)
-	if !ok1 && !ok2 {
-		return 0
-	}
-	if !ok1 {
-		return -1
-	}
-	if !ok2 {
-		return +1
-	}
-	if c := compareInt(pv.major, pw.major); c != 0 {
-		return c
-	}
-	if c := compareInt(pv.minor, pw.minor); c != 0 {
-		return c
-	}
-	if c := compareInt(pv.patch, pw.patch); c != 0 {
-		return c
-	}
-	return comparePrerelease(pv.prerelease, pw.prerelease)
-}
-
-// Max canonicalizes its arguments and then returns the version string
-// that compares greater.
-func Max(v, w string) string {
-	v = Canonical(v)
-	w = Canonical(w)
-	if Compare(v, w) > 0 {
-		return v
-	}
-	return w
-}
-
-func parse(v string) (p parsed, ok bool) {
-	if v == "" || v[0] != 'v' {
-		p.err = "missing v prefix"
-		return
-	}
-	p.major, v, ok = parseInt(v[1:])
-	if !ok {
-		p.err = "bad major version"
-		return
-	}
-	if v == "" {
-		p.minor = "0"
-		p.patch = "0"
-		p.short = ".0.0"
-		return
-	}
-	if v[0] != '.' {
-		p.err = "bad minor prefix"
-		ok = false
-		return
-	}
-	p.minor, v, ok = parseInt(v[1:])
-	if !ok {
-		p.err = "bad minor version"
-		return
-	}
-	if v == "" {
-		p.patch = "0"
-		p.short = ".0"
-		return
-	}
-	if v[0] != '.' {
-		p.err = "bad patch prefix"
-		ok = false
-		return
-	}
-	p.patch, v, ok = parseInt(v[1:])
-	if !ok {
-		p.err = "bad patch version"
-		return
-	}
-	if len(v) > 0 && v[0] == '-' {
-		p.prerelease, v, ok = parsePrerelease(v)
-		if !ok {
-			p.err = "bad prerelease"
-			return
-		}
-	}
-	if len(v) > 0 && v[0] == '+' {
-		p.build, v, ok = parseBuild(v)
-		if !ok {
-			p.err = "bad build"
-			return
-		}
-	}
-	if v != "" {
-		p.err = "junk on end"
-		ok = false
-		return
-	}
-	ok = true
-	return
-}
-
-func parseInt(v string) (t, rest string, ok bool) {
-	if v == "" {
-		return
-	}
-	if v[0] < '0' || '9' < v[0] {
-		return
-	}
-	i := 1
-	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
-		i++
-	}
-	if v[0] == '0' && i != 1 {
-		return
-	}
-	return v[:i], v[i:], true
-}
-
-func parsePrerelease(v string) (t, rest string, ok bool) {
-	// "A pre-release version MAY be denoted by appending a hyphen and
-	// a series of dot separated identifiers immediately following the patch version.
-	// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
-	// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
-	if v == "" || v[0] != '-' {
-		return
-	}
-	i := 1
-	start := 1
-	for i < len(v) && v[i] != '+' {
-		if !isIdentChar(v[i]) && v[i] != '.' {
-			return
-		}
-		if v[i] == '.' {
-			if start == i || isBadNum(v[start:i]) {
-				return
-			}
-			start = i + 1
-		}
-		i++
-	}
-	if start == i || isBadNum(v[start:i]) {
-		return
-	}
-	return v[:i], v[i:], true
-}
-
-func parseBuild(v string) (t, rest string, ok bool) {
-	if v == "" || v[0] != '+' {
-		return
-	}
-	i := 1
-	start := 1
-	for i < len(v) {
-		if !isIdentChar(v[i]) {
-			return
-		}
-		if v[i] == '.' {
-			if start == i {
-				return
-			}
-			start = i + 1
-		}
-		i++
-	}
-	if start == i {
-		return
-	}
-	return v[:i], v[i:], true
-}
-
-func isIdentChar(c byte) bool {
-	return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
-}
-
-func isBadNum(v string) bool {
-	i := 0
-	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
-		i++
-	}
-	return i == len(v) && i > 1 && v[0] == '0'
-}
-
-func isNum(v string) bool {
-	i := 0
-	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
-		i++
-	}
-	return i == len(v)
-}
-
-func compareInt(x, y string) int {
-	if x == y {
-		return 0
-	}
-	if len(x) < len(y) {
-		return -1
-	}
-	if len(x) > len(y) {
-		return +1
-	}
-	if x < y {
-		return -1
-	} else {
-		return +1
-	}
-}
-
-func comparePrerelease(x, y string) int {
-	// "When major, minor, and patch are equal, a pre-release version has
-	// lower precedence than a normal version.
-	// Example: 1.0.0-alpha < 1.0.0.
-	// Precedence for two pre-release versions with the same major, minor,
-	// and patch version MUST be determined by comparing each dot separated
-	// identifier from left to right until a difference is found as follows:
-	// identifiers consisting of only digits are compared numerically and
-	// identifiers with letters or hyphens are compared lexically in ASCII
-	// sort order. Numeric identifiers always have lower precedence than
-	// non-numeric identifiers. A larger set of pre-release fields has a
-	// higher precedence than a smaller set, if all of the preceding
-	// identifiers are equal.
-	// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
-	// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
-	if x == y {
-		return 0
-	}
-	if x == "" {
-		return +1
-	}
-	if y == "" {
-		return -1
-	}
-	for x != "" && y != "" {
-		x = x[1:] // skip - or .
-		y = y[1:] // skip - or .
-		var dx, dy string
-		dx, x = nextIdent(x)
-		dy, y = nextIdent(y)
-		if dx != dy {
-			ix := isNum(dx)
-			iy := isNum(dy)
-			if ix != iy {
-				if ix {
-					return -1
-				} else {
-					return +1
-				}
-			}
-			if ix {
-				if len(dx) < len(dy) {
-					return -1
-				}
-				if len(dx) > len(dy) {
-					return +1
-				}
-			}
-			if dx < dy {
-				return -1
-			} else {
-				return +1
-			}
-		}
-	}
-	if x == "" {
-		return -1
-	} else {
-		return +1
-	}
-}
-
-func nextIdent(x string) (dx, rest string) {
-	i := 0
-	for i < len(x) && x[i] != '.' {
-		i++
-	}
-	return x[:i], x[i:]
-}
diff --git a/internal/semver/semver_test.go b/internal/semver/semver_test.go
deleted file mode 100644
index 96b64a5..0000000
--- a/internal/semver/semver_test.go
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2018 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package semver
-
-import (
-	"strings"
-	"testing"
-)
-
-var tests = []struct {
-	in  string
-	out string
-}{
-	{"bad", ""},
-	{"v1-alpha.beta.gamma", ""},
-	{"v1-pre", ""},
-	{"v1+meta", ""},
-	{"v1-pre+meta", ""},
-	{"v1.2-pre", ""},
-	{"v1.2+meta", ""},
-	{"v1.2-pre+meta", ""},
-	{"v1.0.0-alpha", "v1.0.0-alpha"},
-	{"v1.0.0-alpha.1", "v1.0.0-alpha.1"},
-	{"v1.0.0-alpha.beta", "v1.0.0-alpha.beta"},
-	{"v1.0.0-beta", "v1.0.0-beta"},
-	{"v1.0.0-beta.2", "v1.0.0-beta.2"},
-	{"v1.0.0-beta.11", "v1.0.0-beta.11"},
-	{"v1.0.0-rc.1", "v1.0.0-rc.1"},
-	{"v1", "v1.0.0"},
-	{"v1.0", "v1.0.0"},
-	{"v1.0.0", "v1.0.0"},
-	{"v1.2", "v1.2.0"},
-	{"v1.2.0", "v1.2.0"},
-	{"v1.2.3-456", "v1.2.3-456"},
-	{"v1.2.3-456.789", "v1.2.3-456.789"},
-	{"v1.2.3-456-789", "v1.2.3-456-789"},
-	{"v1.2.3-456a", "v1.2.3-456a"},
-	{"v1.2.3-pre", "v1.2.3-pre"},
-	{"v1.2.3-pre+meta", "v1.2.3-pre"},
-	{"v1.2.3-pre.1", "v1.2.3-pre.1"},
-	{"v1.2.3-zzz", "v1.2.3-zzz"},
-	{"v1.2.3", "v1.2.3"},
-	{"v1.2.3+meta", "v1.2.3"},
-	{"v1.2.3+meta-pre", "v1.2.3"},
-}
-
-func TestIsValid(t *testing.T) {
-	for _, tt := range tests {
-		ok := IsValid(tt.in)
-		if ok != (tt.out != "") {
-			t.Errorf("IsValid(%q) = %v, want %v", tt.in, ok, !ok)
-		}
-	}
-}
-
-func TestCanonical(t *testing.T) {
-	for _, tt := range tests {
-		out := Canonical(tt.in)
-		if out != tt.out {
-			t.Errorf("Canonical(%q) = %q, want %q", tt.in, out, tt.out)
-		}
-	}
-}
-
-func TestMajor(t *testing.T) {
-	for _, tt := range tests {
-		out := Major(tt.in)
-		want := ""
-		if i := strings.Index(tt.out, "."); i >= 0 {
-			want = tt.out[:i]
-		}
-		if out != want {
-			t.Errorf("Major(%q) = %q, want %q", tt.in, out, want)
-		}
-	}
-}
-
-func TestMajorMinor(t *testing.T) {
-	for _, tt := range tests {
-		out := MajorMinor(tt.in)
-		var want string
-		if tt.out != "" {
-			want = tt.in
-			if i := strings.Index(want, "+"); i >= 0 {
-				want = want[:i]
-			}
-			if i := strings.Index(want, "-"); i >= 0 {
-				want = want[:i]
-			}
-			switch strings.Count(want, ".") {
-			case 0:
-				want += ".0"
-			case 1:
-				// ok
-			case 2:
-				want = want[:strings.LastIndex(want, ".")]
-			}
-		}
-		if out != want {
-			t.Errorf("MajorMinor(%q) = %q, want %q", tt.in, out, want)
-		}
-	}
-}
-
-func TestPrerelease(t *testing.T) {
-	for _, tt := range tests {
-		pre := Prerelease(tt.in)
-		var want string
-		if tt.out != "" {
-			if i := strings.Index(tt.out, "-"); i >= 0 {
-				want = tt.out[i:]
-			}
-		}
-		if pre != want {
-			t.Errorf("Prerelease(%q) = %q, want %q", tt.in, pre, want)
-		}
-	}
-}
-
-func TestBuild(t *testing.T) {
-	for _, tt := range tests {
-		build := Build(tt.in)
-		var want string
-		if tt.out != "" {
-			if i := strings.Index(tt.in, "+"); i >= 0 {
-				want = tt.in[i:]
-			}
-		}
-		if build != want {
-			t.Errorf("Build(%q) = %q, want %q", tt.in, build, want)
-		}
-	}
-}
-
-func TestCompare(t *testing.T) {
-	for i, ti := range tests {
-		for j, tj := range tests {
-			cmp := Compare(ti.in, tj.in)
-			var want int
-			if ti.out == tj.out {
-				want = 0
-			} else if i < j {
-				want = -1
-			} else {
-				want = +1
-			}
-			if cmp != want {
-				t.Errorf("Compare(%q, %q) = %d, want %d", ti.in, tj.in, cmp, want)
-			}
-		}
-	}
-}
-
-func TestMax(t *testing.T) {
-	for i, ti := range tests {
-		for j, tj := range tests {
-			max := Max(ti.in, tj.in)
-			want := Canonical(ti.in)
-			if i < j {
-				want = Canonical(tj.in)
-			}
-			if max != want {
-				t.Errorf("Max(%q, %q) = %q, want %q", ti.in, tj.in, max, want)
-			}
-		}
-	}
-}
-
-var (
-	v1 = "v1.0.0+metadata-dash"
-	v2 = "v1.0.0+metadata-dash1"
-)
-
-func BenchmarkCompare(b *testing.B) {
-	for i := 0; i < b.N; i++ {
-		if Compare(v1, v2) != 0 {
-			b.Fatalf("bad compare")
-		}
-	}
-}
diff --git a/internal/span/parse.go b/internal/span/parse.go
index b3f268a..aa17c84 100644
--- a/internal/span/parse.go
+++ b/internal/span/parse.go
@@ -11,7 +11,7 @@
 )
 
 // Parse returns the location represented by the input.
-// All inputs are valid locations, as they can always be a pure filename.
+// Only file paths are accepted, not URIs.
 // The returned span will be normalized, and thus if printed may produce a
 // different string.
 func Parse(input string) Span {
@@ -32,12 +32,12 @@
 	}
 	switch {
 	case suf.sep == ":":
-		return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), Point{})
+		return New(URIFromPath(suf.remains), NewPoint(suf.num, hold, offset), Point{})
 	case suf.sep == "-":
 		// we have a span, fall out of the case to continue
 	default:
 		// separator not valid, rewind to either the : or the start
-		return New(NewURI(valid), NewPoint(hold, 0, offset), Point{})
+		return New(URIFromPath(valid), NewPoint(hold, 0, offset), Point{})
 	}
 	// only the span form can get here
 	// at this point we still don't know what the numbers we have mean
@@ -53,20 +53,20 @@
 	}
 	if suf.sep != ":" {
 		// turns out we don't have a span after all, rewind
-		return New(NewURI(valid), end, Point{})
+		return New(URIFromPath(valid), end, Point{})
 	}
 	valid = suf.remains
 	hold = suf.num
 	suf = rstripSuffix(suf.remains)
 	if suf.sep != ":" {
 		// line#offset only
-		return New(NewURI(valid), NewPoint(hold, 0, offset), end)
+		return New(URIFromPath(valid), NewPoint(hold, 0, offset), end)
 	}
 	// we have a column, so if end only had one number, it is also the column
 	if !hadCol {
 		end = NewPoint(suf.num, end.v.Line, end.v.Offset)
 	}
-	return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), end)
+	return New(URIFromPath(suf.remains), NewPoint(suf.num, hold, offset), end)
 }
 
 type suffix struct {
diff --git a/internal/span/span_test.go b/internal/span/span_test.go
index 8212d0c..150ea3f 100644
--- a/internal/span/span_test.go
+++ b/internal/span/span_test.go
@@ -14,8 +14,7 @@
 )
 
 var (
-	formats = []string{"%v", "%#v", "%+v"}
-	tests   = [][]string{
+	tests = [][]string{
 		{"C:/file_a", "C:/file_a", "file:///C:/file_a:1:1#0"},
 		{"C:/file_b:1:2", "C:/file_b:#1", "file:///C:/file_b:1:2#1"},
 		{"C:/file_c:1000", "C:/file_c:#9990", "file:///C:/file_c:1000:1#9990"},
@@ -30,7 +29,7 @@
 func TestFormat(t *testing.T) {
 	converter := lines(10)
 	for _, test := range tests {
-		for ti, text := range test {
+		for ti, text := range test[:2] {
 			spn := span.Parse(text)
 			if ti <= 1 {
 				// we can check %v produces the same as the input
diff --git a/internal/span/token.go b/internal/span/token.go
index d0ec03a..1710b77 100644
--- a/internal/span/token.go
+++ b/internal/span/token.go
@@ -75,7 +75,7 @@
 	if err != nil {
 		return Span{}, err
 	}
-	s.v.URI = FileURI(startFilename)
+	s.v.URI = URIFromPath(startFilename)
 	if r.End.IsValid() {
 		var endFilename string
 		endFilename, s.v.End.Line, s.v.End.Column, err = position(f, r.End)
diff --git a/internal/span/token_test.go b/internal/span/token_test.go
index db11df1..81b2631 100644
--- a/internal/span/token_test.go
+++ b/internal/span/token_test.go
@@ -32,10 +32,10 @@
 }
 
 var tokenTests = []span.Span{
-	span.New(span.FileURI("/a.go"), span.NewPoint(1, 1, 0), span.Point{}),
-	span.New(span.FileURI("/a.go"), span.NewPoint(3, 7, 20), span.NewPoint(3, 7, 20)),
-	span.New(span.FileURI("/b.go"), span.NewPoint(4, 9, 15), span.NewPoint(4, 13, 19)),
-	span.New(span.FileURI("/c.go"), span.NewPoint(4, 1, 26), span.Point{}),
+	span.New(span.URIFromPath("/a.go"), span.NewPoint(1, 1, 0), span.Point{}),
+	span.New(span.URIFromPath("/a.go"), span.NewPoint(3, 7, 20), span.NewPoint(3, 7, 20)),
+	span.New(span.URIFromPath("/b.go"), span.NewPoint(4, 9, 15), span.NewPoint(4, 13, 19)),
+	span.New(span.URIFromPath("/c.go"), span.NewPoint(4, 1, 26), span.Point{}),
 }
 
 func TestToken(t *testing.T) {
@@ -44,7 +44,7 @@
 	for _, f := range testdata {
 		file := fset.AddFile(f.uri, -1, len(f.content))
 		file.SetLinesForContent(f.content)
-		files[span.FileURI(f.uri)] = file
+		files[span.URIFromPath(f.uri)] = file
 	}
 	for _, test := range tokenTests {
 		f := files[test.URI()]
diff --git a/internal/span/uri.go b/internal/span/uri.go
index 26dc90c..f9f7760 100644
--- a/internal/span/uri.go
+++ b/internal/span/uri.go
@@ -20,6 +20,10 @@
 // URI represents the full URI for a file.
 type URI string
 
+func (uri URI) IsFile() bool {
+	return strings.HasPrefix(string(uri), "file://")
+}
+
 // Filename returns the file path for the given URI.
 // It is an error to call this on a URI that is not a valid filename.
 func (uri URI) Filename() string {
@@ -49,28 +53,27 @@
 	return u.Path, nil
 }
 
-// NewURI returns a span URI for the string.
-// It will attempt to detect if the string is a file path or uri.
-func NewURI(s string) URI {
-	// If a path has a scheme, it is already a URI.
-	// We only handle the file:// scheme.
-	if i := len(fileScheme + "://"); strings.HasPrefix(s, "file:///") {
-		// Handle microsoft/vscode#75027 by making it a special case.
-		// On Windows, VS Code sends file URIs that look like file:///C%3A/x/y/z.
-		// Replace the %3A so that the URI looks like: file:///C:/x/y/z.
-		if strings.ToLower(s[i+2:i+5]) == "%3a" {
-			s = s[:i+2] + ":" + s[i+5:]
-		}
-		// File URIs from Windows may have lowercase drive letters.
-		// Since drive letters are guaranteed to be case insensitive,
-		// we change them to uppercase to remain consistent.
-		// For example, file:///c:/x/y/z becomes file:///C:/x/y/z.
-		if isWindowsDriveURIPath(s[i:]) {
-			s = s[:i+1] + strings.ToUpper(string(s[i+1])) + s[i+2:]
-		}
+func URIFromURI(s string) URI {
+	if !strings.HasPrefix(s, "file:///") {
 		return URI(s)
 	}
-	return FileURI(s)
+
+	// Even though the input is a URI, it may not be in canonical form. VS Code
+	// in particular over-escapes :, @, etc. Unescape and re-encode to canonicalize.
+	path, err := url.PathUnescape(s[len("file://"):])
+	if err != nil {
+		panic(err)
+	}
+
+	// File URIs from Windows may have lowercase drive letters.
+	// Since drive letters are guaranteed to be case insensitive,
+	// we change them to uppercase to remain consistent.
+	// For example, file:///c:/x/y/z becomes file:///C:/x/y/z.
+	if isWindowsDriveURIPath(path) {
+		path = path[:1] + strings.ToUpper(string(path[1])) + path[2:]
+	}
+	u := url.URL{Scheme: fileScheme, Path: path}
+	return URI(u.String())
 }
 
 func CompareURI(a, b URI) int {
@@ -111,9 +114,9 @@
 	return os.SameFile(infoa, infob)
 }
 
-// FileURI returns a span URI for the supplied file path.
+// URIFromPath returns a span URI for the supplied file path.
 // It will always have the file scheme.
-func FileURI(path string) URI {
+func URIFromPath(path string) URI {
 	if path == "" {
 		return ""
 	}
diff --git a/internal/span/uri_test.go b/internal/span/uri_test.go
index a3754e3..cea74aa 100644
--- a/internal/span/uri_test.go
+++ b/internal/span/uri_test.go
@@ -16,7 +16,7 @@
 // include Windows-style URIs and filepaths, but we avoid having OS-specific
 // tests by using only forward slashes, assuming that the standard library
 // functions filepath.ToSlash and filepath.FromSlash do not need testing.
-func TestURI(t *testing.T) {
+func TestURIFromPath(t *testing.T) {
 	for _, test := range []struct {
 		path, wantFile string
 		wantURI        span.URI
@@ -56,25 +56,52 @@
 			wantFile: `C:/Go/src/bob george/george/george.go`,
 			wantURI:  span.URI("file:///C:/Go/src/bob%20george/george/george.go"),
 		},
+	} {
+		got := span.URIFromPath(test.path)
+		if got != test.wantURI {
+			t.Errorf("URIFromPath(%q): got %q, expected %q", test.path, got, test.wantURI)
+		}
+		gotFilename := got.Filename()
+		if gotFilename != test.wantFile {
+			t.Errorf("Filename(%q): got %q, expected %q", got, gotFilename, test.wantFile)
+		}
+	}
+}
+
+func TestURIFromURI(t *testing.T) {
+	for _, test := range []struct {
+		inputURI, wantFile string
+		wantURI            span.URI
+	}{
 		{
-			path:     `file:///c:/Go/src/bob%20george/george/george.go`,
+			inputURI: `file:///c:/Go/src/bob%20george/george/george.go`,
 			wantFile: `C:/Go/src/bob george/george/george.go`,
 			wantURI:  span.URI("file:///C:/Go/src/bob%20george/george/george.go"),
 		},
 		{
-			path:     `file:///C%3A/Go/src/bob%20george/george/george.go`,
+			inputURI: `file:///C%3A/Go/src/bob%20george/george/george.go`,
 			wantFile: `C:/Go/src/bob george/george/george.go`,
 			wantURI:  span.URI("file:///C:/Go/src/bob%20george/george/george.go"),
 		},
 		{
-			path:     `file:///path/to/%25p%25ercent%25/per%25cent.go`,
+			inputURI: `file:///path/to/%25p%25ercent%25/per%25cent.go`,
 			wantFile: `/path/to/%p%ercent%/per%cent.go`,
 			wantURI:  span.URI(`file:///path/to/%25p%25ercent%25/per%25cent.go`),
 		},
+		{
+			inputURI: `file:///C%3A/`,
+			wantFile: `C:/`,
+			wantURI:  span.URI(`file:///C:/`),
+		},
+		{
+			inputURI: `file:///`,
+			wantFile: `/`,
+			wantURI:  span.URI(`file:///`),
+		},
 	} {
-		got := span.NewURI(test.path)
+		got := span.URIFromURI(test.inputURI)
 		if got != test.wantURI {
-			t.Errorf("NewURI(%q): got %q, expected %q", test.path, got, test.wantURI)
+			t.Errorf("NewURI(%q): got %q, expected %q", test.inputURI, got, test.wantURI)
 		}
 		gotFilename := got.Filename()
 		if gotFilename != test.wantFile {
diff --git a/internal/span/uri_windows_test.go b/internal/span/uri_windows_test.go
index 1370b19..2a2632e 100644
--- a/internal/span/uri_windows_test.go
+++ b/internal/span/uri_windows_test.go
@@ -16,7 +16,7 @@
 // include Windows-style URIs and filepaths, but we avoid having OS-specific
 // tests by using only forward slashes, assuming that the standard library
 // functions filepath.ToSlash and filepath.FromSlash do not need testing.
-func TestURI(t *testing.T) {
+func TestURIFromPath(t *testing.T) {
 	for _, test := range []struct {
 		path, wantFile string
 		wantURI        span.URI
@@ -56,28 +56,56 @@
 			wantFile: `C:\Go\src\bob george\george\george.go`,
 			wantURI:  span.URI("file:///C:/Go/src/bob%20george/george/george.go"),
 		},
+	} {
+		got := span.URIFromPath(test.path)
+		if got != test.wantURI {
+			t.Errorf("URIFromPath(%q): got %q, expected %q", test.path, got, test.wantURI)
+		}
+		gotFilename := got.Filename()
+		if gotFilename != test.wantFile {
+			t.Errorf("Filename(%q): got %q, expected %q", got, gotFilename, test.wantFile)
+		}
+	}
+}
+
+func TestURIFromURI(t *testing.T) {
+	for _, test := range []struct {
+		inputURI, wantFile string
+		wantURI            span.URI
+	}{
 		{
-			path:     `file:///c:/Go/src/bob%20george/george/george.go`,
+			inputURI: `file:///c:/Go/src/bob%20george/george/george.go`,
 			wantFile: `C:\Go\src\bob george\george\george.go`,
 			wantURI:  span.URI("file:///C:/Go/src/bob%20george/george/george.go"),
 		},
 		{
-			path:     `file:///C%3A/Go/src/bob%20george/george/george.go`,
+			inputURI: `file:///C%3A/Go/src/bob%20george/george/george.go`,
 			wantFile: `C:\Go\src\bob george\george\george.go`,
 			wantURI:  span.URI("file:///C:/Go/src/bob%20george/george/george.go"),
 		},
 		{
-			path:     `file:///c:/path/to/%25p%25ercent%25/per%25cent.go`,
+			inputURI: `file:///c:/path/to/%25p%25ercent%25/per%25cent.go`,
 			wantFile: `C:\path\to\%p%ercent%\per%cent.go`,
 			wantURI:  span.URI(`file:///C:/path/to/%25p%25ercent%25/per%25cent.go`),
 		},
+		{
+			inputURI: `file:///C%3A/`,
+			wantFile: `C:\`,
+			wantURI:  span.URI(`file:///C:/`),
+		},
+		{
+			inputURI: `file:///`,
+			wantFile: `\`,
+			wantURI:  span.URI(`file:///`),
+		},
 	} {
-		got := span.NewURI(test.path)
+		got := span.URIFromURI(test.inputURI)
 		if got != test.wantURI {
-			t.Errorf("ToURI: got %s, expected %s", got, test.wantURI)
+			t.Errorf("NewURI(%q): got %q, expected %q", test.inputURI, got, test.wantURI)
 		}
-		if got.Filename() != test.wantFile {
-			t.Errorf("Filename: got %s, expected %s", got.Filename(), test.wantFile)
+		gotFilename := got.Filename()
+		if gotFilename != test.wantFile {
+			t.Errorf("Filename(%q): got %q, expected %q", got, gotFilename, test.wantFile)
 		}
 	}
 }
diff --git a/internal/telemetry/export/export.go b/internal/telemetry/export/export.go
index a48c6f1..dc79458 100644
--- a/internal/telemetry/export/export.go
+++ b/internal/telemetry/export/export.go
@@ -35,15 +35,18 @@
 	exporter   = LogWriter(os.Stderr, true)
 )
 
-func AddExporters(e ...Exporter) {
+func SetExporter(e Exporter) {
 	exporterMu.Lock()
 	defer exporterMu.Unlock()
-	exporter = Multi(append([]Exporter{exporter}, e...)...)
+	exporter = e
 }
 
 func StartSpan(ctx context.Context, span *telemetry.Span, at time.Time) {
 	exporterMu.Lock()
 	defer exporterMu.Unlock()
+	if exporter == nil {
+		return
+	}
 	span.Start = at
 	exporter.StartSpan(ctx, span)
 }
@@ -51,6 +54,9 @@
 func FinishSpan(ctx context.Context, span *telemetry.Span, at time.Time) {
 	exporterMu.Lock()
 	defer exporterMu.Unlock()
+	if exporter == nil {
+		return
+	}
 	span.Finish = at
 	exporter.FinishSpan(ctx, span)
 }
@@ -58,6 +64,9 @@
 func Tag(ctx context.Context, at time.Time, tags telemetry.TagList) {
 	exporterMu.Lock()
 	defer exporterMu.Unlock()
+	if exporter == nil {
+		return
+	}
 	// If context has a span we need to add the tags to it
 	span := telemetry.GetSpan(ctx)
 	if span == nil {
@@ -78,6 +87,9 @@
 func Log(ctx context.Context, event telemetry.Event) {
 	exporterMu.Lock()
 	defer exporterMu.Unlock()
+	if exporter == nil {
+		return
+	}
 	// If context has a span we need to add the event to it
 	span := telemetry.GetSpan(ctx)
 	if span != nil {
@@ -90,11 +102,17 @@
 func Metric(ctx context.Context, data telemetry.MetricData) {
 	exporterMu.Lock()
 	defer exporterMu.Unlock()
+	if exporter == nil {
+		return
+	}
 	exporter.Metric(ctx, data)
 }
 
 func Flush() {
 	exporterMu.Lock()
 	defer exporterMu.Unlock()
+	if exporter == nil {
+		return
+	}
 	exporter.Flush()
 }
diff --git a/internal/telemetry/export/multi.go b/internal/telemetry/export/multi.go
deleted file mode 100644
index df19f2c..0000000
--- a/internal/telemetry/export/multi.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2019 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package export
-
-import (
-	"context"
-
-	"golang.org/x/tools/internal/telemetry"
-)
-
-// Multi returns an exporter that invokes all the exporters given to it in order.
-func Multi(e ...Exporter) Exporter {
-	a := make(multi, 0, len(e))
-	for _, i := range e {
-		if i == nil {
-			continue
-		}
-		if i, ok := i.(multi); ok {
-			a = append(a, i...)
-			continue
-		}
-		a = append(a, i)
-	}
-	return a
-}
-
-type multi []Exporter
-
-func (m multi) StartSpan(ctx context.Context, span *telemetry.Span) {
-	for _, o := range m {
-		o.StartSpan(ctx, span)
-	}
-}
-func (m multi) FinishSpan(ctx context.Context, span *telemetry.Span) {
-	for _, o := range m {
-		o.FinishSpan(ctx, span)
-	}
-}
-func (m multi) Log(ctx context.Context, event telemetry.Event) {
-	for _, o := range m {
-		o.Log(ctx, event)
-	}
-}
-func (m multi) Metric(ctx context.Context, data telemetry.MetricData) {
-	for _, o := range m {
-		o.Metric(ctx, data)
-	}
-}
-func (m multi) Flush() {
-	for _, o := range m {
-		o.Flush()
-	}
-}
diff --git a/internal/telemetry/export/null.go b/internal/telemetry/export/null.go
deleted file mode 100644
index cc01ba7..0000000
--- a/internal/telemetry/export/null.go
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2019 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package export
-
-import (
-	"context"
-
-	"golang.org/x/tools/internal/telemetry"
-)
-
-// Null returns an observer that does nothing.
-func Null() Exporter {
-	return null{}
-}
-
-type null struct{}
-
-func (null) StartSpan(context.Context, *telemetry.Span)   {}
-func (null) FinishSpan(context.Context, *telemetry.Span)  {}
-func (null) Log(context.Context, telemetry.Event)         {}
-func (null) Metric(context.Context, telemetry.MetricData) {}
-func (null) Flush()                                       {}
diff --git a/internal/telemetry/export/ocagent/README.md b/internal/telemetry/export/ocagent/README.md
index a76f415..e1a9dc9 100644
--- a/internal/telemetry/export/ocagent/README.md
+++ b/internal/telemetry/export/ocagent/README.md
@@ -79,7 +79,7 @@
 		Rate:    5 * time.Second,
 		Client:  &http.Client{},
 	})
-	export.AddExporters(exporter)
+	export.SetExporter(exporter)
 
 	ctx := context.TODO()
 	mLatency := stats.Float64("latency", "the latency in milliseconds", "ms")
diff --git a/internal/telemetry/export/ocagent/ocagent.go b/internal/telemetry/export/ocagent/ocagent.go
index 31dacab..8e30a7d 100644
--- a/internal/telemetry/export/ocagent/ocagent.go
+++ b/internal/telemetry/export/ocagent/ocagent.go
@@ -78,7 +78,7 @@
 		exporter.config.Rate = 2 * time.Second
 	}
 	go func() {
-		for _ = range time.Tick(exporter.config.Rate) {
+		for range time.Tick(exporter.config.Rate) {
 			exporter.Flush()
 		}
 	}()
@@ -170,7 +170,6 @@
 	if res.Body != nil {
 		res.Body.Close()
 	}
-	return
 }
 
 func errorInExport(message string, args ...interface{}) {
@@ -191,10 +190,10 @@
 
 func convertSpan(span *telemetry.Span) *wire.Span {
 	result := &wire.Span{
-		TraceId:                 span.ID.TraceID[:],
-		SpanId:                  span.ID.SpanID[:],
+		TraceID:                 span.ID.TraceID[:],
+		SpanID:                  span.ID.SpanID[:],
 		TraceState:              nil, //TODO?
-		ParentSpanId:            span.ParentID[:],
+		ParentSpanID:            span.ParentID[:],
 		Name:                    toTruncatableString(span.Name),
 		Kind:                    wire.UnspecifiedSpanKind,
 		StartTime:               convertTimestamp(span.Start),
diff --git a/internal/telemetry/export/ocagent/wire/common.go b/internal/telemetry/export/ocagent/wire/common.go
index b53fb81..61dbfcd 100644
--- a/internal/telemetry/export/ocagent/wire/common.go
+++ b/internal/telemetry/export/ocagent/wire/common.go
@@ -55,7 +55,7 @@
 
 type StackTrace struct {
 	StackFrames      *StackFrames `json:"stack_frames,omitempty"`
-	StackTraceHashId uint64       `json:"stack_trace_hash_id,omitempty"`
+	StackTraceHashID uint64       `json:"stack_trace_hash_id,omitempty"`
 }
 
 type StackFrames struct {
@@ -75,7 +75,7 @@
 
 type Module struct {
 	Module  *TruncatableString `json:"module,omitempty"`
-	BuildId *TruncatableString `json:"build_id,omitempty"`
+	BuildID *TruncatableString `json:"build_id,omitempty"`
 }
 
 type ProcessIdentifier struct {
diff --git a/internal/telemetry/export/ocagent/wire/trace.go b/internal/telemetry/export/ocagent/wire/trace.go
index fb73743..c1a79a5 100644
--- a/internal/telemetry/export/ocagent/wire/trace.go
+++ b/internal/telemetry/export/ocagent/wire/trace.go
@@ -11,10 +11,10 @@
 }
 
 type Span struct {
-	TraceId                 []byte             `json:"trace_id,omitempty"`
-	SpanId                  []byte             `json:"span_id,omitempty"`
+	TraceID                 []byte             `json:"trace_id,omitempty"`
+	SpanID                  []byte             `json:"span_id,omitempty"`
 	TraceState              *TraceState        `json:"tracestate,omitempty"`
-	ParentSpanId            []byte             `json:"parent_span_id,omitempty"`
+	ParentSpanID            []byte             `json:"parent_span_id,omitempty"`
 	Name                    *TruncatableString `json:"name,omitempty"`
 	Kind                    SpanKind           `json:"kind,omitempty"`
 	StartTime               Timestamp          `json:"start_time,omitempty"`
@@ -65,7 +65,7 @@
 
 type MessageEvent struct {
 	Type             MessageEventType `json:"type,omitempty"`
-	Id               uint64           `json:"id,omitempty"`
+	ID               uint64           `json:"id,omitempty"`
 	UncompressedSize uint64           `json:"uncompressed_size,omitempty"`
 	CompressedSize   uint64           `json:"compressed_size,omitempty"`
 }
@@ -91,8 +91,8 @@
 }
 
 type Link struct {
-	TraceId    []byte      `json:"trace_id,omitempty"`
-	SpanId     []byte      `json:"span_id,omitempty"`
+	TraceID    []byte      `json:"trace_id,omitempty"`
+	SpanID     []byte      `json:"span_id,omitempty"`
 	Type       LinkType    `json:"type,omitempty"`
 	Attributes *Attributes `json:"attributes,omitempty"`
 	TraceState *TraceState `json:"tracestate,omitempty"`
diff --git a/internal/telemetry/export/prometheus/prometheus.go b/internal/telemetry/export/prometheus/prometheus.go
index ccbdf96..ffbb01d 100644
--- a/internal/telemetry/export/prometheus/prometheus.go
+++ b/internal/telemetry/export/prometheus/prometheus.go
@@ -25,11 +25,6 @@
 	metrics []telemetry.MetricData
 }
 
-func (e *Exporter) StartSpan(ctx context.Context, span *telemetry.Span)  {}
-func (e *Exporter) FinishSpan(ctx context.Context, span *telemetry.Span) {}
-func (e *Exporter) Log(ctx context.Context, event telemetry.Event)       {}
-func (e *Exporter) Flush()                                               {}
-
 func (e *Exporter) Metric(ctx context.Context, data telemetry.MetricData) {
 	e.mu.Lock()
 	defer e.mu.Unlock()
diff --git a/internal/telemetry/log/bench_test.go b/internal/telemetry/log/bench_test.go
index dc77bd6..16e3476 100644
--- a/internal/telemetry/log/bench_test.go
+++ b/internal/telemetry/log/bench_test.go
@@ -6,6 +6,7 @@
 	"strings"
 	"testing"
 
+	"golang.org/x/tools/internal/telemetry/export"
 	tellog "golang.org/x/tools/internal/telemetry/log"
 	"golang.org/x/tools/internal/telemetry/tag"
 )
@@ -20,14 +21,14 @@
 	return len(b), nil
 }
 
-func A(a int) int {
+func A(ctx context.Context, a int) int {
 	if a > 0 {
 		_ = 10 * 12
 	}
-	return B("Called from A")
+	return B(ctx, "Called from A")
 }
 
-func B(b string) int {
+func B(ctx context.Context, b string) int {
 	b = strings.ToUpper(b)
 	if len(b) > 1024 {
 		b = strings.ToLower(b)
@@ -54,16 +55,16 @@
 	return len(b)
 }
 
-func A_log_stdlib(a int) int {
+func A_log_stdlib(ctx context.Context, a int) int {
 	if a > 0 {
 		stdlog.Printf("a > 0 where a=%d", a)
 		_ = 10 * 12
 	}
 	stdlog.Print("calling b")
-	return B_log_stdlib("Called from A")
+	return B_log_stdlib(ctx, "Called from A")
 }
 
-func B_log_stdlib(b string) int {
+func B_log_stdlib(ctx context.Context, b string) int {
 	b = strings.ToUpper(b)
 	stdlog.Printf("b uppercased, so lowercased where len_b=%d", len(b))
 	if len(b) > 1024 {
@@ -73,12 +74,15 @@
 	return len(b)
 }
 
-func BenchmarkNoTracingNoMetricsNoLogging(b *testing.B) {
+var values = []int{0, 10, 20, 100, 1000}
+
+func BenchmarkBaseline(b *testing.B) {
+	ctx := context.Background()
 	b.ReportAllocs()
-	values := []int{0, 10, 20, 100, 1000}
+	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
 		for _, value := range values {
-			if g := A(value); g <= 0 {
+			if g := A(ctx, value); g <= 0 {
 				b.Fatalf("Unexpected got g(%d) <= 0", g)
 			}
 		}
@@ -86,11 +90,27 @@
 }
 
 func BenchmarkLoggingNoExporter(b *testing.B) {
+	ctx := context.Background()
+	export.SetExporter(nil)
 	b.ReportAllocs()
-	values := []int{0, 10, 20, 100, 1000}
+	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
 		for _, value := range values {
-			if g := A_log(context.TODO(), value); g <= 0 {
+			if g := A_log(ctx, value); g <= 0 {
+				b.Fatalf("Unexpected got g(%d) <= 0", g)
+			}
+		}
+	}
+}
+
+func BenchmarkLogging(b *testing.B) {
+	ctx := context.Background()
+	export.SetExporter(export.LogWriter(new(noopWriter), false))
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		for _, value := range values {
+			if g := A_log(ctx, value); g <= 0 {
 				b.Fatalf("Unexpected got g(%d) <= 0", g)
 			}
 		}
@@ -98,11 +118,12 @@
 }
 
 func BenchmarkLoggingStdlib(b *testing.B) {
+	ctx := context.Background()
 	b.ReportAllocs()
-	values := []int{0, 10, 20, 100, 1000}
+	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
 		for _, value := range values {
-			if g := A_log_stdlib(value); g <= 0 {
+			if g := A_log_stdlib(ctx, value); g <= 0 {
 				b.Fatalf("Unexpected got g(%d) <= 0", g)
 			}
 		}
diff --git a/present/link.go b/present/link.go
index 2aead35..52c52c0 100644
--- a/present/link.go
+++ b/present/link.go
@@ -78,19 +78,19 @@
 		return
 	}
 	if urlEnd == end {
-		simpleUrl := ""
+		simpleURL := ""
 		url, err := url.Parse(rawURL)
 		if err == nil {
 			// If the URL is http://foo.com, drop the http://
 			// In other words, render [[http://golang.org]] as:
 			//   <a href="http://golang.org">golang.org</a>
 			if strings.HasPrefix(rawURL, url.Scheme+"://") {
-				simpleUrl = strings.TrimPrefix(rawURL, url.Scheme+"://")
+				simpleURL = strings.TrimPrefix(rawURL, url.Scheme+"://")
 			} else if strings.HasPrefix(rawURL, url.Scheme+":") {
-				simpleUrl = strings.TrimPrefix(rawURL, url.Scheme+":")
+				simpleURL = strings.TrimPrefix(rawURL, url.Scheme+":")
 			}
 		}
-		return renderLink(rawURL, simpleUrl), end + 2
+		return renderLink(rawURL, simpleURL), end + 2
 	}
 	if s[urlEnd:urlEnd+2] != "][" {
 		return
diff --git a/present/parse.go b/present/parse.go
index dd0f00b..98baec7 100644
--- a/present/parse.go
+++ b/present/parse.go
@@ -402,7 +402,7 @@
 				}
 				parser := parsers[args[0]]
 				if parser == nil {
-					return nil, fmt.Errorf("%s:%d: unknown command %q\n", name, lines.line, text)
+					return nil, fmt.Errorf("%s:%d: unknown command %q", name, lines.line, text)
 				}
 				t, err := parser(ctx, name, lines.line, text)
 				if err != nil {