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

3db8fd26 internal/lsp: use a structured format for the server's version
8694a4a1 internal/lsp/source: don't find possible interface references to types
78b15858 internal/lsp/fake: reflect on-disk changes in clean buffers
bc9fc8d8 internal/lsp: fix flickering analysis diagnostics
bc3cf281 go/loader: loosen a test assertion on go/types error messages
22bd8527 internal/lsp: remove organize imports action for go.mod
aefe0b74 internal/lsp: set correct directness when adding new requires
330dc7d2 internal/lsp/cache: assign a static temp workspace dir to the first view
b6530511 internal/lsp/cmd: delete TestDefinitionHelpExample test
ac612aff internal/lsp: fix the logic to avoid duplicate file watching
6d1a7fa3 internal/imports: handle out of range panic in modInfo
7ad286ab internal/lsp: check for nil snapshot in didModifyFiles
c64668f4 internal/lsp: do not rename in compiler directive comments
f46e4245 internal/lsp/cache: handle nil pointer exception in missing module error
3f6de077 internal/lsp: make Diagnostics.CodeDescription a pointer
3288bc1e go/analysis: add frame pointer check for vet
b53d4cbd internal/lsp/cache: check for symlinks when checking "isSubdirectory"
8860a70d internal/lsp/cache: set a 15 minute deadline on calls to packages.Load
51cde522 internal/lsp: move initialization entirely into the snapshot
1f28ee68 internal/lsp: change `go mod vendor` warning into a diagnostic
582c62ec go/analysis/singlechecker: fix whitespace in package documentation
4fc0492b internal/lsp/cache: keep a cached workspace module dir
3734b819 internal/lsp: delay longer in TestDebouncer
d36b6f68 internal/memoize: add a final argument to Bind for cleaning up
f239dba4 internal/lsp/cache: consider gopls.mod when finding workspace root
d463eb0e internal/lsp/cache: introduce a workspace abstraction
443cd81a Revert "internal/lsp: move initialization entirely into the snapshot"
deb1282f internal/lsp: move initialization entirely into the snapshot
8da1a626 internal/lsp/source/completion: remove "completion_" prefix from files
589136c8 cmd/fiximports,cmd/present,cmd/stringer: update links to pkg.go.dev
cf7a54d0 internal/lsp/source: use bestMatch for fully qualified symbol style
63f8a171 internal/lsp: use the correct method name to register semantic tokens
2feb2bb1 internal/lsp: elide details for non-package files
186a7436 internal/lsp/source: respect user's hover kind in signature help
061905c3 internal/lsp/cache: stop unnecessarily waiting for IWL
c86e6230 internal/lsp/source: add missing vet analyzers
e7a17c4c internal/lsp/cache: preserve OS environment
690a3c24 go/analysis/passes/asmdecl: permit return jump without writing to results
2c115999 internal/lsp: use the go command to fix go.mod files
49729134 internal/lsp: unify go command invocation logic
832c4b44 internal/lsp/source: tweak the WorkspaceSymbols docstring
2b84a066 internal/lsp: use gocommand.Invocation more
5bbba664 internal/lsp/source: synchronous commands the default
dc70f74c internal/lsp: correct typo
0dcbe365 gopls/doc: update links from godoc.org to pkg.go.dev
eafbe7b9 internal/lsp/protocol/typescript: code for latest 3.16 LSP
8cd080b7 internal/lsp: handle nil pointer exceptions in check for Go files
0b86805d internal/lsp: finish work when synchronous commands complete
63122083 go/internal/gccgoimporter: support notinheap annotation
8dabb740 internal/lsp: update lsp protocol stubs to match LSP 3.16 revisions
e84cfc6d all: clear GOMODCACHE in tests
c8cfbd0f internal/lsp/source: handle nil pointer in rename_check.go
2f4fa188 go/packages: use native overlay support for 1.16
ffe8bce7 cmd/stress: print elapsed time, percentage failure
13b3b307 internal/lsp/semantic.go: remove global variable
9cf592e8 all: update all dependencies to latest pseudoversions

Change-Id: I1e690a9cb361ed05cc1d184951e06b3e2099dc08
diff --git a/cmd/fiximports/main.go b/cmd/fiximports/main.go
index 1fa87ea..53a9944 100644
--- a/cmd/fiximports/main.go
+++ b/cmd/fiximports/main.go
@@ -114,7 +114,7 @@
 in the style of the go tool; see "go help packages".
 Hint: use "all" or "..." to match the entire workspace.
 
-For details, see http://godoc.org/golang.org/x/tools/cmd/fiximports.
+For details, see https://pkg.go.dev/golang.org/x/tools/cmd/fiximports
 
 Flags:
   -n:	       dry run: show changes, but don't apply them
diff --git a/cmd/present/doc.go b/cmd/present/doc.go
index 9ad136e..e66984e 100644
--- a/cmd/present/doc.go
+++ b/cmd/present/doc.go
@@ -47,6 +47,6 @@
 	.article      // article format, such as a blog post
 
 The present file format is documented by the present package:
-http://godoc.org/golang.org/x/tools/present
+https://pkg.go.dev/golang.org/x/tools/present
 */
 package main
diff --git a/cmd/stress/stress.go b/cmd/stress/stress.go
index afcfa10..e127735 100644
--- a/cmd/stress/stress.go
+++ b/cmd/stress/stress.go
@@ -113,6 +113,7 @@
 		}()
 	}
 	runs, fails := 0, 0
+	start := time.Now()
 	ticker := time.NewTicker(5 * time.Second).C
 	for {
 		select {
@@ -137,7 +138,12 @@
 				fmt.Printf("\n%s\n%s\n", f.Name(), out)
 			}
 		case <-ticker:
-			fmt.Printf("%v runs so far, %v failures\n", runs, fails)
+			elapsed := time.Since(start).Truncate(time.Second)
+			var pct string
+			if fails > 0 {
+				pct = fmt.Sprintf(" (%0.2f%%)", 100.0*float64(fails)/float64(runs))
+			}
+			fmt.Printf("%v: %v runs so far, %v failures%s\n", elapsed, runs, fails, pct)
 		}
 	}
 }
diff --git a/cmd/stringer/stringer.go b/cmd/stringer/stringer.go
index a5cd587..558a234 100644
--- a/cmd/stringer/stringer.go
+++ b/cmd/stringer/stringer.go
@@ -98,7 +98,7 @@
 	fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T [directory]\n")
 	fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T files... # Must be a single package\n")
 	fmt.Fprintf(os.Stderr, "For more information, see:\n")
-	fmt.Fprintf(os.Stderr, "\thttp://godoc.org/golang.org/x/tools/cmd/stringer\n")
+	fmt.Fprintf(os.Stderr, "\thttps://pkg.go.dev/golang.org/x/tools/cmd/stringer\n")
 	fmt.Fprintf(os.Stderr, "Flags:\n")
 	flag.PrintDefaults()
 }
diff --git a/go.mod b/go.mod
index ecc60d6..57607cb 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@
 require (
 	github.com/yuin/goldmark v1.2.1
 	golang.org/x/mod v0.3.0
-	golang.org/x/net v0.0.0-20200822124328-c89045814202
-	golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
+	golang.org/x/net v0.0.0-20201021035429-f5854403a974
+	golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
 )
diff --git a/go.sum b/go.sum
index 83e5649..1608006 100644
--- a/go.sum
+++ b/go.sum
@@ -6,22 +6,20 @@
 golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/go/analysis/passes/asmdecl/asmdecl.go b/go/analysis/passes/asmdecl/asmdecl.go
index d63855b..eb0016b 100644
--- a/go/analysis/passes/asmdecl/asmdecl.go
+++ b/go/analysis/passes/asmdecl/asmdecl.go
@@ -308,7 +308,8 @@
 				continue
 			}
 
-			if strings.Contains(line, "RET") {
+			if strings.Contains(line, "RET") && !strings.Contains(line, "(SB)") {
+				// RET f(SB) is a tail call. It is okay to not write the results.
 				retLine = append(retLine, lineno)
 			}
 
diff --git a/go/analysis/passes/asmdecl/testdata/src/a/asm.go b/go/analysis/passes/asmdecl/testdata/src/a/asm.go
index 27470c5..6bcfb2f 100644
--- a/go/analysis/passes/asmdecl/testdata/src/a/asm.go
+++ b/go/analysis/passes/asmdecl/testdata/src/a/asm.go
@@ -51,3 +51,5 @@
 func pickStableABI(x int)
 func pickInternalABI(x int)
 func pickFutureABI(x int)
+
+func retjmp() int
diff --git a/go/analysis/passes/asmdecl/testdata/src/a/asm1.s b/go/analysis/passes/asmdecl/testdata/src/a/asm1.s
index 8fa0401..8c43223 100644
--- a/go/analysis/passes/asmdecl/testdata/src/a/asm1.s
+++ b/go/analysis/passes/asmdecl/testdata/src/a/asm1.s
@@ -345,3 +345,7 @@
 TEXT ·pickFutureABI<ABISomethingNotyetInvented>(SB), NOSPLIT, $32
 	MOVQ	x+0(FP), AX
 	RET
+
+// return jump
+TEXT ·retjmp(SB), NOSPLIT, $0-8
+	RET	retjmp1(SB) // It's okay to not write results if there's a tail call.
diff --git a/go/analysis/passes/framepointer/framepointer.go b/go/analysis/passes/framepointer/framepointer.go
new file mode 100644
index 0000000..6c13eeb
--- /dev/null
+++ b/go/analysis/passes/framepointer/framepointer.go
@@ -0,0 +1,91 @@
+// 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 framepointer defines an Analyzer that reports assembly code
+// that clobbers the frame pointer before saving it.
+package framepointer
+
+import (
+	"go/build"
+	"regexp"
+	"strings"
+
+	"golang.org/x/tools/go/analysis"
+	"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
+)
+
+const Doc = "report assembly that clobbers the frame pointer before saving it"
+
+var Analyzer = &analysis.Analyzer{
+	Name: "framepointer",
+	Doc:  Doc,
+	Run:  run,
+}
+
+var (
+	re             = regexp.MustCompile
+	asmWriteBP     = re(`,\s*BP$`) // TODO: can have false positive, e.g. for TESTQ BP,BP. Seems unlikely.
+	asmMentionBP   = re(`\bBP\b`)
+	asmControlFlow = re(`^(J|RET)`)
+)
+
+func run(pass *analysis.Pass) (interface{}, error) {
+	if build.Default.GOARCH != "amd64" { // TODO: arm64 also?
+		return nil, nil
+	}
+	if build.Default.GOOS != "linux" && build.Default.GOOS != "darwin" {
+		return nil, nil
+	}
+
+	// Find assembly files to work on.
+	var sfiles []string
+	for _, fname := range pass.OtherFiles {
+		if strings.HasSuffix(fname, ".s") {
+			sfiles = append(sfiles, fname)
+		}
+	}
+
+	for _, fname := range sfiles {
+		content, tf, err := analysisutil.ReadFile(pass.Fset, fname)
+		if err != nil {
+			return nil, err
+		}
+
+		lines := strings.SplitAfter(string(content), "\n")
+		active := false
+		for lineno, line := range lines {
+			lineno++
+
+			// Ignore comments and commented-out code.
+			if i := strings.Index(line, "//"); i >= 0 {
+				line = line[:i]
+			}
+			line = strings.TrimSpace(line)
+
+			// We start checking code at a TEXT line for a frameless function.
+			if strings.HasPrefix(line, "TEXT") && strings.Contains(line, "(SB)") && strings.Contains(line, "$0") {
+				active = true
+				continue
+			}
+			if !active {
+				continue
+			}
+
+			if asmWriteBP.MatchString(line) { // clobber of BP, function is not OK
+				pass.Reportf(analysisutil.LineStart(tf, lineno), "frame pointer is clobbered before saving")
+				active = false
+				continue
+			}
+			if asmMentionBP.MatchString(line) { // any other use of BP might be a read, so function is OK
+				active = false
+				continue
+			}
+			if asmControlFlow.MatchString(line) { // give up after any branch instruction
+				active = false
+				continue
+			}
+		}
+	}
+	return nil, nil
+}
diff --git a/go/analysis/passes/framepointer/framepointer_test.go b/go/analysis/passes/framepointer/framepointer_test.go
new file mode 100644
index 0000000..e849308
--- /dev/null
+++ b/go/analysis/passes/framepointer/framepointer_test.go
@@ -0,0 +1,26 @@
+// 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 framepointer_test
+
+import (
+	"go/build"
+	"testing"
+
+	"golang.org/x/tools/go/analysis/analysistest"
+	"golang.org/x/tools/go/analysis/passes/framepointer"
+)
+
+func Test(t *testing.T) {
+	if build.Default.GOOS != "linux" && build.Default.GOOS != "darwin" {
+		// The test has an os-generic assembly file, testdata/a/asm_amd64.s.
+		// It should produce errors on linux or darwin, but not on other archs.
+		// Unfortunately, there's no way to say that in the "want" comments
+		// in that file. So we skip testing on other GOOSes. The framepointer
+		// analyzer should not report any errors on those GOOSes, so it's not
+		// really a hard test on those platforms.
+		t.Skipf("test for GOOS=%s is not implemented", build.Default.GOOS)
+	}
+	analysistest.Run(t, analysistest.TestData(), framepointer.Analyzer, "a")
+}
diff --git a/go/analysis/passes/framepointer/testdata/src/a/asm.go b/go/analysis/passes/framepointer/testdata/src/a/asm.go
new file mode 100644
index 0000000..20d6846
--- /dev/null
+++ b/go/analysis/passes/framepointer/testdata/src/a/asm.go
@@ -0,0 +1,5 @@
+// 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 a
diff --git a/go/analysis/passes/framepointer/testdata/src/a/asm_amd64.s b/go/analysis/passes/framepointer/testdata/src/a/asm_amd64.s
new file mode 100644
index 0000000..a7d1b1c
--- /dev/null
+++ b/go/analysis/passes/framepointer/testdata/src/a/asm_amd64.s
@@ -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.
+
+TEXT ·bad1(SB), 0, $0
+	MOVQ	$0, BP // want `frame pointer is clobbered before saving`
+	RET
+TEXT ·bad2(SB), 0, $0
+	MOVQ	AX, BP // want `frame pointer is clobbered before saving`
+	RET
+TEXT ·bad3(SB), 0, $0
+	MOVQ	6(AX), BP // want `frame pointer is clobbered before saving`
+	RET
+TEXT ·good1(SB), 0, $0
+	PUSHQ	BP
+	MOVQ	$0, BP // this is ok
+	POPQ	BP
+	RET
+TEXT ·good2(SB), 0, $0
+	MOVQ	BP, BX
+	MOVQ	$0, BP // this is ok
+	MOVQ	BX, BP
+	RET
+TEXT ·good3(SB), 0, $0
+	CMPQ	AX, BX
+	JEQ	skip
+	MOVQ	$0, BP // this is ok
+skip:
+	RET
+TEXT ·good4(SB), 0, $0
+	RET
+	MOVQ	$0, BP // this is ok
+	RET
+TEXT ·good5(SB), 0, $8
+	MOVQ	$0, BP // this is ok
+	RET
diff --git a/go/analysis/passes/framepointer/testdata/src/a/asm_darwin_amd64.s b/go/analysis/passes/framepointer/testdata/src/a/asm_darwin_amd64.s
new file mode 100644
index 0000000..4a159ec
--- /dev/null
+++ b/go/analysis/passes/framepointer/testdata/src/a/asm_darwin_amd64.s
@@ -0,0 +1,7 @@
+// 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.
+
+TEXT ·z1(SB), 0, $0
+	MOVQ	$0, BP // want `frame pointer is clobbered before saving`
+	RET
diff --git a/go/analysis/passes/framepointer/testdata/src/a/asm_linux_amd64.s b/go/analysis/passes/framepointer/testdata/src/a/asm_linux_amd64.s
new file mode 100644
index 0000000..4a159ec
--- /dev/null
+++ b/go/analysis/passes/framepointer/testdata/src/a/asm_linux_amd64.s
@@ -0,0 +1,7 @@
+// 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.
+
+TEXT ·z1(SB), 0, $0
+	MOVQ	$0, BP // want `frame pointer is clobbered before saving`
+	RET
diff --git a/go/analysis/passes/framepointer/testdata/src/a/asm_windows_amd64.s b/go/analysis/passes/framepointer/testdata/src/a/asm_windows_amd64.s
new file mode 100644
index 0000000..9e81d69
--- /dev/null
+++ b/go/analysis/passes/framepointer/testdata/src/a/asm_windows_amd64.s
@@ -0,0 +1,7 @@
+// 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.
+
+TEXT ·z1(SB), 0, $0
+	MOVQ	$0, BP // not an error on windows
+	RET
diff --git a/go/analysis/passes/framepointer/testdata/src/a/buildtag_amd64.s b/go/analysis/passes/framepointer/testdata/src/a/buildtag_amd64.s
new file mode 100644
index 0000000..6d2ea43
--- /dev/null
+++ b/go/analysis/passes/framepointer/testdata/src/a/buildtag_amd64.s
@@ -0,0 +1,9 @@
+// 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 nope
+
+TEXT ·bt1(SB), 0, $0
+	MOVQ	$0, BP // ok because of build tag
+	RET
diff --git a/go/analysis/singlechecker/singlechecker.go b/go/analysis/singlechecker/singlechecker.go
index 500a40a..2853077 100644
--- a/go/analysis/singlechecker/singlechecker.go
+++ b/go/analysis/singlechecker/singlechecker.go
@@ -11,7 +11,7 @@
 // all that is needed to define a standalone tool is a file,
 // example.org/findbadness/cmd/findbadness/main.go, containing:
 //
-//      // The findbadness command runs an analysis.
+// 	// The findbadness command runs an analysis.
 // 	package main
 //
 // 	import (
diff --git a/go/internal/gccgoimporter/importer_test.go b/go/internal/gccgoimporter/importer_test.go
index 6c3644e..d6fe970 100644
--- a/go/internal/gccgoimporter/importer_test.go
+++ b/go/internal/gccgoimporter/importer_test.go
@@ -100,6 +100,7 @@
 	{pkgpath: "issue30628", name: "Apple", want: "type Apple struct{hey sync.RWMutex; x int; RQ [517]struct{Count uintptr; NumBytes uintptr; Last uintptr}}"},
 	{pkgpath: "issue31540", name: "S", gccgoVersion: 7, want: "type S struct{b int; map[Y]Z}"},
 	{pkgpath: "issue34182", name: "T1", want: "type T1 struct{f *T2}"},
+	{pkgpath: "notinheap", name: "S", want: "type S struct{}"},
 }
 
 func TestGoxImporter(t *testing.T) {
diff --git a/go/internal/gccgoimporter/parser.go b/go/internal/gccgoimporter/parser.go
index 29e8c60..7f07553 100644
--- a/go/internal/gccgoimporter/parser.go
+++ b/go/internal/gccgoimporter/parser.go
@@ -521,6 +521,13 @@
 		p.errorf("%v has nil type", obj)
 	}
 
+	if p.tok == scanner.Ident && p.lit == "notinheap" {
+		p.next()
+		// The go/types package has no way of recording that
+		// this type is marked notinheap. Presumably no user
+		// of this package actually cares.
+	}
+
 	// type alias
 	if p.tok == '=' {
 		p.next()
diff --git a/go/internal/gccgoimporter/testdata/notinheap.go b/go/internal/gccgoimporter/testdata/notinheap.go
new file mode 100644
index 0000000..b1ac967
--- /dev/null
+++ b/go/internal/gccgoimporter/testdata/notinheap.go
@@ -0,0 +1,4 @@
+package notinheap
+
+//go:notinheap
+type S struct{}
diff --git a/go/internal/gccgoimporter/testdata/notinheap.gox b/go/internal/gccgoimporter/testdata/notinheap.gox
new file mode 100644
index 0000000..cc438e7
--- /dev/null
+++ b/go/internal/gccgoimporter/testdata/notinheap.gox
@@ -0,0 +1,7 @@
+v3;
+package notinheap
+pkgpath notinheap
+init notinheap ~notinheap
+types 3 2 30 18
+type 1 "S" notinheap <type 2>
+type 2 struct { }
diff --git a/go/loader/loader_test.go b/go/loader/loader_test.go
index 5fa3254..e68405a 100644
--- a/go/loader/loader_test.go
+++ b/go/loader/loader_test.go
@@ -645,8 +645,11 @@
 	for pkg, info := range prog.AllPackages {
 		switch pkg.Path() {
 		case "a":
-			if !hasError(info.Errors, "cannot convert false") {
-				t.Errorf("a.Errors = %v, want bool conversion (type) error", info.Errors)
+			// The match below is unfortunately vague, because in go1.16 the error
+			// message in go/types changed from "cannot convert ..." to "cannot use
+			// ... as ... in assignment".
+			if !hasError(info.Errors, "cannot") {
+				t.Errorf("a.Errors = %v, want bool assignment (type) error", info.Errors)
 			}
 			if !hasError(info.Errors, "could not import c") {
 				t.Errorf("a.Errors = %v, want import (loader) error", info.Errors)
@@ -659,7 +662,7 @@
 	}
 
 	// Check errors reported via error handler.
-	if !hasError(allErrors, "cannot convert false") ||
+	if !hasError(allErrors, "cannot") ||
 		!hasError(allErrors, "rune literal not terminated") ||
 		!hasError(allErrors, "could not import c") {
 		t.Errorf("allErrors = %v, want syntax, type and loader errors", allErrors)
diff --git a/go/packages/golist.go b/go/packages/golist.go
index 787783c..81381fa 100644
--- a/go/packages/golist.go
+++ b/go/packages/golist.go
@@ -10,6 +10,7 @@
 	"encoding/json"
 	"fmt"
 	"go/types"
+	"io/ioutil"
 	"log"
 	"os"
 	"os/exec"
@@ -208,56 +209,58 @@
 		}
 	}
 
-	modifiedPkgs, needPkgs, err := state.processGolistOverlay(response)
-	if err != nil {
-		return nil, err
-	}
+	// Only use go/packages' overlay processing if we're using a Go version
+	// below 1.16. Otherwise, go list handles it.
+	if goVersion, err := state.getGoVersion(); err == nil && goVersion < 16 {
+		modifiedPkgs, needPkgs, err := state.processGolistOverlay(response)
+		if err != nil {
+			return nil, err
+		}
 
-	var containsCandidates []string
-	if len(containFiles) > 0 {
-		containsCandidates = append(containsCandidates, modifiedPkgs...)
-		containsCandidates = append(containsCandidates, needPkgs...)
-	}
-	if err := state.addNeededOverlayPackages(response, needPkgs); err != nil {
-		return nil, err
-	}
-	// Check candidate packages for containFiles.
-	if len(containFiles) > 0 {
-		for _, id := range containsCandidates {
-			pkg, ok := response.seenPackages[id]
-			if !ok {
-				response.addPackage(&Package{
-					ID: id,
-					Errors: []Error{
-						{
+		var containsCandidates []string
+		if len(containFiles) > 0 {
+			containsCandidates = append(containsCandidates, modifiedPkgs...)
+			containsCandidates = append(containsCandidates, needPkgs...)
+		}
+		if err := state.addNeededOverlayPackages(response, needPkgs); err != nil {
+			return nil, err
+		}
+		// Check candidate packages for containFiles.
+		if len(containFiles) > 0 {
+			for _, id := range containsCandidates {
+				pkg, ok := response.seenPackages[id]
+				if !ok {
+					response.addPackage(&Package{
+						ID: id,
+						Errors: []Error{{
 							Kind: ListError,
 							Msg:  fmt.Sprintf("package %s expected but not seen", id),
-						},
-					},
-				})
-				continue
-			}
-			for _, f := range containFiles {
-				for _, g := range pkg.GoFiles {
-					if sameFile(f, g) {
-						response.addRoot(id)
+						}},
+					})
+					continue
+				}
+				for _, f := range containFiles {
+					for _, g := range pkg.GoFiles {
+						if sameFile(f, g) {
+							response.addRoot(id)
+						}
 					}
 				}
 			}
 		}
-	}
-	// Add root for any package that matches a pattern. This applies only to
-	// packages that are modified by overlays, since they are not added as
-	// roots automatically.
-	for _, pattern := range restPatterns {
-		match := matchPattern(pattern)
-		for _, pkgID := range modifiedPkgs {
-			pkg, ok := response.seenPackages[pkgID]
-			if !ok {
-				continue
-			}
-			if match(pkg.PkgPath) {
-				response.addRoot(pkg.ID)
+		// Add root for any package that matches a pattern. This applies only to
+		// packages that are modified by overlays, since they are not added as
+		// roots automatically.
+		for _, pattern := range restPatterns {
+			match := matchPattern(pattern)
+			for _, pkgID := range modifiedPkgs {
+				pkg, ok := response.seenPackages[pkgID]
+				if !ok {
+					continue
+				}
+				if match(pkg.PkgPath) {
+					response.addRoot(pkg.ID)
+				}
 			}
 		}
 	}
@@ -835,6 +838,26 @@
 	cfg := state.cfg
 
 	inv := state.cfgInvocation()
+
+	// For Go versions 1.16 and above, `go list` accepts overlays directly via
+	// the -overlay flag. Set it, if it's available.
+	//
+	// The check for "list" is not necessarily required, but we should avoid
+	// getting the go version if possible.
+	if verb == "list" {
+		goVersion, err := state.getGoVersion()
+		if err != nil {
+			return nil, err
+		}
+		if goVersion >= 16 {
+			filename, cleanup, err := state.writeOverlays()
+			if err != nil {
+				return nil, err
+			}
+			defer cleanup()
+			inv.Overlay = filename
+		}
+	}
 	inv.Verb = verb
 	inv.Args = args
 	gocmdRunner := cfg.gocmdRunner
@@ -976,6 +999,67 @@
 	return stdout, nil
 }
 
+// OverlayJSON is the format overlay files are expected to be in.
+// The Replace map maps from overlaid paths to replacement paths:
+// the Go command will forward all reads trying to open
+// each overlaid path to its replacement path, or consider the overlaid
+// path not to exist if the replacement path is empty.
+//
+// From golang/go#39958.
+type OverlayJSON struct {
+	Replace map[string]string `json:"replace,omitempty"`
+}
+
+// writeOverlays writes out files for go list's -overlay flag, as described
+// above.
+func (state *golistState) writeOverlays() (filename string, cleanup func(), err error) {
+	// Do nothing if there are no overlays in the config.
+	if len(state.cfg.Overlay) == 0 {
+		return "", func() {}, nil
+	}
+	dir, err := ioutil.TempDir("", "gopackages-*")
+	if err != nil {
+		return "", nil, err
+	}
+	// The caller must clean up this directory, unless this function returns an
+	// error.
+	cleanup = func() {
+		os.RemoveAll(dir)
+	}
+	defer func() {
+		if err != nil {
+			cleanup()
+		}
+	}()
+	overlays := map[string]string{}
+	for k, v := range state.cfg.Overlay {
+		// Create a unique filename for the overlaid files, to avoid
+		// creating nested directories.
+		noSeparator := strings.Join(strings.Split(filepath.ToSlash(k), "/"), "")
+		f, err := ioutil.TempFile(dir, fmt.Sprintf("*-%s", noSeparator))
+		if err != nil {
+			return "", func() {}, err
+		}
+		if _, err := f.Write(v); err != nil {
+			return "", func() {}, err
+		}
+		if err := f.Close(); err != nil {
+			return "", func() {}, err
+		}
+		overlays[k] = f.Name()
+	}
+	b, err := json.Marshal(OverlayJSON{Replace: overlays})
+	if err != nil {
+		return "", func() {}, err
+	}
+	// Write out the overlay file that contains the filepath mappings.
+	filename = filepath.Join(dir, "overlay.json")
+	if err := ioutil.WriteFile(filename, b, 0665); err != nil {
+		return "", func() {}, err
+	}
+	return filename, cleanup, nil
+}
+
 func containsGoFile(s []string) bool {
 	for _, f := range s {
 		if strings.HasSuffix(f, ".go") {
diff --git a/go/packages/overlay_test.go b/go/packages/overlay_test.go
index a706570..8ce5a7c 100644
--- a/go/packages/overlay_test.go
+++ b/go/packages/overlay_test.go
@@ -87,6 +87,9 @@
 		{"fake [fake.test]", "foox", 2},
 		{"fake.test", "main", 1},
 	}
+	if len(initial) != 3 {
+		t.Fatalf("expected 3 packages, got %v", len(initial))
+	}
 	for i := 0; i < 3; i++ {
 		if ok := checkPkg(t, initial[i], want[i].id, want[i].name, want[i].count); !ok {
 			t.Errorf("%d: got {%s %s %d}, expected %v", i, initial[i].ID,
@@ -102,7 +105,8 @@
 	packagestest.TestAll(t, testOverlayChangesTestPackageName)
 }
 func testOverlayChangesTestPackageName(t *testing.T, exporter packagestest.Exporter) {
-	log.SetFlags(log.Lshortfile)
+	testenv.NeedsGo1Point(t, 16)
+
 	exported := packagestest.Export(t, exporter, []packagestest.Module{{
 		Name: "fake",
 		Files: map[string]interface{}{
@@ -127,10 +131,13 @@
 		id, name string
 		count    int
 	}{
-		{"fake", "foo", 0},
+		{"fake", "foox", 0},
 		{"fake [fake.test]", "foox", 1},
 		{"fake.test", "main", 1},
 	}
+	if len(initial) != 3 {
+		t.Fatalf("expected 3 packages, got %v", len(initial))
+	}
 	for i := 0; i < 3; i++ {
 		if ok := checkPkg(t, initial[i], want[i].id, want[i].name, want[i].count); !ok {
 			t.Errorf("got {%s %s %d}, expected %v", initial[i].ID,
@@ -329,6 +336,9 @@
 
 	// Find package golang.org/fake/c
 	sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].ID < pkgs[j].ID })
+	if len(pkgs) != 2 {
+		t.Fatalf("expected 2 packages, got %v", len(pkgs))
+	}
 	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)
@@ -804,6 +814,9 @@
 				if err != nil {
 					t.Fatal(err)
 				}
+				if len(initial) != 1 {
+					t.Fatalf("expected 1 packages, got %v", len(initial))
+				}
 				pkg := initial[0]
 				if pkg.ID != tt.wantID {
 					t.Fatalf("expected package ID %q, got %q", tt.wantID, pkg.ID)
@@ -986,7 +999,7 @@
 				}
 			}
 			if match == nil {
-				t.Fatalf(`expected package path "golang.org/fake/a", got %q`, match.PkgPath)
+				t.Fatalf(`expected package path "golang.org/fake/a", got none`)
 			}
 			if match.PkgPath != "golang.org/fake/a" {
 				t.Fatalf(`expected package path "golang.org/fake/a", got %q`, match.PkgPath)
@@ -1072,6 +1085,9 @@
 	if err != nil {
 		t.Error(err)
 	}
+	if len(initial) != 1 {
+		t.Fatalf(`expected 1 package, got %v`, len(initial))
+	}
 	pkg := initial[0]
 	if pkg.PkgPath != "b.com/inner" {
 		t.Fatalf(`expected package path "b.com/inner", got %q`, pkg.PkgPath)
diff --git a/go/packages/packagestest/modules.go b/go/packages/packagestest/modules.go
index 4608766..42b6206 100644
--- a/go/packages/packagestest/modules.go
+++ b/go/packages/packagestest/modules.go
@@ -163,6 +163,7 @@
 	exported.Config.Env = append(exported.Config.Env,
 		"GO111MODULE=on",
 		"GOPATH="+filepath.Join(exported.temp, "modcache"),
+		"GOMODCACHE=",
 		"GOPROXY="+proxydir.ToURL(modProxyDir),
 		"GOSUMDB=off",
 	)
diff --git a/gopls/doc/commands.md b/gopls/doc/commands.md
index d660835..f489f27 100644
--- a/gopls/doc/commands.md
+++ b/gopls/doc/commands.md
@@ -41,12 +41,24 @@
 name.
 
 
+### **Add dependency**
+Identifier: `gopls.add_dependency`
+
+add_dependency adds a dependency.
+
+
 ### **Upgrade dependency**
 Identifier: `gopls.upgrade_dependency`
 
 upgrade_dependency upgrades a dependency.
 
 
+### **Remove dependency**
+Identifier: `gopls.remove_dependency`
+
+remove_dependency removes a dependency.
+
+
 ### **Run go mod vendor**
 Identifier: `gopls.vendor`
 
diff --git a/gopls/doc/design.md b/gopls/doc/design.md
index 131db79..6edd9aa 100644
--- a/gopls/doc/design.md
+++ b/gopls/doc/design.md
@@ -364,11 +364,11 @@
 [godoc]: https://golang.org/cmd/godoc
 [gofmt]: https://golang.org/cmd/gofmt
 [gogetdoc]: https://github.com/zmb3/gogetdoc
-[goimports]: https://godoc.org/golang.org/x/tools/cmd/goimports
-[gorename]: https://godoc.org/golang.org/x/tools/cmd/gorename
+[goimports]: https://pkg.go.dev/golang.org/x/tools/cmd/goimports
+[gorename]: https://pkg.go.dev/golang.org/x/tools/cmd/gorename
 [goreturns]: https://github.com/sqs/goreturns
 [gotags]: https://github.com/jstemmer/gotags
-[guru]: https://godoc.org/golang.org/x/tools/cmd/guru
+[guru]: https://pkg.go.dev/golang.org/x/tools/cmd/guru
 [impl]: https://github.com/josharian/impl
 [staticcheck]: https://staticcheck.io/docs/
 [go/types]: https://golang.org/pkg/go/types/
diff --git a/gopls/doc/integrating.md b/gopls/doc/integrating.md
index 21a7fb9..845f9eb 100644
--- a/gopls/doc/integrating.md
+++ b/gopls/doc/integrating.md
@@ -61,9 +61,9 @@
 Monitoring files inside gopls directly has a lot of awkward problems, but the [LSP specification] has methods that allow gopls to request that the client notify it of file system changes, specifically [`workspace/didChangeWatchedFiles`].
 This is currently being added to gopls by a community member, and tracked in [#31553]
 
-[InitializeResult]: https://godoc.org/golang.org/x/tools/internal/lsp/protocol#InitializeResult
-[ServerCapabilities]: https://godoc.org/golang.org/x/tools/internal/lsp/protocol#ServerCapabilities
-[`golang.org/x/tools/internal/span`]: https://godoc.org/golang.org/x/tools/internal/span#NewPoint
+[InitializeResult]: https://pkg.go.dev/golang.org/x/tools/internal/lsp/protocol#InitializeResult
+[ServerCapabilities]: https://pkg.go.dev/golang.org/x/tools/internal/lsp/protocol#ServerCapabilities
+[`golang.org/x/tools/internal/span`]: https://pkg.go.dev/golang.org/x/tools/internal/span#NewPoint
 
 [LSP specification]: https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/
 [lsp-response]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#response-message
@@ -88,4 +88,4 @@
 
 [#31080]: https://github.com/golang/go/issues/31080
 [#31553]: https://github.com/golang/go/issues/31553
-[#31526]: https://github.com/golang/go/issues/31526
\ No newline at end of file
+[#31526]: https://github.com/golang/go/issues/31526
diff --git a/gopls/go.mod b/gopls/go.mod
index bd3e083..633d7ab 100644
--- a/gopls/go.mod
+++ b/gopls/go.mod
@@ -4,10 +4,10 @@
 
 require (
 	github.com/sergi/go-diff v1.1.0
-	golang.org/x/tools v0.0.0-20201028153306-37f0764111ff
+	golang.org/x/tools v0.0.0-20201021214918-23787c007979
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
-	honnef.co/go/tools v0.0.1-2020.1.5
-	mvdan.cc/gofumpt v0.0.0-20200802201014-ab5a8192947d
+	honnef.co/go/tools v0.0.1-2020.1.6
+	mvdan.cc/gofumpt v0.0.0-20200927160801-5bfeb2e70dd6
 	mvdan.cc/xurls/v2 v2.2.0
 )
 
diff --git a/gopls/go.sum b/gopls/go.sum
index 5c6e6c4..e813703 100644
--- a/gopls/go.sum
+++ b/gopls/go.sum
@@ -24,22 +24,21 @@
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -51,9 +50,9 @@
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-honnef.co/go/tools v0.0.1-2020.1.5 h1:nI5egYTGJakVyOryqLs1cQO5dO0ksin5XXs2pspk75k=
-honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-mvdan.cc/gofumpt v0.0.0-20200802201014-ab5a8192947d h1:t8TAw9WgTLghti7RYkpPmqk4JtQ3+wcP5GgZqgWeWLQ=
-mvdan.cc/gofumpt v0.0.0-20200802201014-ab5a8192947d/go.mod h1:bzrjFmaD6+xqohD3KYP0H2FEuxknnBmyyOxdhLdaIws=
+honnef.co/go/tools v0.0.1-2020.1.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc=
+honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY=
+mvdan.cc/gofumpt v0.0.0-20200927160801-5bfeb2e70dd6 h1:z+/YqapuV7VZPvBb3GYmuEJbA88M3PFUxaHilHYVCpQ=
+mvdan.cc/gofumpt v0.0.0-20200927160801-5bfeb2e70dd6/go.mod h1:bzrjFmaD6+xqohD3KYP0H2FEuxknnBmyyOxdhLdaIws=
 mvdan.cc/xurls/v2 v2.2.0 h1:NSZPykBXJFCetGZykLAxaL6SIpvbVy/UFEniIfHAa8A=
 mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8=
diff --git a/gopls/internal/regtest/codelens_test.go b/gopls/internal/regtest/codelens_test.go
index 1c2e709..a5fca84 100644
--- a/gopls/internal/regtest/codelens_test.go
+++ b/gopls/internal/regtest/codelens_test.go
@@ -9,6 +9,7 @@
 	"strings"
 	"testing"
 
+	"golang.org/x/tools/internal/lsp"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/tests"
@@ -109,26 +110,9 @@
 `
 	runner.Run(t, shouldUpdateDep, func(t *testing.T, env *Env) {
 		env.OpenFile("go.mod")
-		lenses := env.CodeLens("go.mod")
-		want := "Upgrade dependency to v1.3.3"
-		var found protocol.CodeLens
-		for _, lens := range lenses {
-			if lens.Command.Title == want {
-				found = lens
-				break
-			}
-		}
-		if found.Command.Command == "" {
-			t.Fatalf("did not find lens %q, got %v", want, lenses)
-		}
-		if _, err := env.Editor.ExecuteCommand(env.Ctx, &protocol.ExecuteCommandParams{
-			Command:   found.Command.Command,
-			Arguments: found.Command.Arguments,
-		}); err != nil {
-			t.Fatal(err)
-		}
-		env.Await(NoOutstandingWork())
-		got := env.ReadWorkspaceFile("go.mod")
+		env.ExecuteCodeLensCommand("go.mod", source.CommandUpgradeDependency)
+		env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1))
+		got := env.Editor.BufferText("go.mod")
 		const wantGoMod = `module mod.com
 
 go 1.12
@@ -182,8 +166,8 @@
 	runner.Run(t, shouldRemoveDep, func(t *testing.T, env *Env) {
 		env.OpenFile("go.mod")
 		env.ExecuteCodeLensCommand("go.mod", source.CommandTidy)
-		env.Await(NoOutstandingWork())
-		got := env.ReadWorkspaceFile("go.mod")
+		env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1))
+		got := env.Editor.BufferText("go.mod")
 		const wantGoMod = `module mod.com
 
 go 1.14
diff --git a/gopls/internal/regtest/diagnostics_test.go b/gopls/internal/regtest/diagnostics_test.go
index 18f19c0..d3685dd 100644
--- a/gopls/internal/regtest/diagnostics_test.go
+++ b/gopls/internal/regtest/diagnostics_test.go
@@ -716,7 +716,6 @@
 		WithProxyFiles(ardanLabsProxy),
 	).run(t, emptyFile, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
-		env.OpenFile("go.mod")
 		env.EditBuffer("main.go", fake.NewEdit(0, 0, 0, 0, `package main
 
 import "github.com/ardanlabs/conf"
@@ -734,6 +733,7 @@
 			),
 		)
 		env.ApplyQuickFixes("main.go", d.Diagnostics)
+		env.CheckForFileChanges()
 		env.Await(
 			EmptyDiagnostics("main.go"),
 		)
diff --git a/gopls/internal/regtest/modfile_test.go b/gopls/internal/regtest/modfile_test.go
index f7f745f..2f897a2 100644
--- a/gopls/internal/regtest/modfile_test.go
+++ b/gopls/internal/regtest/modfile_test.go
@@ -141,9 +141,8 @@
 				randomDiag = diag
 			}
 		}
-		env.OpenFile("go.mod")
 		env.ApplyQuickFixes("main.go", []protocol.Diagnostic{randomDiag})
-		if got := env.Editor.BufferText("go.mod"); got != want {
+		if got := env.ReadWorkspaceFile("go.mod"); got != want {
 			t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(want, got))
 		}
 	})
@@ -223,7 +222,7 @@
 			),
 		)
 		env.ApplyQuickFixes("go.mod", d.Diagnostics)
-		if got := env.Editor.BufferText("go.mod"); got != want {
+		if got := env.ReadWorkspaceFile("go.mod"); got != want {
 			t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(want, got))
 		}
 	})
@@ -268,7 +267,6 @@
     caire.RemoveTempImage()
 }`
 	runner.Run(t, repro, func(t *testing.T, env *Env) {
-		env.OpenFile("go.mod")
 		env.OpenFile("main.go")
 		var d protocol.PublishDiagnosticsParams
 		env.Await(
@@ -287,7 +285,7 @@
 	google.golang.org/protobuf v1.20.0
 )
 `
-		if got := env.Editor.BufferText("go.mod"); got != want {
+		if got := env.ReadWorkspaceFile("go.mod"); got != want {
 			t.Fatalf("TestNewDepWithUnusedDep failed:\n%s", tests.Diff(want, got))
 		}
 	}, WithProxyFiles(proxy))
@@ -321,12 +319,12 @@
 	}, WithProxyFiles(proxy))
 }
 
+// Tests golang/go#39784: a missing indirect dependency, necessary
+// due to blah@v2.0.0's incomplete go.mod file.
 func TestBadlyVersionedModule(t *testing.T) {
 	testenv.NeedsGo1Point(t, 14)
 
-	const badModule = `
--- example.com/blah/@v/list --
-v1.0.0
+	const proxy = `
 -- example.com/blah/@v/v1.0.0.mod --
 module example.com
 
@@ -335,17 +333,6 @@
 package blah
 
 const Name = "Blah"
--- example.com/blah@v1.0.0/blah_test.go --
-package blah_test
-
-import (
-	"testing"
-)
-
-func TestBlah(t *testing.T) {}
-
--- example.com/blah/v2/@v/list --
-v2.0.0
 -- example.com/blah/v2/@v/v2.0.0.mod --
 module example.com
 
@@ -353,35 +340,26 @@
 -- example.com/blah/v2@v2.0.0/blah.go --
 package blah
 
+import "example.com/blah"
+
+var _ = blah.Name
 const Name = "Blah"
--- example.com/blah/v2@v2.0.0/blah_test.go --
-package blah_test
-
-import (
-	"testing"
-
-	"example.com/blah"
-)
-
-func TestBlah(t *testing.T) {}
 `
-	const pkg = `
+	const files = `
 -- go.mod --
 module mod.com
 
-require (
-	example.com/blah/v2 v2.0.0
-)
+go 1.12
+
+require example.com/blah/v2 v2.0.0
 -- main.go --
 package main
 
 import "example.com/blah/v2"
 
-func main() {
-	println(blah.Name)
-}
+var _ = blah.Name
 `
-	runner.Run(t, pkg, func(t *testing.T, env *Env) {
+	withOptions(WithProxyFiles(proxy)).run(t, files, func(t *testing.T, env *Env) {
 		env.OpenFile("main.go")
 		env.OpenFile("go.mod")
 		var d protocol.PublishDiagnosticsParams
@@ -394,15 +372,18 @@
 		env.ApplyQuickFixes("main.go", d.Diagnostics)
 		const want = `module mod.com
 
+go 1.12
+
 require (
-	example.com/blah v1.0.0
+	example.com/blah v1.0.0 // indirect
 	example.com/blah/v2 v2.0.0
 )
 `
+		env.Await(EmptyDiagnostics("go.mod"))
 		if got := env.Editor.BufferText("go.mod"); got != want {
 			t.Fatalf("suggested fixes failed:\n%s", tests.Diff(want, got))
 		}
-	}, WithProxyFiles(badModule))
+	})
 }
 
 // Reproduces golang/go#38232.
@@ -479,44 +460,6 @@
 	})
 }
 
-func TestTidyOnSave(t *testing.T) {
-	testenv.NeedsGo1Point(t, 14)
-
-	const untidyModule = `
--- go.mod --
-module mod.com
-
-go 1.14
-
-require random.org v1.2.3
--- main.go --
-package main
-
-import "example.com/blah"
-
-func main() {
-	fmt.Println(blah.Name)
-}
-`
-	withOptions(WithProxyFiles(proxy)).run(t, untidyModule, func(t *testing.T, env *Env) {
-		env.OpenFile("go.mod")
-		env.Await(
-			env.DiagnosticAtRegexp("main.go", `"example.com/blah"`),
-			env.DiagnosticAtRegexp("go.mod", `require random.org v1.2.3`),
-		)
-		env.SaveBuffer("go.mod")
-		const want = `module mod.com
-
-go 1.14
-
-require example.com v1.2.3
-`
-		if got := env.ReadWorkspaceFile("go.mod"); got != want {
-			t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(want, got))
-		}
-	})
-}
-
 // Confirm that an error in an indirect dependency of a requirement is surfaced
 // as a diagnostic in the go.mod file.
 func TestErrorInIndirectDependency(t *testing.T) {
diff --git a/gopls/internal/regtest/runner.go b/gopls/internal/regtest/runner.go
index 9f8a779..8c5efe0 100644
--- a/gopls/internal/regtest/runner.go
+++ b/gopls/internal/regtest/runner.go
@@ -125,6 +125,12 @@
 	})
 }
 
+func SendPID() RunOption {
+	return optionSetter(func(opts *runConfig) {
+		opts.editor.SendPID = true
+	})
+}
+
 // EditorConfig is a RunOption option that configured the regtest editor.
 type EditorConfig fake.EditorConfig
 
diff --git a/gopls/internal/regtest/vendor_test.go b/gopls/internal/regtest/vendor_test.go
index 7f11d4a..7e754be 100644
--- a/gopls/internal/regtest/vendor_test.go
+++ b/gopls/internal/regtest/vendor_test.go
@@ -4,6 +4,8 @@
 	"testing"
 
 	"golang.org/x/tools/internal/lsp"
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/testenv"
 )
 
@@ -20,6 +22,7 @@
 
 func TestInconsistentVendoring(t *testing.T) {
 	testenv.NeedsGo1Point(t, 14)
+
 	const pkgThatUsesVendoring = `
 -- go.mod --
 module mod.com
@@ -52,15 +55,21 @@
 			// will be executed.
 			OnceMet(
 				CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1),
-				ShowMessageRequest("go mod vendor"),
+				env.DiagnosticAtRegexp("go.mod", "module mod.com"),
 			),
 		)
+		// Apply the quickfix associated with the diagnostic.
+		d := &protocol.PublishDiagnosticsParams{}
+		env.Await(ReadDiagnostics("go.mod", d))
+		env.ApplyQuickFixes("go.mod", d.Diagnostics)
+
+		// Check for file changes when the command completes.
+		env.Await(CompletedWork(source.CommandVendor.Title, 1))
 		env.CheckForFileChanges()
+
+		// Confirm that there is no longer any inconsistent vendoring.
 		env.Await(
-			OnceMet(
-				CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
-				DiagnosticAt("a/a1.go", 6, 5),
-			),
+			DiagnosticAt("a/a1.go", 6, 5),
 		)
 	})
 }
diff --git a/gopls/internal/regtest/workspace_test.go b/gopls/internal/regtest/workspace_test.go
index 4e9559d..1887ab4 100644
--- a/gopls/internal/regtest/workspace_test.go
+++ b/gopls/internal/regtest/workspace_test.go
@@ -6,6 +6,9 @@
 
 import (
 	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
 	"strings"
 	"testing"
 
@@ -158,6 +161,16 @@
 }
 
 const workspaceModuleProxy = `
+-- example.com@v1.2.3/go.mod --
+module example.com
+
+go 1.12
+-- example.com@v1.2.3/blah/blah.go --
+package blah
+
+func SaySomething() {
+	fmt.Println("something")
+}
 -- b.com@v1.2.3/go.mod --
 module b.com
 
@@ -364,6 +377,9 @@
 }
 
 func TestUseGoplsMod(t *testing.T) {
+	// This test validates certain functionality related to using a gopls.mod
+	// file to specify workspace modules.
+	testenv.NeedsGo1Point(t, 14)
 	const multiModule = `
 -- moda/a/go.mod --
 module a.com
@@ -384,6 +400,7 @@
 -- modb/go.mod --
 module b.com
 
+require example.com v1.2.3
 -- modb/b/b.go --
 package b
 
@@ -404,12 +421,18 @@
 		WithProxyFiles(workspaceModuleProxy),
 		WithModes(Experimental),
 	).run(t, multiModule, func(t *testing.T, env *Env) {
+		// Initially, the gopls.mod should cause only the a.com module to be
+		// loaded. Validate this by jumping to a definition in b.com and ensuring
+		// that we go to the module cache.
 		env.OpenFile("moda/a/a.go")
-		original, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
-		if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(original, want) {
-			t.Errorf("expected %s, got %v", want, original)
+		location, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
+		if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(location, want) {
+			t.Errorf("expected %s, got %v", want, location)
 		}
 		workdir := env.Sandbox.Workdir.RootURI().SpanURI().Filename()
+
+		// Now, modify the gopls.mod file on disk to activate the b.com module in
+		// the workspace.
 		env.WriteWorkspaceFile("gopls.mod", fmt.Sprintf(`module gopls-workspace
 
 require (
@@ -426,9 +449,41 @@
 				env.DiagnosticAtRegexp("modb/b/b.go", "x"),
 			),
 		)
-		newLocation, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
-		if want := "modb/b/b.go"; !strings.HasSuffix(newLocation, want) {
-			t.Errorf("expected %s, got %v", want, newLocation)
+		env.OpenFile("modb/go.mod")
+		// Check that go.mod diagnostics picked up the newly active mod file.
+		env.Await(env.DiagnosticAtRegexp("modb/go.mod", `require example.com v1.2.3`))
+		// ...and that jumping to definition now goes to b.com in the workspace.
+		location, _ = env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
+		if want := "modb/b/b.go"; !strings.HasSuffix(location, want) {
+			t.Errorf("expected %s, got %v", want, location)
+		}
+
+		// Now, let's modify the gopls.mod *overlay* (not on disk), and verify that
+		// this change is also picked up.
+		env.OpenFile("gopls.mod")
+		env.SetBufferContent("gopls.mod", fmt.Sprintf(`module gopls-workspace
+
+require (
+	a.com v0.0.0-goplsworkspace
+)
+
+replace a.com => %s/moda/a
+`, workdir))
+		env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChange), 1))
+		// TODO: diagnostics are not being cleared from the old go.mod location,
+		// because it's not treated as a 'deleted' file. Uncomment this after
+		// fixing.
+		/*
+			env.Await(OnceMet(
+				CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChange), 1),
+				EmptyDiagnostics("modb/go.mod"),
+			))
+		*/
+
+		// Just as before, check that we now jump to the module cache.
+		location, _ = env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
+		if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(location, want) {
+			t.Errorf("expected %s, got %v", want, location)
 		}
 	})
 }
@@ -511,3 +566,65 @@
 		)
 	})
 }
+
+func TestWorkspaceDirAccess(t *testing.T) {
+	const multiModule = `
+-- moda/a/go.mod --
+module a.com
+
+-- moda/a/a.go --
+package main
+
+func main() {
+	fmt.Println("Hello")
+}
+-- modb/go.mod --
+module b.com
+-- modb/b/b.go --
+package main
+
+func main() {
+	fmt.Println("World")
+}
+`
+	withOptions(
+		WithModes(Experimental),
+		SendPID(),
+	).run(t, multiModule, func(t *testing.T, env *Env) {
+		pid := os.Getpid()
+		// Don't factor this out of Server.addFolders. vscode-go expects this
+		// directory.
+		modPath := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.workspace", pid), "go.mod")
+		gotb, err := ioutil.ReadFile(modPath)
+		if err != nil {
+			t.Fatalf("reading expected workspace modfile: %v", err)
+		}
+		got := string(gotb)
+		for _, want := range []string{"a.com v0.0.0-goplsworkspace", "b.com v0.0.0-goplsworkspace"} {
+			if !strings.Contains(got, want) {
+				// want before got here, since the go.mod is multi-line
+				t.Fatalf("workspace go.mod missing %q. got:\n%s", want, got)
+			}
+		}
+		workdir := env.Sandbox.Workdir.RootURI().SpanURI().Filename()
+		env.WriteWorkspaceFile("gopls.mod", fmt.Sprintf(`
+				module gopls-workspace
+
+				require (
+					a.com v0.0.0-goplsworkspace
+				)
+
+				replace a.com => %s/moda/a
+				`, workdir))
+		env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1))
+		gotb, err = ioutil.ReadFile(modPath)
+		if err != nil {
+			t.Fatalf("reading expected workspace modfile: %v", err)
+		}
+		got = string(gotb)
+		want := "b.com v0.0.0-goplsworkspace"
+		if strings.Contains(got, want) {
+			t.Fatalf("workspace go.mod contains unexpected %q. got:\n%s", want, got)
+		}
+	})
+}
diff --git a/gopls/internal/regtest/wrappers.go b/gopls/internal/regtest/wrappers.go
index 53f7952..d7ca87f 100644
--- a/gopls/internal/regtest/wrappers.go
+++ b/gopls/internal/regtest/wrappers.go
@@ -92,6 +92,13 @@
 	}
 }
 
+func (e *Env) SetBufferContent(name string, content string) {
+	e.T.Helper()
+	if err := e.Editor.SetBufferContent(e.Ctx, name, content); err != nil {
+		e.T.Fatal(err)
+	}
+}
+
 // RegexpRange returns the range of the first match for re in the buffer
 // specified by name, calling t.Fatal on any error. It first searches for the
 // position in open buffers, then in workspace files.
diff --git a/internal/gocommand/invoke.go b/internal/gocommand/invoke.go
index b5c061b..8ba2253 100644
--- a/internal/gocommand/invoke.go
+++ b/internal/gocommand/invoke.go
@@ -132,6 +132,7 @@
 	BuildFlags []string
 	ModFlag    string
 	ModFile    string
+	Overlay    string
 	Env        []string
 	WorkingDir string
 	Logf       func(format string, args ...interface{})
@@ -171,6 +172,11 @@
 			goArgs = append(goArgs, "-mod="+i.ModFlag)
 		}
 	}
+	appendOverlayFlag := func() {
+		if i.Overlay != "" {
+			goArgs = append(goArgs, "-overlay="+i.Overlay)
+		}
+	}
 
 	switch i.Verb {
 	case "env", "version":
@@ -189,6 +195,7 @@
 		goArgs = append(goArgs, i.BuildFlags...)
 		appendModFile()
 		appendModFlag()
+		appendOverlayFlag()
 		goArgs = append(goArgs, i.Args...)
 	}
 	cmd := exec.Command("go", goArgs...)
diff --git a/internal/imports/mod.go b/internal/imports/mod.go
index 8a83613..73f7a49 100644
--- a/internal/imports/mod.go
+++ b/internal/imports/mod.go
@@ -347,10 +347,11 @@
 	}
 
 	if r.dirInModuleCache(dir) {
-		matches := modCacheRegexp.FindStringSubmatch(dir)
-		index := strings.Index(dir, matches[1]+"@"+matches[2])
-		modDir := filepath.Join(dir[:index], matches[1]+"@"+matches[2])
-		return modDir, readModName(filepath.Join(modDir, "go.mod"))
+		if matches := modCacheRegexp.FindStringSubmatch(dir); len(matches) == 3 {
+			index := strings.Index(dir, matches[1]+"@"+matches[2])
+			modDir := filepath.Join(dir[:index], matches[1]+"@"+matches[2])
+			return modDir, readModName(filepath.Join(modDir, "go.mod"))
+		}
 	}
 	for {
 		if info, ok := r.cacheLoad(dir); ok {
diff --git a/internal/imports/mod_test.go b/internal/imports/mod_test.go
index 5d73c88..c6bbe56 100644
--- a/internal/imports/mod_test.go
+++ b/internal/imports/mod_test.go
@@ -688,6 +688,7 @@
 	env := &ProcessEnv{
 		Env: map[string]string{
 			"GOPATH":      filepath.Join(dir, "gopath"),
+			"GOMODCACHE":  "",
 			"GO111MODULE": "on",
 			"GOSUMDB":     "off",
 			"GOPROXY":     proxydir.ToURL(proxyDir),
diff --git a/internal/lsp/cache/analysis.go b/internal/lsp/cache/analysis.go
index 3257324..872f1d2 100644
--- a/internal/lsp/cache/analysis.go
+++ b/internal/lsp/cache/analysis.go
@@ -143,7 +143,7 @@
 			}
 		}
 		return runAnalysis(ctx, snapshot, a, pkg, results)
-	})
+	}, nil)
 	act.handle = h
 
 	act = s.addActionHandle(act)
diff --git a/internal/lsp/cache/cache.go b/internal/lsp/cache/cache.go
index c2b4fe5..5e6180d 100644
--- a/internal/lsp/cache/cache.go
+++ b/internal/lsp/cache/cache.go
@@ -79,14 +79,10 @@
 		return fh, nil
 	}
 
-	select {
-	case ioLimit <- struct{}{}:
-	case <-ctx.Done():
-		return nil, ctx.Err()
+	fh, err := readFile(ctx, uri, fi.ModTime())
+	if err != nil {
+		return nil, err
 	}
-	defer func() { <-ioLimit }()
-
-	fh = readFile(ctx, uri, fi.ModTime())
 	c.fileMu.Lock()
 	c.fileContent[uri] = fh
 	c.fileMu.Unlock()
@@ -96,7 +92,14 @@
 // ioLimit limits the number of parallel file reads per process.
 var ioLimit = make(chan struct{}, 128)
 
-func readFile(ctx context.Context, uri span.URI, modTime time.Time) *fileHandle {
+func readFile(ctx context.Context, uri span.URI, modTime time.Time) (*fileHandle, error) {
+	select {
+	case ioLimit <- struct{}{}:
+	case <-ctx.Done():
+		return nil, ctx.Err()
+	}
+	defer func() { <-ioLimit }()
+
 	ctx, done := event.Start(ctx, "cache.readFile", tag.File.Of(uri.Filename()))
 	_ = ctx
 	defer done()
@@ -106,14 +109,14 @@
 		return &fileHandle{
 			modTime: modTime,
 			err:     err,
-		}
+		}, nil
 	}
 	return &fileHandle{
 		modTime: modTime,
 		uri:     uri,
 		bytes:   data,
 		hash:    hashContents(data),
-	}
+	}, nil
 }
 
 func (c *Cache) NewSession(ctx context.Context) *Session {
diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go
index c7bf6ec..187a391 100644
--- a/internal/lsp/cache/check.go
+++ b/internal/lsp/cache/check.go
@@ -99,7 +99,7 @@
 		wg.Wait()
 
 		return data
-	})
+	}, nil)
 	ph.handle = h
 
 	// Cache the handle in the snapshot. If a package handle has already
@@ -189,6 +189,19 @@
 	return packageHandleKey(hashContents(b.Bytes()))
 }
 
+// hashEnv returns a hash of the snapshot's configuration.
+func hashEnv(s *snapshot) string {
+	s.view.optionsMu.Lock()
+	env := s.view.options.EnvSlice()
+	s.view.optionsMu.Unlock()
+
+	b := &bytes.Buffer{}
+	for _, e := range env {
+		b.WriteString(e)
+	}
+	return hashContents(b.Bytes())
+}
+
 // hashConfig returns the hash for the *packages.Config.
 func hashConfig(config *packages.Config) string {
 	b := bytes.NewBuffer(nil)
diff --git a/internal/lsp/cache/imports.go b/internal/lsp/cache/imports.go
index 5039eac..48216ab 100644
--- a/internal/lsp/cache/imports.go
+++ b/internal/lsp/cache/imports.go
@@ -33,34 +33,31 @@
 	// Use temporary go.mod files, but always go to disk for the contents.
 	// Rebuilding the cache is expensive, and we don't want to do it for
 	// transient changes.
-	var modFH, sumFH source.FileHandle
+	var modFH source.FileHandle
+	var gosum []byte
 	var modFileIdentifier string
 	var err error
-	// TODO(heschik): Change the goimports logic to use a persistent workspace
+	// TODO(rfindley): Change the goimports logic to use a persistent workspace
 	// module for workspace module mode.
 	//
 	// Get the go.mod file that corresponds to this view's root URI. This is
 	// broken because it assumes that the view's root is a module, but this is
 	// not more broken than the previous state--it is a temporary hack that
 	// should be removed ASAP.
-	var match *moduleRoot
-	for _, m := range snapshot.modules {
-		if m.rootURI == snapshot.view.rootURI {
-			match = m
+	var matchURI span.URI
+	for modURI := range snapshot.workspace.activeModFiles() {
+		if dirURI(modURI) == snapshot.view.rootURI {
+			matchURI = modURI
 		}
 	}
-	if match != nil {
-		modFH, err = snapshot.GetFile(ctx, match.modURI)
+	// TODO(rFindley): should it be an error if matchURI is empty?
+	if matchURI != "" {
+		modFH, err = snapshot.GetFile(ctx, matchURI)
 		if err != nil {
 			return err
 		}
 		modFileIdentifier = modFH.FileIdentity().Hash
-		if match.sumURI != "" {
-			sumFH, err = snapshot.GetFile(ctx, match.sumURI)
-			if err != nil {
-				return err
-			}
-		}
+		gosum = snapshot.goSum(ctx, matchURI)
 	}
 	// v.goEnv is immutable -- changes make a new view. Options can change.
 	// We can't compare build flags directly because we may add -modfile.
@@ -87,7 +84,7 @@
 		}
 		s.cachedModFileIdentifier = modFileIdentifier
 		s.cachedBuildFlags = currentBuildFlags
-		s.cleanupProcessEnv, err = s.populateProcessEnv(ctx, snapshot, modFH, sumFH)
+		s.cleanupProcessEnv, err = s.populateProcessEnv(ctx, snapshot, modFH, gosum)
 		if err != nil {
 			return err
 		}
@@ -125,7 +122,7 @@
 
 // populateProcessEnv sets the dynamically configurable fields for the view's
 // process environment. Assumes that the caller is holding the s.view.importsMu.
-func (s *importsState) populateProcessEnv(ctx context.Context, snapshot *snapshot, modFH, sumFH source.FileHandle) (cleanup func(), err error) {
+func (s *importsState) populateProcessEnv(ctx context.Context, snapshot *snapshot, modFH source.FileHandle, gosum []byte) (cleanup func(), err error) {
 	cleanup = func() {}
 	pe := s.processEnv
 
@@ -166,7 +163,7 @@
 	// Add -modfile to the build flags, if we are using it.
 	if snapshot.workspaceMode()&tempModfile != 0 && modFH != nil {
 		var tmpURI span.URI
-		tmpURI, cleanup, err = tempModFile(modFH, sumFH)
+		tmpURI, cleanup, err = tempModFile(modFH, gosum)
 		if err != nil {
 			return nil, err
 		}
diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go
index 12a09b6..9eed1c4 100644
--- a/internal/lsp/cache/load.go
+++ b/internal/lsp/cache/load.go
@@ -13,11 +13,14 @@
 	"path/filepath"
 	"sort"
 	"strings"
+	"time"
 
 	"golang.org/x/tools/go/packages"
 	"golang.org/x/tools/internal/event"
+	"golang.org/x/tools/internal/gocommand"
 	"golang.org/x/tools/internal/lsp/debug/tag"
 	"golang.org/x/tools/internal/lsp/source"
+	"golang.org/x/tools/internal/memoize"
 	"golang.org/x/tools/internal/packagesinternal"
 	"golang.org/x/tools/internal/span"
 	errors "golang.org/x/xerrors"
@@ -90,71 +93,20 @@
 	ctx, done := event.Start(ctx, "cache.view.load", tag.Query.Of(query))
 	defer done()
 
-	cleanup := func() {}
-	wdir := s.view.rootURI.Filename()
-
-	var modFile string
-	var modURI span.URI
-	var modContent []byte
-	switch {
-	case s.workspaceMode()&usesWorkspaceModule != 0:
-		var (
-			tmpDir span.URI
-			err    error
-		)
-		tmpDir, cleanup, err = s.tempWorkspaceModule(ctx)
-		if err != nil {
-			return err
-		}
-		wdir = tmpDir.Filename()
-		modURI = span.URIFromPath(filepath.Join(wdir, "go.mod"))
-		modContent, err = ioutil.ReadFile(modURI.Filename())
-		if err != nil {
-			return err
-		}
-	case s.workspaceMode()&tempModfile != 0:
-		// -modfile is unsupported when there are > 1 modules in the workspace.
-		if len(s.modules) != 1 {
-			panic(fmt.Sprintf("unsupported use of -modfile, expected 1 module, got %v", len(s.modules)))
-		}
-		var mod *moduleRoot
-		for _, m := range s.modules { // range to access the only element
-			mod = m
-		}
-		modURI = mod.modURI
-		modFH, err := s.GetFile(ctx, mod.modURI)
-		if err != nil {
-			return err
-		}
-		modContent, err = modFH.Read()
-		if err != nil {
-			return err
-		}
-		var sumFH source.FileHandle
-		if mod.sumURI != "" {
-			sumFH, err = s.GetFile(ctx, mod.sumURI)
-			if err != nil {
-				return err
-			}
-		}
-		var tmpURI span.URI
-		tmpURI, cleanup, err = tempModFile(modFH, sumFH)
-		if err != nil {
-			return err
-		}
-		modFile = tmpURI.Filename()
-	}
-
-	cfg := s.config(ctx, wdir)
-	packagesinternal.SetModFile(cfg, modFile)
-	modMod, err := s.needsModEqualsMod(ctx, modURI, modContent)
+	_, inv, cleanup, err := s.goCommandInvocation(ctx, source.ForTypeChecking, &gocommand.Invocation{
+		WorkingDir: s.view.rootURI.Filename(),
+	})
 	if err != nil {
 		return err
 	}
-	if modMod {
-		packagesinternal.SetModFlag(cfg, "mod")
-	}
 
+	// Set a last resort deadline on packages.Load since it calls the go
+	// command, which may hang indefinitely if it has a bug. golang/go#42132
+	// and golang/go#42255 have more context.
+	ctx, cancel := context.WithTimeout(ctx, 15*time.Minute)
+	defer cancel()
+
+	cfg := s.config(ctx, inv)
 	pkgs, err := packages.Load(cfg, query...)
 	cleanup()
 
@@ -165,11 +117,6 @@
 		return ctx.Err()
 	}
 	if err != nil {
-		// Match on common error messages. This is really hacky, but I'm not sure
-		// of any better way. This can be removed when golang/go#39164 is resolved.
-		if strings.Contains(err.Error(), "inconsistent vendoring") {
-			return source.InconsistentVendoring
-		}
 		event.Error(ctx, "go/packages.Load", err, tag.Snapshot.Of(s.ID()), tag.Directory.Of(cfg.Dir), tag.Query.Of(query), tag.PackageCount.Of(len(pkgs)))
 	} else {
 		event.Log(ctx, "go/packages.Load", tag.Snapshot.Of(s.ID()), tag.Directory.Of(cfg.Dir), tag.Query.Of(query), tag.PackageCount.Of(len(pkgs)))
@@ -239,40 +186,62 @@
 	return srcErrs
 }
 
-// tempWorkspaceModule creates a temporary directory for use with
-// packages.Loads that occur from within the workspace module.
-func (s *snapshot) tempWorkspaceModule(ctx context.Context) (_ span.URI, cleanup func(), err error) {
-	cleanup = func() {}
-	if s.workspaceMode()&usesWorkspaceModule == 0 {
-		return "", cleanup, nil
+type workspaceDirKey string
+
+type workspaceDirData struct {
+	dir string
+	err error
+}
+
+// getWorkspaceDir gets the URI for the workspace directory associated with
+// this snapshot. The workspace directory is a temp directory containing the
+// go.mod file computed from all active modules.
+func (s *snapshot) getWorkspaceDir(ctx context.Context) (span.URI, error) {
+	s.mu.Lock()
+	h := s.workspaceDirHandle
+	s.mu.Unlock()
+	if h != nil {
+		return getWorkspaceDir(ctx, h, s.generation)
 	}
-	wsModuleHandle, err := s.getWorkspaceModuleHandle(ctx)
+	file, err := s.workspace.modFile(ctx, s)
 	if err != nil {
-		return "", nil, err
-	}
-	file, err := wsModuleHandle.build(ctx, s)
-	if err != nil {
-		return "", nil, err
+		return "", err
 	}
 	content, err := file.Format()
 	if err != nil {
-		return "", cleanup, err
+		return "", err
 	}
-	// Create a temporary working directory for the go command that contains
-	// the workspace module file.
-	name, err := ioutil.TempDir("", "gopls-mod")
+	key := workspaceDirKey(hashContents(content))
+	s.mu.Lock()
+	s.workspaceDirHandle = s.generation.Bind(key, func(context.Context, memoize.Arg) interface{} {
+		tmpdir, err := ioutil.TempDir("", "gopls-workspace-mod")
+		if err != nil {
+			return &workspaceDirData{err: err}
+		}
+		filename := filepath.Join(tmpdir, "go.mod")
+		if err := ioutil.WriteFile(filename, content, 0644); err != nil {
+			os.RemoveAll(tmpdir)
+			return &workspaceDirData{err: err}
+		}
+		return &workspaceDirData{dir: tmpdir}
+	}, func(v interface{}) {
+		d := v.(*workspaceDirData)
+		if d.dir != "" {
+			if err := os.RemoveAll(d.dir); err != nil {
+				event.Error(context.Background(), "cleaning workspace dir", err)
+			}
+		}
+	})
+	s.mu.Unlock()
+	return getWorkspaceDir(ctx, s.workspaceDirHandle, s.generation)
+}
+
+func getWorkspaceDir(ctx context.Context, h *memoize.Handle, g *memoize.Generation) (span.URI, error) {
+	v, err := h.Get(ctx, g, nil)
 	if err != nil {
-		return "", cleanup, err
+		return "", err
 	}
-	cleanup = func() {
-		os.RemoveAll(name)
-	}
-	filename := filepath.Join(name, "go.mod")
-	if err := ioutil.WriteFile(filename, content, 0644); err != nil {
-		cleanup()
-		return "", cleanup, err
-	}
-	return span.URIFromPath(filepath.Dir(filename)), cleanup, nil
+	return span.URIFromPath(v.(*workspaceDirData).dir), nil
 }
 
 // setMetadata extracts metadata from pkg and records it in s. It
diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go
index 0c2a96b..95a9132 100644
--- a/internal/lsp/cache/mod.go
+++ b/internal/lsp/cache/mod.go
@@ -19,11 +19,11 @@
 	"golang.org/x/mod/modfile"
 	"golang.org/x/mod/module"
 	"golang.org/x/tools/internal/event"
+	"golang.org/x/tools/internal/gocommand"
 	"golang.org/x/tools/internal/lsp/debug/tag"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/memoize"
-	"golang.org/x/tools/internal/packagesinternal"
 	"golang.org/x/tools/internal/span"
 	errors "golang.org/x/xerrors"
 )
@@ -88,7 +88,7 @@
 			}
 		}
 		return data
-	})
+	}, nil)
 
 	pmh := &parseModHandle{handle: h}
 	s.mu.Lock()
@@ -98,24 +98,26 @@
 	return pmh.parse(ctx, s)
 }
 
-func (s *snapshot) sumFH(ctx context.Context, modFH source.FileHandle) (source.FileHandle, error) {
+// goSum reads the go.sum file for the go.mod file at modURI, if it exists. If
+// it doesn't exist, it returns nil.
+func (s *snapshot) goSum(ctx context.Context, modURI span.URI) []byte {
 	// Get the go.sum file, either from the snapshot or directly from the
 	// cache. Avoid (*snapshot).GetFile here, as we don't want to add
 	// nonexistent file handles to the snapshot if the file does not exist.
-	sumURI := span.URIFromPath(sumFilename(modFH.URI()))
+	sumURI := span.URIFromPath(sumFilename(modURI))
 	var sumFH source.FileHandle = s.FindFile(sumURI)
 	if sumFH == nil {
 		var err error
 		sumFH, err = s.view.session.cache.getFile(ctx, sumURI)
 		if err != nil {
-			return nil, err
+			return nil
 		}
 	}
-	_, err := sumFH.Read()
+	content, err := sumFH.Read()
 	if err != nil {
-		return nil, err
+		return nil
 	}
-	return sumFH, nil
+	return content
 }
 
 func sumFilename(modURI span.URI) string {
@@ -165,7 +167,7 @@
 // modKey is uniquely identifies cached data for `go mod why` or dependencies
 // to upgrade.
 type modKey struct {
-	sessionID, cfg, view string
+	sessionID, env, view string
 	mod                  source.FileIdentity
 	verb                 modAction
 }
@@ -202,17 +204,12 @@
 	if fh.Kind() != source.Mod {
 		return nil, fmt.Errorf("%s is not a go.mod file", fh.URI())
 	}
-	if err := s.awaitLoaded(ctx); err != nil {
-		return nil, err
-	}
 	if handle := s.getModWhyHandle(fh.URI()); handle != nil {
 		return handle.why(ctx, s)
 	}
-	// Make sure to use the module root as the working directory.
-	cfg := s.config(ctx, filepath.Dir(fh.URI().Filename()))
 	key := modKey{
 		sessionID: s.view.session.id,
-		cfg:       hashConfig(cfg),
+		env:       hashEnv(s),
 		mod:       fh.FileIdentity(),
 		view:      s.view.rootURI.Filename(),
 		verb:      why,
@@ -232,11 +229,15 @@
 			return &modWhyData{}
 		}
 		// Run `go mod why` on all the dependencies.
-		args := []string{"why", "-m"}
-		for _, req := range pm.File.Require {
-			args = append(args, req.Mod.Path)
+		inv := &gocommand.Invocation{
+			Verb:       "mod",
+			Args:       []string{"why", "-m"},
+			WorkingDir: filepath.Dir(fh.URI().Filename()),
 		}
-		stdout, err := snapshot.runGoCommandWithConfig(ctx, cfg, "mod", args)
+		for _, req := range pm.File.Require {
+			inv.Args = append(inv.Args, req.Mod.Path)
+		}
+		stdout, err := snapshot.RunGoCommandDirect(ctx, source.Normal, inv)
 		if err != nil {
 			return &modWhyData{err: err}
 		}
@@ -251,7 +252,7 @@
 			why[req.Mod.Path] = whyList[i]
 		}
 		return &modWhyData{why: why}
-	})
+	}, nil)
 
 	mwh := &modWhyHandle{handle: h}
 	s.mu.Lock()
@@ -294,17 +295,12 @@
 	if fh.Kind() != source.Mod {
 		return nil, fmt.Errorf("%s is not a go.mod file", fh.URI())
 	}
-	if err := s.awaitLoaded(ctx); err != nil {
-		return nil, err
-	}
 	if handle := s.getModUpgradeHandle(fh.URI()); handle != nil {
 		return handle.upgrades(ctx, s)
 	}
-	// Use the module root as the working directory.
-	cfg := s.config(ctx, filepath.Dir(fh.URI().Filename()))
 	key := modKey{
 		sessionID: s.view.session.id,
-		cfg:       hashConfig(cfg),
+		env:       hashEnv(s),
 		mod:       fh.FileIdentity(),
 		view:      s.view.rootURI.Filename(),
 		verb:      upgrade,
@@ -326,13 +322,17 @@
 		}
 		// Run "go list -mod readonly -u -m all" to be able to see which deps can be
 		// upgraded without modifying mod file.
-		args := []string{"-u", "-m", "-json", "all"}
+		inv := &gocommand.Invocation{
+			Verb:       "list",
+			Args:       []string{"-u", "-m", "-json", "all"},
+			WorkingDir: filepath.Dir(fh.URI().Filename()),
+		}
 		if s.workspaceMode()&tempModfile == 0 || containsVendor(fh.URI()) {
 			// Use -mod=readonly if the module contains a vendor directory
 			// (see golang/go#38711).
-			packagesinternal.SetModFlag(cfg, "readonly")
+			inv.ModFlag = "readonly"
 		}
-		stdout, err := snapshot.runGoCommandWithConfig(ctx, cfg, "list", args)
+		stdout, err := snapshot.RunGoCommandDirect(ctx, source.Normal, inv)
 		if err != nil {
 			return &modUpgradeData{err: err}
 		}
@@ -360,7 +360,7 @@
 		return &modUpgradeData{
 			upgrades: upgrades,
 		}
-	})
+	}, nil)
 	muh := &modUpgradeHandle{handle: h}
 	s.mu.Lock()
 	s.modUpgradeHandles[fh.URI()] = muh
diff --git a/internal/lsp/cache/mod_tidy.go b/internal/lsp/cache/mod_tidy.go
index 67feb17..76479ee 100644
--- a/internal/lsp/cache/mod_tidy.go
+++ b/internal/lsp/cache/mod_tidy.go
@@ -17,6 +17,7 @@
 
 	"golang.org/x/mod/modfile"
 	"golang.org/x/tools/internal/event"
+	"golang.org/x/tools/internal/gocommand"
 	"golang.org/x/tools/internal/lsp/debug/tag"
 	"golang.org/x/tools/internal/lsp/diff"
 	"golang.org/x/tools/internal/lsp/protocol"
@@ -27,7 +28,7 @@
 
 type modTidyKey struct {
 	sessionID       string
-	cfg             string
+	env             string
 	gomod           source.FileIdentity
 	imports         string
 	unsavedOverlays string
@@ -56,9 +57,6 @@
 	if fh.Kind() != source.Mod {
 		return nil, fmt.Errorf("%s is not a go.mod file", fh.URI())
 	}
-	if s.workspaceMode()&tempModfile == 0 {
-		return nil, source.ErrTmpModfileUnsupported
-	}
 	if handle := s.getModTidyHandle(fh.URI()); handle != nil {
 		return handle.tidy(ctx, s)
 	}
@@ -71,6 +69,9 @@
 	}
 	workspacePkgs, err := s.WorkspacePackages(ctx)
 	if err != nil {
+		if tm, ok := s.parseModErrors(ctx, fh, err); ok {
+			return tm, nil
+		}
 		return nil, err
 	}
 	importHash, err := hashImports(ctx, workspacePkgs)
@@ -82,15 +83,13 @@
 	overlayHash := hashUnsavedOverlays(s.files)
 	s.mu.Unlock()
 
-	// Make sure to use the module root in the configuration.
-	cfg := s.config(ctx, filepath.Dir(fh.URI().Filename()))
 	key := modTidyKey{
 		sessionID:       s.view.session.id,
 		view:            s.view.folder.Filename(),
 		imports:         importHash,
 		unsavedOverlays: overlayHash,
 		gomod:           fh.FileIdentity(),
-		cfg:             hashConfig(cfg),
+		env:             hashEnv(s),
 	}
 	h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
 		ctx, done := event.Start(ctx, "cache.ModTidyHandle", tag.URI.Of(fh.URI()))
@@ -114,17 +113,19 @@
 				err: err,
 			}
 		}
-		// Get a new config to avoid races, since it may be modified by
-		// goCommandInvocation.
-		cfg := s.config(ctx, filepath.Dir(fh.URI().Filename()))
-		tmpURI, runner, inv, cleanup, err := snapshot.goCommandInvocation(ctx, cfg, true, "mod", []string{"tidy"})
+		inv := &gocommand.Invocation{
+			Verb:       "mod",
+			Args:       []string{"tidy"},
+			WorkingDir: filepath.Dir(fh.URI().Filename()),
+		}
+		tmpURI, inv, cleanup, err := snapshot.goCommandInvocation(ctx, source.WriteTemporaryModFile, inv)
 		if err != nil {
 			return &modTidyData{err: err}
 		}
 		// Keep the temporary go.mod file around long enough to parse it.
 		defer cleanup()
 
-		if _, err := runner.Run(ctx, *inv); err != nil {
+		if _, err := s.view.session.gocmdRunner.Run(ctx, *inv); err != nil {
 			return &modTidyData{err: err}
 		}
 		// Go directly to disk to get the temporary mod file, since it is
@@ -152,7 +153,7 @@
 				TidiedContent: tempContents,
 			},
 		}
-	})
+	}, nil)
 
 	mth := &modTidyHandle{handle: h}
 	s.mu.Lock()
@@ -162,6 +163,50 @@
 	return mth.tidy(ctx, s)
 }
 
+func (s *snapshot) parseModErrors(ctx context.Context, fh source.FileHandle, err error) (*source.TidiedModule, bool) {
+	if err == nil {
+		return nil, false
+	}
+	switch {
+	// Match on common error messages. This is really hacky, but I'm not sure
+	// of any better way. This can be removed when golang/go#39164 is resolved.
+	case strings.Contains(err.Error(), "inconsistent vendoring"):
+		pmf, err := s.ParseMod(ctx, fh)
+		if err != nil {
+			return nil, false
+		}
+		if pmf.File.Module == nil || pmf.File.Module.Syntax == nil {
+			return nil, false
+		}
+		rng, err := rangeFromPositions(pmf.Mapper, pmf.File.Module.Syntax.Start, pmf.File.Module.Syntax.End)
+		if err != nil {
+			return nil, false
+		}
+		args, err := source.MarshalArgs(protocol.URIFromSpanURI(fh.URI()))
+		if err != nil {
+			return nil, false
+		}
+		return &source.TidiedModule{
+			Parsed: pmf,
+			Errors: []source.Error{{
+				URI:   fh.URI(),
+				Range: rng,
+				Kind:  source.ListError,
+				Message: `Inconsistent vendoring detected. Please re-run "go mod vendor".
+See https://github.com/golang/go/issues/39164 for more detail on this issue.`,
+				SuggestedFixes: []source.SuggestedFix{{
+					Command: &protocol.Command{
+						Command:   source.CommandVendor.ID(),
+						Title:     source.CommandVendor.Title,
+						Arguments: args,
+					},
+				}},
+			}},
+		}, true
+	}
+	return nil, false
+}
+
 func hashImports(ctx context.Context, wsPackages []source.Package) (string, error) {
 	results := make(map[string]bool)
 	var imports []string
@@ -308,7 +353,7 @@
 	if err != nil {
 		return source.Error{}, err
 	}
-	edits, err := dropDependency(req, m, computeEdits)
+	args, err := source.MarshalArgs(m.URI, false, []string{req.Mod.Path + "@none"})
 	if err != nil {
 		return source.Error{}, err
 	}
@@ -319,8 +364,10 @@
 		URI:      m.URI,
 		SuggestedFixes: []source.SuggestedFix{{
 			Title: fmt.Sprintf("Remove dependency: %s", req.Mod.Path),
-			Edits: map[span.URI][]protocol.TextEdit{
-				m.URI: edits,
+			Command: &protocol.Command{
+				Title:     source.CommandRemoveDependency.Title,
+				Command:   source.CommandRemoveDependency.ID(),
+				Arguments: args,
 			},
 		}},
 	}, nil
@@ -368,53 +415,37 @@
 }
 
 func missingModuleError(snapshot source.Snapshot, pm *source.ParsedModule, req *modfile.Require) (source.Error, error) {
-	start, end := pm.File.Module.Syntax.Span()
-	rng, err := rangeFromPositions(pm.Mapper, start, end)
+	var rng protocol.Range
+	// Default to the start of the file if there is no module declaration.
+	if pm.File != nil && pm.File.Module != nil && pm.File.Module.Syntax != nil {
+		start, end := pm.File.Module.Syntax.Span()
+		var err error
+		rng, err = rangeFromPositions(pm.Mapper, start, end)
+		if err != nil {
+			return source.Error{}, err
+		}
+	}
+	args, err := source.MarshalArgs(pm.Mapper.URI, !req.Indirect, []string{req.Mod.Path + "@" + req.Mod.Version})
 	if err != nil {
 		return source.Error{}, err
 	}
-	edits, err := addRequireFix(pm.Mapper, req, snapshot.View().Options().ComputeEdits)
-	if err != nil {
-		return source.Error{}, err
-	}
-	fix := &source.SuggestedFix{
-		Title: fmt.Sprintf("Add %s to your go.mod file", req.Mod.Path),
-		Edits: map[span.URI][]protocol.TextEdit{
-			pm.Mapper.URI: edits,
-		},
-	}
 	return source.Error{
-		URI:            pm.Mapper.URI,
-		Range:          rng,
-		Message:        fmt.Sprintf("%s is not in your go.mod file", req.Mod.Path),
-		Category:       source.GoModTidy,
-		Kind:           source.ModTidyError,
-		SuggestedFixes: []source.SuggestedFix{*fix},
+		URI:      pm.Mapper.URI,
+		Range:    rng,
+		Message:  fmt.Sprintf("%s is not in your go.mod file", req.Mod.Path),
+		Category: source.GoModTidy,
+		Kind:     source.ModTidyError,
+		SuggestedFixes: []source.SuggestedFix{{
+			Title: fmt.Sprintf("Add %s to your go.mod file", req.Mod.Path),
+			Command: &protocol.Command{
+				Title:     source.CommandAddDependency.Title,
+				Command:   source.CommandAddDependency.ID(),
+				Arguments: args,
+			},
+		}},
 	}, nil
 }
 
-// dropDependency returns the edits to remove the given require from the go.mod
-// file.
-func dropDependency(req *modfile.Require, m *protocol.ColumnMapper, computeEdits diff.ComputeEdits) ([]protocol.TextEdit, error) {
-	// We need a private copy of the parsed go.mod file, since we're going to
-	// modify it.
-	copied, err := modfile.Parse("", m.Content, nil)
-	if err != nil {
-		return nil, err
-	}
-	if err := copied.DropRequire(req.Mod.Path); err != nil {
-		return nil, err
-	}
-	copied.Cleanup()
-	newContent, err := copied.Format()
-	if err != nil {
-		return nil, err
-	}
-	// Calculate the edits to be made due to the change.
-	diff := computeEdits(m.URI, string(m.Content), string(newContent))
-	return source.ToProtocolEdits(m, diff)
-}
-
 // switchDirectness gets the edits needed to change an indirect dependency to
 // direct and vice versa.
 func switchDirectness(req *modfile.Require, m *protocol.ColumnMapper, computeEdits diff.ComputeEdits) ([]protocol.TextEdit, error) {
@@ -472,28 +503,6 @@
 	}, nil
 }
 
-// addRequireFix creates edits for adding a given require to a go.mod file.
-func addRequireFix(m *protocol.ColumnMapper, req *modfile.Require, computeEdits diff.ComputeEdits) ([]protocol.TextEdit, error) {
-	// We need a private copy of the parsed go.mod file, since we're going to
-	// modify it.
-	copied, err := modfile.Parse("", m.Content, nil)
-	if err != nil {
-		return nil, err
-	}
-	// Calculate the quick fix edits that need to be made to the go.mod file.
-	if err := copied.AddRequire(req.Mod.Path, req.Mod.Version); err != nil {
-		return nil, err
-	}
-	copied.SortBlocks()
-	newContents, err := copied.Format()
-	if err != nil {
-		return nil, err
-	}
-	// Calculate the edits to be made due to the change.
-	diff := computeEdits(m.URI, string(m.Content), string(newContents))
-	return source.ToProtocolEdits(m, diff)
-}
-
 func rangeFromPositions(m *protocol.ColumnMapper, s, e modfile.Position) (protocol.Range, error) {
 	toPoint := func(offset int) (span.Point, error) {
 		l, c, err := m.Converter.ToPosition(offset)
diff --git a/internal/lsp/cache/parse.go b/internal/lsp/cache/parse.go
index 95e9de5..0a2c371 100644
--- a/internal/lsp/cache/parse.go
+++ b/internal/lsp/cache/parse.go
@@ -61,12 +61,12 @@
 	parseHandle := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
 		snapshot := arg.(*snapshot)
 		return parseGo(ctx, snapshot.view.session.cache.fset, fh, mode)
-	})
+	}, nil)
 
 	astHandle := s.generation.Bind(astCacheKey(key), func(ctx context.Context, arg memoize.Arg) interface{} {
 		snapshot := arg.(*snapshot)
 		return buildASTCache(ctx, snapshot, parseHandle)
-	})
+	}, nil)
 
 	pgh := &parseGoHandle{
 		handle:         parseHandle,
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index 733c937..36bfc50 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -8,7 +8,6 @@
 	"context"
 	"fmt"
 	"os"
-	"path/filepath"
 	"strconv"
 	"strings"
 	"sync"
@@ -145,10 +144,10 @@
 	return s.cache
 }
 
-func (s *Session) NewView(ctx context.Context, name string, folder span.URI, options *source.Options) (source.View, source.Snapshot, func(), error) {
+func (s *Session) NewView(ctx context.Context, name string, folder, tempWorkspace span.URI, options *source.Options) (source.View, source.Snapshot, func(), error) {
 	s.viewMu.Lock()
 	defer s.viewMu.Unlock()
-	view, snapshot, release, err := s.createView(ctx, name, folder, options, 0)
+	view, snapshot, release, err := s.createView(ctx, name, folder, tempWorkspace, options, 0)
 	if err != nil {
 		return nil, nil, func() {}, err
 	}
@@ -158,7 +157,7 @@
 	return view, snapshot, release, nil
 }
 
-func (s *Session) createView(ctx context.Context, name string, folder span.URI, options *source.Options, snapshotID uint64) (*View, *snapshot, func(), error) {
+func (s *Session) createView(ctx context.Context, name string, folder, tempWorkspace span.URI, options *source.Options, snapshotID uint64) (*View, *snapshot, func(), error) {
 	index := atomic.AddInt64(&viewIndex, 1)
 
 	if s.cache.options != nil {
@@ -171,10 +170,8 @@
 		return nil, nil, func() {}, err
 	}
 
-	// If workspace module mode is enabled, find all of the modules in the
-	// workspace. By default, we just find the root module.
-	var modules map[span.URI]*moduleRoot
-	modules, err = findWorkspaceModules(ctx, ws.rootURI, options)
+	// Build the gopls workspace, collecting active modules in the view.
+	workspace, err := newWorkspace(ctx, ws.rootURI, s, options.ExperimentalWorkspaceModule)
 	if err != nil {
 		return nil, nil, func() {}, err
 	}
@@ -186,9 +183,8 @@
 
 	v := &View{
 		session:              s,
-		initialized:          make(chan struct{}),
+		initialWorkspaceLoad: make(chan struct{}),
 		initializationSema:   make(chan struct{}, 1),
-		initializeOnce:       &sync.Once{},
 		id:                   strconv.FormatInt(index, 10),
 		options:              options,
 		baseCtx:              baseCtx,
@@ -199,6 +195,7 @@
 		filesByURI:           make(map[span.URI]*fileBase),
 		filesByBase:          make(map[string][]*fileBase),
 		workspaceInformation: *ws,
+		tempWorkspace:        tempWorkspace,
 	}
 	v.importsState = &importsState{
 		ctx: backgroundCtx,
@@ -211,6 +208,7 @@
 	v.snapshot = &snapshot{
 		id:                snapshotID,
 		view:              v,
+		initializeOnce:    &sync.Once{},
 		generation:        s.cache.store.Generation(generationName(v, 0)),
 		packages:          make(map[packageKey]*packageHandle),
 		ids:               make(map[span.URI][]packageID),
@@ -225,11 +223,9 @@
 		modTidyHandles:    make(map[span.URI]*modTidyHandle),
 		modUpgradeHandles: make(map[span.URI]*modUpgradeHandle),
 		modWhyHandles:     make(map[span.URI]*modWhyHandle),
-		modules:           modules,
+		workspace:         workspace,
 	}
 
-	v.snapshot.workspaceDirectories = v.snapshot.findWorkspaceDirectories(ctx)
-
 	// Initialize the view without blocking.
 	initCtx, initCancel := context.WithCancel(xcontext.Detach(ctx))
 	v.initCancelFirstAttempt = initCancel
@@ -237,57 +233,24 @@
 	release := snapshot.generation.Acquire(initCtx)
 	go func() {
 		snapshot.initialize(initCtx, true)
+		if v.tempWorkspace != "" {
+			var err error
+			if err = os.Mkdir(v.tempWorkspace.Filename(), 0700); err == nil {
+				var wsdir span.URI
+				wsdir, err = snapshot.getWorkspaceDir(initCtx)
+				if err == nil {
+					err = copyWorkspace(v.tempWorkspace, wsdir)
+				}
+			}
+			if err != nil {
+				event.Error(initCtx, "creating workspace dir", err)
+			}
+		}
 		release()
 	}()
 	return v, snapshot, snapshot.generation.Acquire(ctx), nil
 }
 
-// findWorkspaceModules walks the view's root folder, looking for go.mod files.
-// Any that are found are added to the view's set of modules, which are then
-// used to construct the workspace module.
-//
-// It assumes that the caller has not yet created the view, and therefore does
-// not lock any of the internal data structures before accessing them.
-//
-// TODO(rstambler): Check overlays for go.mod files.
-func findWorkspaceModules(ctx context.Context, root span.URI, options *source.Options) (map[span.URI]*moduleRoot, error) {
-	// Walk the view's folder to find all modules in the view.
-	modules := make(map[span.URI]*moduleRoot)
-	if !options.ExperimentalWorkspaceModule {
-		path := filepath.Join(root.Filename(), "go.mod")
-		if info, _ := os.Stat(path); info != nil {
-			if m := getViewModule(ctx, root, span.URIFromPath(path), options); m != nil {
-				modules[m.rootURI] = m
-			}
-		}
-		return modules, nil
-	}
-	return modules, filepath.Walk(root.Filename(), func(path string, info os.FileInfo, err error) error {
-		if err != nil {
-			// Probably a permission error. Keep looking.
-			return filepath.SkipDir
-		}
-		// For any path that is not the workspace folder, check if the path
-		// would be ignored by the go command. Vendor directories also do not
-		// contain workspace modules.
-		if info.IsDir() && path != root.Filename() {
-			suffix := strings.TrimPrefix(path, root.Filename())
-			switch {
-			case checkIgnored(suffix),
-				strings.Contains(filepath.ToSlash(suffix), "/vendor/"):
-				return filepath.SkipDir
-			}
-		}
-		// We're only interested in go.mod files.
-		if filepath.Base(path) == "go.mod" {
-			if m := getViewModule(ctx, root, span.URIFromPath(path), options); m != nil {
-				modules[m.rootURI] = m
-			}
-		}
-		return nil
-	})
-}
-
 // View returns the view by name.
 func (s *Session) View(name string) source.View {
 	s.viewMu.Lock()
@@ -401,7 +364,7 @@
 	view.snapshotMu.Lock()
 	snapshotID := view.snapshot.id
 	view.snapshotMu.Unlock()
-	v, _, release, err := s.createView(ctx, view.name, view.folder, options, snapshotID)
+	v, _, release, err := s.createView(ctx, view.name, view.folder, view.tempWorkspace, options, snapshotID)
 	release()
 	if err != nil {
 		// we have dropped the old view, but could not create the new one
@@ -439,8 +402,14 @@
 	return err
 }
 
+type fileChange struct {
+	content    []byte
+	exists     bool
+	fileHandle source.VersionedFileHandle
+}
+
 func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModification) (map[span.URI]source.View, map[source.View]source.Snapshot, []func(), []span.URI, error) {
-	views := make(map[*View]map[span.URI]source.VersionedFileHandle)
+	views := make(map[*View]map[span.URI]*fileChange)
 	bestViews := map[span.URI]source.View{}
 	// 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
@@ -485,23 +454,28 @@
 				return nil, nil, nil, nil, err
 			}
 			if _, ok := views[view]; !ok {
-				views[view] = make(map[span.URI]source.VersionedFileHandle)
+				views[view] = make(map[span.URI]*fileChange)
 			}
-			var (
-				fh source.VersionedFileHandle
-				ok bool
-			)
-			if fh, ok = overlays[c.URI]; ok {
-				views[view][c.URI] = fh
+			if fh, ok := overlays[c.URI]; ok {
+				views[view][c.URI] = &fileChange{
+					content:    fh.text,
+					exists:     true,
+					fileHandle: fh,
+				}
 				delete(deletions, c.URI)
 			} else {
 				fsFile, err := s.cache.getFile(ctx, c.URI)
 				if err != nil {
 					return nil, nil, nil, nil, err
 				}
-				fh = &closedFile{fsFile}
-				views[view][c.URI] = fh
-				if _, err := fh.Read(); err != nil {
+				content, err := fsFile.Read()
+				fh := &closedFile{fsFile}
+				views[view][c.URI] = &fileChange{
+					content:    content,
+					exists:     err == nil,
+					fileHandle: fh,
+				}
+				if err != nil {
 					deletions[c.URI] = struct{}{}
 				}
 			}
@@ -510,8 +484,8 @@
 
 	snapshots := map[source.View]source.Snapshot{}
 	var releases []func()
-	for view, uris := range views {
-		snapshot, release := view.invalidateContent(ctx, uris, forceReloadMetadata)
+	for view, changed := range views {
+		snapshot, release := view.invalidateContent(ctx, changed, forceReloadMetadata)
 		snapshots[view] = snapshot
 		releases = append(releases, release)
 	}
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index 021c534..045415f 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -46,6 +46,17 @@
 	// builtin pins the AST and package for builtin.go in memory.
 	builtin *builtinPackageHandle
 
+	// The snapshot's initialization state is controlled by the fields below.
+	//
+	// initializeOnce guards snapshot initialization. Each snapshot is
+	// initialized at most once: reinitialization is triggered on later snapshots
+	// by invalidating this field.
+	initializeOnce *sync.Once
+	// initializedErr holds the last error resulting from initialization. If
+	// initialization fails, we only retry when the the workspace modules change,
+	// to avoid too many go/packages calls.
+	initializedErr error
+
 	// mu guards all of the maps in the snapshot.
 	mu sync.Mutex
 
@@ -78,10 +89,6 @@
 	// when the view is created.
 	workspacePackages map[packageID]packagePath
 
-	// workspaceDirectories are the directories containing workspace packages.
-	// They are the view's root, as well as any replace targets.
-	workspaceDirectories map[span.URI]struct{}
-
 	// unloadableFiles keeps track of files that we've failed to load.
 	unloadableFiles map[span.URI]struct{}
 
@@ -96,12 +103,8 @@
 	modUpgradeHandles map[span.URI]*modUpgradeHandle
 	modWhyHandles     map[span.URI]*modWhyHandle
 
-	// modules is the set of modules currently in this workspace.
-	modules map[span.URI]*moduleRoot
-
-	// workspaceModuleHandle keeps track of the in-memory representation of the
-	// go.mod file for the workspace module.
-	workspaceModuleHandle *workspaceModuleHandle
+	workspace          *workspace
+	workspaceDirHandle *memoize.Handle
 }
 
 type packageKey struct {
@@ -128,14 +131,14 @@
 
 func (s *snapshot) ModFiles() []span.URI {
 	var uris []span.URI
-	for _, m := range s.modules {
-		uris = append(uris, m.modURI)
+	for modURI := range s.workspace.activeModFiles() {
+		uris = append(uris, modURI)
 	}
 	return uris
 }
 
 func (s *snapshot) ValidBuildConfiguration() bool {
-	return validBuildConfiguration(s.view.rootURI, &s.view.workspaceInformation, s.modules)
+	return validBuildConfiguration(s.view.rootURI, &s.view.workspaceInformation, s.workspace.activeModFiles())
 }
 
 // workspaceMode describes the way in which the snapshot's workspace should
@@ -152,7 +155,7 @@
 	// If the view is not in a module and contains no modules, but still has a
 	// valid workspace configuration, do not create the workspace module.
 	// It could be using GOPATH or a different build system entirely.
-	if len(s.modules) == 0 && validBuildConfiguration {
+	if len(s.workspace.activeModFiles()) == 0 && validBuildConfiguration {
 		return mode
 	}
 	mode |= moduleMode
@@ -182,17 +185,16 @@
 // TODO(rstambler): go/packages requires that we do not provide overlays for
 // multiple modules in on config, so buildOverlay needs to filter overlays by
 // module.
-func (s *snapshot) config(ctx context.Context, dir string) *packages.Config {
+func (s *snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config {
 	s.view.optionsMu.Lock()
-	env, buildFlags := s.view.envLocked()
 	verboseOutput := s.view.options.VerboseOutput
 	s.view.optionsMu.Unlock()
 
 	cfg := &packages.Config{
 		Context:    ctx,
-		Dir:        dir,
-		Env:        append(append([]string{}, env...), "GO111MODULE="+s.view.go111module),
-		BuildFlags: append([]string{}, buildFlags...),
+		Dir:        inv.WorkingDir,
+		Env:        inv.Env,
+		BuildFlags: inv.BuildFlags,
 		Mode: packages.NeedName |
 			packages.NeedFiles |
 			packages.NeedCompiledGoFiles |
@@ -212,6 +214,8 @@
 		},
 		Tests: true,
 	}
+	packagesinternal.SetModFile(cfg, inv.ModFile)
+	packagesinternal.SetModFlag(cfg, inv.ModFlag)
 	// We want to type check cgo code if go/types supports it.
 	if typesinternal.SetUsesCgo(&types.Config{}) {
 		cfg.Mode |= packages.LoadMode(packagesinternal.TypecheckCgo)
@@ -220,63 +224,78 @@
 	return cfg
 }
 
-func (s *snapshot) RunGoCommandDirect(ctx context.Context, wd, verb string, args []string) error {
-	cfg := s.config(ctx, wd)
-	_, runner, inv, cleanup, err := s.goCommandInvocation(ctx, cfg, false, verb, args)
-	if err != nil {
-		return err
-	}
-	defer cleanup()
-
-	_, err = runner.Run(ctx, *inv)
-	return err
-}
-
-func (s *snapshot) runGoCommandWithConfig(ctx context.Context, cfg *packages.Config, verb string, args []string) (*bytes.Buffer, error) {
-	_, runner, inv, cleanup, err := s.goCommandInvocation(ctx, cfg, true, verb, args)
+func (s *snapshot) RunGoCommandDirect(ctx context.Context, mode source.InvocationMode, inv *gocommand.Invocation) (*bytes.Buffer, error) {
+	_, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
 	if err != nil {
 		return nil, err
 	}
 	defer cleanup()
 
-	return runner.Run(ctx, *inv)
+	return s.view.session.gocmdRunner.Run(ctx, *inv)
 }
 
-func (s *snapshot) RunGoCommandPiped(ctx context.Context, wd, verb string, args []string, stdout, stderr io.Writer) error {
-	cfg := s.config(ctx, wd)
-	_, runner, inv, cleanup, err := s.goCommandInvocation(ctx, cfg, true, verb, args)
+func (s *snapshot) RunGoCommandPiped(ctx context.Context, mode source.InvocationMode, inv *gocommand.Invocation, stdout, stderr io.Writer) error {
+	_, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
 	if err != nil {
 		return err
 	}
 	defer cleanup()
-	return runner.RunPiped(ctx, *inv, stdout, stderr)
+	return s.view.session.gocmdRunner.RunPiped(ctx, *inv, stdout, stderr)
 }
 
-func (s *snapshot) goCommandInvocation(ctx context.Context, cfg *packages.Config, allowTempModfile bool, verb string, args []string) (tmpURI span.URI, runner *gocommand.Runner, inv *gocommand.Invocation, cleanup func(), err error) {
+func (s *snapshot) goCommandInvocation(ctx context.Context, mode source.InvocationMode, inv *gocommand.Invocation) (tmpURI span.URI, updatedInv *gocommand.Invocation, cleanup func(), err error) {
+	s.view.optionsMu.Lock()
+	inv.Env = append(append(append(os.Environ(), s.view.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.go111module)
+	inv.BuildFlags = append([]string{}, s.view.options.BuildFlags...)
+	s.view.optionsMu.Unlock()
 	cleanup = func() {} // fallback
-	modURI := s.GoModForFile(ctx, span.URIFromPath(cfg.Dir))
 
-	inv = &gocommand.Invocation{
-		Verb:       verb,
-		Args:       args,
-		Env:        cfg.Env,
-		WorkingDir: cfg.Dir,
+	var modURI span.URI
+	if s.workspaceMode()&moduleMode != 0 {
+		// Select the module context to use.
+		// If we're type checking, we need to use the workspace context, meaning
+		// the main (workspace) module. Otherwise, we should use the module for
+		// the passed-in working dir.
+		if mode == source.ForTypeChecking {
+			if s.workspaceMode()&usesWorkspaceModule == 0 {
+				for m := range s.workspace.activeModFiles() { // range to access the only element
+					modURI = m
+				}
+			} else {
+				var tmpDir span.URI
+				var err error
+				tmpDir, err = s.getWorkspaceDir(ctx)
+				if err != nil {
+					return "", nil, cleanup, err
+				}
+				inv.WorkingDir = tmpDir.Filename()
+				modURI = span.URIFromPath(filepath.Join(tmpDir.Filename(), "go.mod"))
+			}
+		} else {
+			modURI = s.GoModForFile(ctx, span.URIFromPath(inv.WorkingDir))
+		}
 	}
 
-	if allowTempModfile && s.workspaceMode()&tempModfile != 0 {
+	wantTempMod := mode != source.UpdateUserModFile
+	needTempMod := mode == source.WriteTemporaryModFile
+	tempMod := wantTempMod && s.workspaceMode()&tempModfile != 0
+	if needTempMod && !tempMod {
+		return "", nil, cleanup, source.ErrTmpModfileUnsupported
+	}
+
+	if tempMod {
 		if modURI == "" {
-			return "", nil, nil, cleanup, fmt.Errorf("no go.mod file found in %s", cfg.Dir)
+			return "", nil, cleanup, fmt.Errorf("no go.mod file found in %s", inv.WorkingDir)
 		}
 		modFH, err := s.GetFile(ctx, modURI)
 		if err != nil {
-			return "", nil, nil, cleanup, err
+			return "", nil, cleanup, err
 		}
 		// Use the go.sum if it happens to be available.
-		sumFH, _ := s.sumFH(ctx, modFH)
-
-		tmpURI, cleanup, err = tempModFile(modFH, sumFH)
+		gosum := s.goSum(ctx, modURI)
+		tmpURI, cleanup, err = tempModFile(modFH, gosum)
 		if err != nil {
-			return "", nil, nil, cleanup, err
+			return "", nil, cleanup, err
 		}
 		inv.ModFile = tmpURI.Filename()
 	}
@@ -285,23 +304,22 @@
 	if modURI != "" {
 		modFH, err := s.GetFile(ctx, modURI)
 		if err != nil {
-			return "", nil, nil, cleanup, err
+			return "", nil, cleanup, err
 		}
 		modContent, err = modFH.Read()
 		if err != nil {
-			return "", nil, nil, nil, err
+			return "", nil, nil, err
 		}
 	}
 	modMod, err := s.needsModEqualsMod(ctx, modURI, modContent)
 	if err != nil {
-		return "", nil, nil, cleanup, err
+		return "", nil, cleanup, err
 	}
 	if modMod {
 		inv.ModFlag = "mod"
 	}
 
-	runner = packagesinternal.GetGoCmdRunner(cfg)
-	return tmpURI, runner, inv, cleanup, nil
+	return tmpURI, inv, cleanup, nil
 }
 
 func (s *snapshot) buildOverlay() map[string][]byte {
@@ -583,14 +601,7 @@
 }
 
 func (s *snapshot) WorkspaceDirectories(ctx context.Context) []span.URI {
-	s.mu.Lock()
-	defer s.mu.Unlock()
-
-	var dirs []span.URI
-	for d := range s.workspaceDirectories {
-		dirs = append(dirs, d)
-	}
-	return dirs
+	return s.workspace.dirs(ctx, s)
 }
 
 func (s *snapshot) WorkspacePackages(ctx context.Context) ([]source.Package, error) {
@@ -666,12 +677,12 @@
 
 func (s *snapshot) GoModForFile(ctx context.Context, uri span.URI) span.URI {
 	var match span.URI
-	for _, m := range s.modules {
-		if !isSubdirectory(m.rootURI.Filename(), uri.Filename()) {
+	for modURI := range s.workspace.activeModFiles() {
+		if !source.InDir(dirURI(modURI).Filename(), uri.Filename()) {
 			continue
 		}
-		if len(m.modURI) > len(match) {
-			match = m.modURI
+		if len(modURI) > len(match) {
+			match = modURI
 		}
 	}
 	return match
@@ -792,7 +803,7 @@
 // GetVersionedFile returns a File for the given URI. If the file is unknown it
 // is added to the managed set.
 //
-// GetFile succeeds even if the file does not exist. A non-nil error return
+// GetVersionedFile succeeds even if the file does not exist. A non-nil error return
 // indicates some type of internal error, for example if ctx is cancelled.
 func (s *snapshot) GetVersionedFile(ctx context.Context, uri span.URI) (source.VersionedFileHandle, error) {
 	f, err := s.view.getFile(uri)
@@ -848,7 +859,7 @@
 	s.mu.Lock()
 	defer s.mu.Unlock()
 	if len(s.metadata) == 0 {
-		return s.view.initializedErr
+		return s.initializedErr
 	}
 	return nil
 }
@@ -857,7 +868,7 @@
 	select {
 	case <-ctx.Done():
 		return
-	case <-s.view.initialized:
+	case <-s.view.initialWorkspaceLoad:
 	}
 	// We typically prefer to run something as intensive as the IWL without
 	// blocking. I'm not sure if there is a way to do that here.
@@ -946,6 +957,11 @@
 		if !contains(s.view.session.viewsOf(uri), s.view) {
 			continue
 		}
+		// If the file is not open and is in a vendor directory, don't treat it
+		// like a workspace package.
+		if _, ok := fh.(*overlay); !ok && inVendor(uri) {
+			continue
+		}
 		// Don't reload metadata for files we've already deemed unloadable.
 		if _, ok := s.unloadableFiles[uri]; ok {
 			continue
@@ -970,36 +986,62 @@
 	return false
 }
 
+func inVendor(uri span.URI) bool {
+	toSlash := filepath.ToSlash(uri.Filename())
+	if !strings.Contains(toSlash, "/vendor/") {
+		return false
+	}
+	// Only packages in _subdirectories_ of /vendor/ are considered vendored
+	// (/vendor/a/foo.go is vendored, /vendor/foo.go is not).
+	split := strings.Split(toSlash, "/vendor/")
+	if len(split) < 2 {
+		return false
+	}
+	return strings.Contains(split[1], "/")
+}
+
 func generationName(v *View, snapshotID uint64) string {
 	return fmt.Sprintf("v%v/%v", v.id, snapshotID)
 }
 
-func (s *snapshot) clone(ctx context.Context, withoutURIs map[span.URI]source.VersionedFileHandle, forceReloadMetadata bool) (*snapshot, reinitializeView) {
+func (s *snapshot) clone(ctx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) (*snapshot, bool) {
+	// Track some important types of changes.
+	var (
+		vendorChanged  bool
+		modulesChanged bool
+	)
+	newWorkspace, workspaceChanged := s.workspace.invalidate(ctx, changes)
+
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
 	newGen := s.view.session.cache.store.Generation(generationName(s.view, s.id+1))
 	result := &snapshot{
-		id:                    s.id + 1,
-		generation:            newGen,
-		view:                  s.view,
-		builtin:               s.builtin,
-		ids:                   make(map[span.URI][]packageID),
-		importedBy:            make(map[packageID][]packageID),
-		metadata:              make(map[packageID]*metadata),
-		packages:              make(map[packageKey]*packageHandle),
-		actions:               make(map[actionKey]*actionHandle),
-		files:                 make(map[span.URI]source.VersionedFileHandle),
-		goFiles:               make(map[parseKey]*parseGoHandle),
-		workspaceDirectories:  make(map[span.URI]struct{}),
-		workspacePackages:     make(map[packageID]packagePath),
-		unloadableFiles:       make(map[span.URI]struct{}),
-		parseModHandles:       make(map[span.URI]*parseModHandle),
-		modTidyHandles:        make(map[span.URI]*modTidyHandle),
-		modUpgradeHandles:     make(map[span.URI]*modUpgradeHandle),
-		modWhyHandles:         make(map[span.URI]*modWhyHandle),
-		modules:               make(map[span.URI]*moduleRoot),
-		workspaceModuleHandle: s.workspaceModuleHandle,
+		id:                s.id + 1,
+		generation:        newGen,
+		view:              s.view,
+		builtin:           s.builtin,
+		initializeOnce:    s.initializeOnce,
+		initializedErr:    s.initializedErr,
+		ids:               make(map[span.URI][]packageID),
+		importedBy:        make(map[packageID][]packageID),
+		metadata:          make(map[packageID]*metadata),
+		packages:          make(map[packageKey]*packageHandle),
+		actions:           make(map[actionKey]*actionHandle),
+		files:             make(map[span.URI]source.VersionedFileHandle),
+		goFiles:           make(map[parseKey]*parseGoHandle),
+		workspacePackages: make(map[packageID]packagePath),
+		unloadableFiles:   make(map[span.URI]struct{}),
+		parseModHandles:   make(map[span.URI]*parseModHandle),
+		modTidyHandles:    make(map[span.URI]*modTidyHandle),
+		modUpgradeHandles: make(map[span.URI]*modUpgradeHandle),
+		modWhyHandles:     make(map[span.URI]*modWhyHandle),
+		workspace:         newWorkspace,
+	}
+
+	if !workspaceChanged && s.workspaceDirHandle != nil {
+		result.workspaceDirHandle = s.workspaceDirHandle
+		newGen.Inherit(s.workspaceDirHandle)
 	}
 
 	if s.builtin != nil {
@@ -1010,6 +1052,7 @@
 	for k, v := range s.files {
 		result.files[k] = v
 	}
+
 	// Copy the set of unloadable files.
 	for k, v := range s.unloadableFiles {
 		result.unloadableFiles[k] = v
@@ -1018,13 +1061,9 @@
 	for k, v := range s.parseModHandles {
 		result.parseModHandles[k] = v
 	}
-	// Copy all of the workspace directories. They may be reset later.
-	for k, v := range s.workspaceDirectories {
-		result.workspaceDirectories[k] = v
-	}
 
 	for k, v := range s.goFiles {
-		if _, ok := withoutURIs[k.file.URI]; ok {
+		if _, ok := changes[k.file.URI]; ok {
 			continue
 		}
 		newGen.Inherit(v.handle)
@@ -1035,53 +1074,58 @@
 	// Copy all of the go.mod-related handles. They may be invalidated later,
 	// so we inherit them at the end of the function.
 	for k, v := range s.modTidyHandles {
-		if _, ok := withoutURIs[k]; ok {
+		if _, ok := changes[k]; ok {
 			continue
 		}
 		result.modTidyHandles[k] = v
 	}
 	for k, v := range s.modUpgradeHandles {
-		if _, ok := withoutURIs[k]; ok {
+		if _, ok := changes[k]; ok {
 			continue
 		}
 		result.modUpgradeHandles[k] = v
 	}
 	for k, v := range s.modWhyHandles {
-		if _, ok := withoutURIs[k]; ok {
+		if _, ok := changes[k]; ok {
 			continue
 		}
 		result.modWhyHandles[k] = v
 	}
 
-	// Add all of the modules now. They may be deleted or added to later.
-	for k, v := range s.modules {
-		result.modules[k] = v
-	}
-
-	var modulesChanged, shouldReinitializeView bool
-
 	// directIDs keeps track of package IDs that have directly changed.
 	// It maps id->invalidateMetadata.
 	directIDs := map[packageID]bool{}
-	for withoutURI, currentFH := range withoutURIs {
+	// Invalidate all package metadata if the workspace module has changed.
+	if workspaceChanged {
+		for k := range s.metadata {
+			directIDs[k] = true
+		}
+	}
+
+	for uri, change := range changes {
+		// Maybe reinitialize the view if we see a change in the vendor
+		// directory.
+		if inVendor(uri) {
+			vendorChanged = true
+		}
 
 		// The original FileHandle for this URI is cached on the snapshot.
-		originalFH := s.files[withoutURI]
+		originalFH := s.files[uri]
 
 		// Check if the file's package name or imports have changed,
 		// and if so, invalidate this file's packages' metadata.
-		invalidateMetadata := forceReloadMetadata || s.shouldInvalidateMetadata(ctx, result, originalFH, currentFH)
+		invalidateMetadata := forceReloadMetadata || s.shouldInvalidateMetadata(ctx, result, originalFH, change.fileHandle)
 
 		// Mark all of the package IDs containing the given file.
 		// TODO: if the file has moved into a new package, we should invalidate that too.
-		filePackages := guessPackagesForURI(withoutURI, s.ids)
+		filePackages := guessPackagesForURI(uri, s.ids)
 		for _, id := range filePackages {
 			directIDs[id] = directIDs[id] || invalidateMetadata
 		}
 
 		// Invalidate the previous modTidyHandle if any of the files have been
 		// saved or if any of the metadata has been invalidated.
-		if invalidateMetadata || fileWasSaved(originalFH, currentFH) {
+		if invalidateMetadata || fileWasSaved(originalFH, change.fileHandle) {
 			// TODO(rstambler): Only delete mod handles for which the
 			// withoutURI is relevant.
 			for k := range s.modTidyHandles {
@@ -1094,75 +1138,22 @@
 				delete(result.modWhyHandles, k)
 			}
 		}
-		currentExists := true
-		if _, err := currentFH.Read(); os.IsNotExist(err) {
-			currentExists = false
-		}
-		// If the file invalidation is for a go.mod. originalFH is nil if the
-		// file is newly created.
-		currentMod := currentExists && currentFH.Kind() == source.Mod
-		originalMod := originalFH != nil && originalFH.Kind() == source.Mod
-		if currentMod || originalMod {
-			modulesChanged = true
-
+		if isGoMod(uri) {
 			// If the view's go.mod file's contents have changed, invalidate
 			// the metadata for every known package in the snapshot.
-			if invalidateMetadata {
-				for k := range s.metadata {
-					directIDs[k] = true
-				}
-				// If a go.mod file in the workspace has changed, we need to
-				// rebuild the workspace module.
-				result.workspaceModuleHandle = nil
-			}
-			delete(result.parseModHandles, withoutURI)
-
-			// Check if this is a newly created go.mod file. When a new module
-			// is created, we have to retry the initial workspace load.
-			rootURI := span.URIFromPath(filepath.Dir(withoutURI.Filename()))
-			if currentMod {
-				if _, ok := result.modules[rootURI]; !ok {
-					if m := getViewModule(ctx, s.view.rootURI, currentFH.URI(), s.view.Options()); m != nil {
-						result.modules[m.rootURI] = m
-						shouldReinitializeView = true
-					}
-
-				}
-			} else if originalMod {
-				// Similarly, we need to retry the IWL if a go.mod in the workspace
-				// was deleted.
-				if _, ok := result.modules[rootURI]; ok {
-					delete(result.modules, rootURI)
-					shouldReinitializeView = true
-				}
+			delete(result.parseModHandles, uri)
+			if _, ok := result.workspace.activeModFiles()[uri]; ok {
+				modulesChanged = true
 			}
 		}
-		// Keep track of the creations and deletions of go.sum files.
-		// Creating a go.sum without an associated go.mod has no effect on the
-		// set of modules.
-		currentSum := currentExists && currentFH.Kind() == source.Sum
-		originalSum := originalFH != nil && originalFH.Kind() == source.Sum
-		if currentSum || originalSum {
-			rootURI := span.URIFromPath(filepath.Dir(withoutURI.Filename()))
-			if currentSum {
-				if mod, ok := result.modules[rootURI]; ok {
-					mod.sumURI = currentFH.URI()
-				}
-			} else if originalSum {
-				if mod, ok := result.modules[rootURI]; ok {
-					mod.sumURI = ""
-				}
-			}
-		}
-
 		// Handle the invalidated file; it may have new contents or not exist.
-		if !currentExists {
-			delete(result.files, withoutURI)
+		if !change.exists {
+			delete(result.files, uri)
 		} else {
-			result.files[withoutURI] = currentFH
+			result.files[uri] = change.fileHandle
 		}
 		// Make sure to remove the changed file from the unloadable set.
-		delete(result.unloadableFiles, withoutURI)
+		delete(result.unloadableFiles, uri)
 	}
 
 	// Invalidate reverse dependencies too.
@@ -1190,12 +1181,6 @@
 		addRevDeps(id, invalidateMetadata)
 	}
 
-	// When modules change, we need to recompute their workspace directories,
-	// as replace directives may have changed.
-	if modulesChanged {
-		result.workspaceDirectories = result.findWorkspaceDirectories(ctx)
-	}
-
 	// Copy the package type information.
 	for k, v := range s.packages {
 		if _, ok := transitiveIDs[k.id]; ok {
@@ -1274,26 +1259,22 @@
 	for _, v := range result.parseModHandles {
 		newGen.Inherit(v.handle)
 	}
-	if result.workspaceModuleHandle != nil {
-		newGen.Inherit(result.workspaceModuleHandle.handle)
-	}
 	// Don't bother copying the importedBy graph,
 	// as it changes each time we update metadata.
 
-	var reinitialize reinitializeView
-	if modulesChanged {
-		reinitialize = maybeReinit
-	}
-	if shouldReinitializeView {
-		reinitialize = definitelyReinit
-	}
-
 	// If the snapshot's workspace mode has changed, the packages loaded using
 	// the previous mode are no longer relevant, so clear them out.
 	if s.workspaceMode() != result.workspaceMode() {
 		result.workspacePackages = map[packageID]packagePath{}
 	}
-	return result, reinitialize
+
+	// The snapshot may need to be reinitialized.
+	if modulesChanged || workspaceChanged || vendorChanged {
+		if workspaceChanged || result.initializedErr != nil {
+			result.initializeOnce = &sync.Once{}
+		}
+	}
+	return result, workspaceChanged
 }
 
 // guessPackagesForURI returns all packages related to uri. If we haven't seen this
@@ -1346,14 +1327,6 @@
 	return found
 }
 
-type reinitializeView int
-
-const (
-	doNotReinit = reinitializeView(iota)
-	maybeReinit
-	definitelyReinit
-)
-
 // fileWasSaved reports whether the FileHandle passed in has been saved. It
 // accomplishes this by checking to see if the original and current FileHandles
 // are both overlays, and if the current FileHandle is saved while the original
@@ -1382,7 +1355,7 @@
 	}
 	// If a go.mod in the workspace has been changed, invalidate metadata.
 	if kind := originalFH.Kind(); kind == source.Mod {
-		return isSubdirectory(filepath.Dir(s.view.rootURI.Filename()), filepath.Dir(originalFH.URI().Filename()))
+		return source.InDir(filepath.Dir(s.view.rootURI.Filename()), filepath.Dir(originalFH.URI().Filename()))
 	}
 	// Get the original and current parsed files in order to check package name
 	// and imports. Use the new snapshot to parse to avoid modifying the
@@ -1425,45 +1398,6 @@
 	return false
 }
 
-// findWorkspaceDirectoriesLocked returns all of the directories that are
-// considered to be part of the view's workspace. For GOPATH workspaces, this
-// is just the view's root. For modules-based workspaces, this is the module
-// root and any replace targets. It also returns the parseModHandle for the
-// view's go.mod file if it has one.
-//
-// It assumes that the file handle is the view's go.mod file, if it has one.
-// The caller need not be holding the snapshot's mutex, but it might be.
-func (s *snapshot) findWorkspaceDirectories(ctx context.Context) map[span.URI]struct{} {
-	// If the view does not have a go.mod file, only the root directory
-	// is known. In GOPATH mode, we should really watch the entire GOPATH,
-	// but that's too expensive.
-	dirs := map[span.URI]struct{}{
-		s.view.rootURI: {},
-	}
-	for _, m := range s.modules {
-		fh, err := s.GetFile(ctx, m.modURI)
-		if err != nil {
-			continue
-		}
-		// Ignore parse errors. An invalid go.mod is not fatal.
-		// TODO(rstambler): Try to preserve existing watched directories as
-		// much as possible, otherwise we will thrash when a go.mod is edited.
-		mod, err := s.ParseMod(ctx, fh)
-		if err != nil {
-			continue
-		}
-		for _, r := range mod.File.Replace {
-			// We may be replacing a module with a different version, not a path
-			// on disk.
-			if r.New.Version != "" {
-				continue
-			}
-			dirs[span.URIFromPath(r.New.Path)] = struct{}{}
-		}
-	}
-	return dirs
-}
-
 func (s *snapshot) BuiltinPackage(ctx context.Context) (*source.BuiltinPackage, error) {
 	s.AwaitInitialized(ctx)
 
@@ -1510,116 +1444,45 @@
 				Package:    pkg,
 			},
 		}
-	})
+	}, nil)
 	s.builtin = &builtinPackageHandle{handle: h}
 	return nil
 }
 
-type workspaceModuleHandle struct {
-	handle *memoize.Handle
-}
-
-type workspaceModuleData struct {
-	file *modfile.File
-	err  error
-}
-
-type workspaceModuleKey string
-
-func (wmh *workspaceModuleHandle) build(ctx context.Context, snapshot *snapshot) (*modfile.File, error) {
-	v, err := wmh.handle.Get(ctx, snapshot.generation, snapshot)
+// BuildGoplsMod generates a go.mod file for all modules in the workspace. It
+// bypasses any existing gopls.mod.
+func BuildGoplsMod(ctx context.Context, root span.URI, fs source.FileSource) (*modfile.File, error) {
+	allModules, err := findAllModules(ctx, root)
 	if err != nil {
 		return nil, err
 	}
-	data := v.(*workspaceModuleData)
-	return data.file, data.err
+	return buildWorkspaceModFile(ctx, allModules, fs)
 }
 
-func (s *snapshot) getWorkspaceModuleHandle(ctx context.Context) (*workspaceModuleHandle, error) {
-	s.mu.Lock()
-	wsModule := s.workspaceModuleHandle
-	s.mu.Unlock()
-	if wsModule != nil {
-		return wsModule, nil
-	}
-	var fhs []source.FileHandle
-	for _, mod := range s.modules {
-		fh, err := s.GetFile(ctx, mod.modURI)
-		if err != nil {
-			return nil, err
-		}
-		fhs = append(fhs, fh)
-	}
-	goplsModURI := span.URIFromPath(filepath.Join(s.view.Folder().Filename(), "gopls.mod"))
-	goplsModFH, err := s.GetFile(ctx, goplsModURI)
-	if err != nil {
-		return nil, err
-	}
-	_, err = goplsModFH.Read()
-	switch {
-	case err == nil:
-		// We have a gopls.mod. Our handle only depends on it.
-		fhs = []source.FileHandle{goplsModFH}
-	case os.IsNotExist(err):
-		// No gopls.mod, so we must build the workspace mod file automatically.
-		// Defensively ensure that the goplsModFH is nil as this controls automatic
-		// building of the workspace mod file.
-		goplsModFH = nil
-	default:
-		return nil, errors.Errorf("error getting gopls.mod: %w", err)
-	}
-
-	sort.Slice(fhs, func(i, j int) bool {
-		return fhs[i].URI() < fhs[j].URI()
-	})
-	var k string
-	for _, fh := range fhs {
-		k += fh.FileIdentity().String()
-	}
-	key := workspaceModuleKey(hashContents([]byte(k)))
-	h := s.generation.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} {
-		if goplsModFH != nil {
-			parsed, err := s.ParseMod(ctx, goplsModFH)
-			if err != nil {
-				return &workspaceModuleData{err: err}
-			}
-			return &workspaceModuleData{file: parsed.File}
-		}
-		s := arg.(*snapshot)
-		data := &workspaceModuleData{}
-		data.file, data.err = s.BuildWorkspaceModFile(ctx)
-		return data
-	})
-	wsModule = &workspaceModuleHandle{
-		handle: h,
-	}
-	s.mu.Lock()
-	defer s.mu.Unlock()
-	s.workspaceModuleHandle = wsModule
-	return s.workspaceModuleHandle, nil
-}
-
-// BuildWorkspaceModFile generates a workspace module given the modules in the
-// the workspace. It does not read gopls.mod.
-func (s *snapshot) BuildWorkspaceModFile(ctx context.Context) (*modfile.File, error) {
+// TODO(rfindley): move this to workspacemodule.go
+func buildWorkspaceModFile(ctx context.Context, modFiles map[span.URI]struct{}, fs source.FileSource) (*modfile.File, error) {
 	file := &modfile.File{}
 	file.AddModuleStmt("gopls-workspace")
 
-	paths := make(map[string]*moduleRoot)
-	for _, mod := range s.modules {
-		fh, err := s.GetFile(ctx, mod.modURI)
+	paths := make(map[string]span.URI)
+	for modURI := range modFiles {
+		fh, err := fs.GetFile(ctx, modURI)
 		if err != nil {
 			return nil, err
 		}
-		parsed, err := s.ParseMod(ctx, fh)
+		content, err := fh.Read()
 		if err != nil {
 			return nil, err
 		}
-		if parsed.File == nil || parsed.File.Module == nil {
-			return nil, fmt.Errorf("no module declaration for %s", mod.modURI)
+		parsed, err := modfile.Parse(fh.URI().Filename(), content, nil)
+		if err != nil {
+			return nil, err
 		}
-		path := parsed.File.Module.Mod.Path
-		paths[path] = mod
+		if file == nil || parsed.Module == nil {
+			return nil, fmt.Errorf("no module declaration for %s", modURI)
+		}
+		path := parsed.Module.Mod.Path
+		paths[path] = modURI
 		// If the module's path includes a major version, we expect it to have
 		// a matching major version.
 		_, majorVersion, _ := module.SplitPathVersion(path)
@@ -1628,24 +1491,28 @@
 		}
 		majorVersion = strings.TrimLeft(majorVersion, "/.") // handle gopkg.in versions
 		file.AddNewRequire(path, source.WorkspaceModuleVersion(majorVersion), false)
-		if err := file.AddReplace(path, "", mod.rootURI.Filename(), ""); err != nil {
+		if err := file.AddReplace(path, "", dirURI(modURI).Filename(), ""); err != nil {
 			return nil, err
 		}
 	}
 	// Go back through all of the modules to handle any of their replace
 	// statements.
-	for _, module := range s.modules {
-		fh, err := s.GetFile(ctx, module.modURI)
+	for modURI := range modFiles {
+		fh, err := fs.GetFile(ctx, modURI)
 		if err != nil {
 			return nil, err
 		}
-		pmf, err := s.ParseMod(ctx, fh)
+		content, err := fh.Read()
+		if err != nil {
+			return nil, err
+		}
+		parsed, err := modfile.Parse(fh.URI().Filename(), content, nil)
 		if err != nil {
 			return nil, err
 		}
 		// If any of the workspace modules have replace directives, they need
 		// to be reflected in the workspace module.
-		for _, rep := range pmf.File.Replace {
+		for _, rep := range parsed.Replace {
 			// Don't replace any modules that are in our workspace--we should
 			// always use the version in the workspace.
 			if _, ok := paths[rep.Old.Path]; ok {
@@ -1655,12 +1522,12 @@
 			newVersion := rep.New.Version
 			// If a replace points to a module in the workspace, make sure we
 			// direct it to version of the module in the workspace.
-			if mod, ok := paths[rep.New.Path]; ok {
-				newPath = mod.rootURI.Filename()
+			if m, ok := paths[rep.New.Path]; ok {
+				newPath = dirURI(m).Filename()
 				newVersion = ""
 			} else if rep.New.Version == "" && !filepath.IsAbs(rep.New.Path) {
 				// Make any relative paths absolute.
-				newPath = filepath.Join(module.rootURI.Filename(), rep.New.Path)
+				newPath = filepath.Join(dirURI(modURI).Filename(), rep.New.Path)
 			}
 			if err := file.AddReplace(rep.Old.Path, rep.Old.Version, newPath, newVersion); err != nil {
 				return nil, err
@@ -1669,23 +1536,3 @@
 	}
 	return file, nil
 }
-
-func getViewModule(ctx context.Context, viewRootURI, modURI span.URI, options *source.Options) *moduleRoot {
-	rootURI := span.URIFromPath(filepath.Dir(modURI.Filename()))
-	// If we are not in multi-module mode, check that the affected module is
-	// in the workspace root.
-	if !options.ExperimentalWorkspaceModule {
-		if span.CompareURI(rootURI, viewRootURI) != 0 {
-			return nil
-		}
-	}
-	sumURI := span.URIFromPath(sumFilename(modURI))
-	if info, _ := os.Stat(sumURI.Filename()); info == nil {
-		sumURI = ""
-	}
-	return &moduleRoot{
-		rootURI: rootURI,
-		modURI:  modURI,
-		sumURI:  sumURI,
-	}
-}
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index b20182a..489504b 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -68,37 +68,30 @@
 	filesByURI  map[span.URI]*fileBase
 	filesByBase map[string][]*fileBase
 
+	// initCancelFirstAttempt can be used to terminate the view's first
+	// attempt at initialization.
+	initCancelFirstAttempt context.CancelFunc
+
 	snapshotMu sync.Mutex
 	snapshot   *snapshot
 
-	// initialized is closed when the view has been fully initialized. On
-	// initialization, the view's workspace packages are loaded. All of the
-	// fields below are set as part of initialization. If we failed to load, we
-	// only retry if the go.mod file changes, to avoid too many go/packages
-	// calls.
-	//
-	// When the view is created, initializeOnce is non-nil, initialized is
-	// open, and initCancelFirstAttempt can be used to terminate
-	// initialization. Once initialization completes, initializedErr may be set
-	// and initializeOnce becomes nil. If initializedErr is non-nil,
-	// initialization may be retried (depending on how files are changed). To
-	// indicate that initialization should be retried, initializeOnce will be
-	// set. The next time a caller requests workspace packages, the
-	// initialization will retry.
-	initialized            chan struct{}
-	initCancelFirstAttempt context.CancelFunc
+	// initialWorkspaceLoad is closed when the first workspace initialization has
+	// completed. If we failed to load, we only retry if the go.mod file changes,
+	// to avoid too many go/packages calls.
+	initialWorkspaceLoad chan struct{}
 
-	// initializationSema is used as a mutex to guard initializeOnce and
-	// initializedErr, which will be updated after each attempt to initialize
+	// initializationSema is used limit concurrent initialization of snapshots in
 	// the view. We use a channel instead of a mutex to avoid blocking when a
 	// context is canceled.
 	initializationSema chan struct{}
-	initializeOnce     *sync.Once
-	initializedErr     error
 
 	// workspaceInformation tracks various details about this view's
 	// environment variables, go version, and use of modules.
 	workspaceInformation
+
+	// tempWorkspace is a temporary directory dedicated to holding the latest
+	// version of the workspace go.mod file. (TODO: also go.sum file)
+	tempWorkspace span.URI
 }
 
 type workspaceInformation struct {
@@ -126,7 +119,7 @@
 }
 
 type environmentVariables struct {
-	gocache, gopath, goprivate, gomodcache, gomod string
+	gocache, gopath, goprivate, gomodcache string
 }
 
 type workspaceMode int
@@ -151,11 +144,6 @@
 	err    error
 }
 
-type moduleRoot struct {
-	rootURI        span.URI
-	modURI, sumURI span.URI
-}
-
 // fileBase holds the common functionality for all files.
 // It is intended to be embedded in the file implementations
 type fileBase struct {
@@ -183,7 +171,7 @@
 // tempModFile creates a temporary go.mod file based on the contents of the
 // given go.mod file. It is the caller's responsibility to clean up the files
 // when they are done using them.
-func tempModFile(modFh, sumFH source.FileHandle) (tmpURI span.URI, cleanup func(), err error) {
+func tempModFile(modFh source.FileHandle, gosum []byte) (tmpURI span.URI, cleanup func(), err error) {
 	filenameHash := hashContents([]byte(modFh.URI().Filename()))
 	tmpMod, err := ioutil.TempFile("", fmt.Sprintf("go.%s.*.mod", filenameHash))
 	if err != nil {
@@ -217,12 +205,8 @@
 	}()
 
 	// Create an analogous go.sum, if one exists.
-	if sumFH != nil {
-		sumContents, err := sumFH.Read()
-		if err != nil {
-			return "", cleanup, err
-		}
-		if err := ioutil.WriteFile(tmpSumName, sumContents, 0655); err != nil {
+	if gosum != nil {
+		if err := ioutil.WriteFile(tmpSumName, gosum, 0655); err != nil {
 			return "", cleanup, err
 		}
 	}
@@ -285,7 +269,8 @@
 
 func (s *snapshot) WriteEnv(ctx context.Context, w io.Writer) error {
 	s.view.optionsMu.Lock()
-	env, buildFlags := s.view.envLocked()
+	env := s.view.options.EnvSlice()
+	buildFlags := append([]string{}, s.view.options.BuildFlags...)
 	s.view.optionsMu.Unlock()
 
 	fullEnv := make(map[string]string)
@@ -330,14 +315,6 @@
 	return s.view.importsState.runProcessEnvFunc(ctx, s, fn)
 }
 
-// envLocked returns the environment and build flags for the current view.
-// It assumes that the caller is holding the view's optionsMu.
-func (v *View) envLocked() ([]string, []string) {
-	env := append(os.Environ(), v.options.EnvSlice()...)
-	buildFlags := append([]string{}, v.options.BuildFlags...)
-	return env, buildFlags
-}
-
 func (v *View) contains(uri span.URI) bool {
 	return strings.HasPrefix(string(uri), string(v.rootURI))
 }
@@ -434,6 +411,8 @@
 	v.session.removeView(ctx, v)
 }
 
+// TODO(rFindley): probably some of this should also be one in View.Shutdown
+// above?
 func (v *View) shutdown(ctx context.Context) {
 	// Cancel the initial workspace load if it is still running.
 	v.initCancelFirstAttempt()
@@ -447,6 +426,11 @@
 	v.snapshotMu.Lock()
 	go v.snapshot.generation.Destroy()
 	v.snapshotMu.Unlock()
+	if v.tempWorkspace != "" {
+		if err := os.RemoveAll(v.tempWorkspace.Filename()); err != nil {
+			event.Error(ctx, "removing temp workspace", err)
+		}
+	}
 }
 
 func (v *View) BackgroundContext() context.Context {
@@ -459,14 +443,14 @@
 func (s *snapshot) IgnoredFile(uri span.URI) bool {
 	filename := uri.Filename()
 	var prefixes []string
-	if len(s.modules) == 0 {
+	if len(s.workspace.activeModFiles()) == 0 {
 		for _, entry := range filepath.SplitList(s.view.gopath) {
 			prefixes = append(prefixes, filepath.Join(entry, "src"))
 		}
 	} else {
 		prefixes = append(prefixes, s.view.gomodcache)
-		for _, m := range s.modules {
-			prefixes = append(prefixes, m.rootURI.Filename())
+		for m := range s.workspace.activeModFiles() {
+			prefixes = append(prefixes, dirURI(m).Filename())
 		}
 	}
 	for _, prefix := range prefixes {
@@ -509,14 +493,14 @@
 		<-s.view.initializationSema
 	}()
 
-	if s.view.initializeOnce == nil {
+	if s.initializeOnce == nil {
 		return
 	}
-	s.view.initializeOnce.Do(func() {
+	s.initializeOnce.Do(func() {
 		defer func() {
-			s.view.initializeOnce = nil
+			s.initializeOnce = nil
 			if firstAttempt {
-				close(s.view.initialized)
+				close(s.view.initialWorkspaceLoad)
 			}
 		}()
 
@@ -534,25 +518,23 @@
 				Message:  err.Error(),
 			})
 		}
-		if len(s.modules) > 0 {
-			for _, mod := range s.modules {
-				fh, err := s.GetFile(ctx, mod.modURI)
-				if err != nil {
-					addError(mod.modURI, err)
-					continue
-				}
-				parsed, err := s.ParseMod(ctx, fh)
-				if err != nil {
-					addError(mod.modURI, err)
-					continue
-				}
-				if parsed.File == nil || parsed.File.Module == nil {
-					addError(mod.modURI, fmt.Errorf("no module path for %s", mod.modURI))
-					continue
-				}
-				path := parsed.File.Module.Mod.Path
-				scopes = append(scopes, moduleLoadScope(path))
+		for modURI := range s.workspace.activeModFiles() {
+			fh, err := s.GetFile(ctx, modURI)
+			if err != nil {
+				addError(modURI, err)
+				continue
 			}
+			parsed, err := s.ParseMod(ctx, fh)
+			if err != nil {
+				addError(modURI, err)
+				continue
+			}
+			if parsed.File == nil || parsed.File.Module == nil {
+				addError(modURI, fmt.Errorf("no module path for %s", modURI))
+				continue
+			}
+			path := parsed.File.Module.Mod.Path
+			scopes = append(scopes, moduleLoadScope(path))
 		}
 		if len(scopes) == 0 {
 			scopes = append(scopes, viewLoadScope("LOAD_VIEW"))
@@ -564,9 +546,9 @@
 		if err != nil {
 			event.Error(ctx, "initial workspace load failed", err)
 			if modErrors != nil {
-				s.view.initializedErr = errors.Errorf("errors loading modules: %v: %w", err, modErrors)
+				s.initializedErr = errors.Errorf("errors loading modules: %v: %w", err, modErrors)
 			} else {
-				s.view.initializedErr = err
+				s.initializedErr = err
 			}
 		}
 	})
@@ -575,7 +557,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 map[span.URI]source.VersionedFileHandle, forceReloadMetadata bool) (source.Snapshot, func()) {
+func (v *View) invalidateContent(ctx context.Context, changes map[span.URI]*fileChange, forceReloadMetadata bool) (source.Snapshot, func()) {
 	// Detach the context so that content invalidation cannot be canceled.
 	ctx = xcontext.Detach(ctx)
 
@@ -591,17 +573,45 @@
 	defer v.snapshotMu.Unlock()
 
 	oldSnapshot := v.snapshot
-	var reinitialize reinitializeView
-	v.snapshot, reinitialize = oldSnapshot.clone(ctx, uris, forceReloadMetadata)
+
+	var workspaceChanged bool
+	v.snapshot, workspaceChanged = oldSnapshot.clone(ctx, changes, forceReloadMetadata)
+	if workspaceChanged && v.tempWorkspace != "" {
+		snap := v.snapshot
+		go func() {
+			wsdir, err := snap.getWorkspaceDir(ctx)
+			if err != nil {
+				event.Error(ctx, "getting workspace dir", err)
+			}
+			if err := copyWorkspace(v.tempWorkspace, wsdir); err != nil {
+				event.Error(ctx, "copying workspace dir", err)
+			}
+		}()
+	}
 	go oldSnapshot.generation.Destroy()
 
-	if reinitialize == maybeReinit || reinitialize == definitelyReinit {
-		v.reinitialize(reinitialize == definitelyReinit)
-	}
-
 	return v.snapshot, v.snapshot.generation.Acquire(ctx)
 }
 
+func copyWorkspace(dst span.URI, src span.URI) error {
+	srcMod := filepath.Join(src.Filename(), "go.mod")
+	srcf, err := os.Open(srcMod)
+	if err != nil {
+		return errors.Errorf("opening snapshot mod file: %w", err)
+	}
+	defer srcf.Close()
+	dstMod := filepath.Join(dst.Filename(), "go.mod")
+	dstf, err := os.Create(dstMod)
+	if err != nil {
+		return errors.Errorf("truncating view mod file: %w", err)
+	}
+	defer dstf.Close()
+	if _, err := io.Copy(dstf, srcf); err != nil {
+		return errors.Errorf("copying modfiles: %w", err)
+	}
+	return nil
+}
+
 func (v *View) cancelBackground() {
 	v.mu.Lock()
 	defer v.mu.Unlock()
@@ -613,19 +623,6 @@
 	v.backgroundCtx, v.cancel = context.WithCancel(v.baseCtx)
 }
 
-func (v *View) reinitialize(force bool) {
-	v.initializationSema <- struct{}{}
-	defer func() {
-		<-v.initializationSema
-	}()
-
-	if !force && v.initializedErr == nil {
-		return
-	}
-	var once sync.Once
-	v.initializeOnce = &once
-}
-
 func (s *Session) getWorkspaceInformation(ctx context.Context, folder span.URI, options *source.Options) (*workspaceInformation, error) {
 	if err := checkPathCase(folder.Filename()); err != nil {
 		return nil, errors.Errorf("invalid workspace configuration: %w", err)
@@ -668,13 +665,15 @@
 	tool, _ := exec.LookPath("gopackagesdriver")
 	hasGopackagesDriver := gopackagesdriver != "off" && (gopackagesdriver != "" || tool != "")
 
-	var modURI span.URI
-	if envVars.gomod != os.DevNull && envVars.gomod != "" {
-		modURI = span.URIFromPath(envVars.gomod)
-	}
 	root := folder
-	if options.ExpandWorkspaceToModule && modURI != "" {
-		root = span.URIFromPath(filepath.Dir(modURI.Filename()))
+	if options.ExpandWorkspaceToModule {
+		wsRoot, err := findWorkspaceRoot(ctx, root, s)
+		if err != nil {
+			return nil, err
+		}
+		if wsRoot != "" {
+			root = wsRoot
+		}
 	}
 	return &workspaceInformation{
 		hasGopackagesDriver:  hasGopackagesDriver,
@@ -686,6 +685,39 @@
 	}, nil
 }
 
+func findWorkspaceRoot(ctx context.Context, folder span.URI, fs source.FileSource) (span.URI, error) {
+	for _, basename := range []string{"gopls.mod", "go.mod"} {
+		dir, err := findRootPattern(ctx, folder, basename, fs)
+		if err != nil {
+			return "", errors.Errorf("finding %s: %w", basename, err)
+		}
+		if dir != "" {
+			return dir, nil
+		}
+	}
+	return "", nil
+}
+
+func findRootPattern(ctx context.Context, folder span.URI, basename string, fs source.FileSource) (span.URI, error) {
+	dir := folder.Filename()
+	for dir != "" {
+		target := filepath.Join(dir, basename)
+		exists, err := fileExists(ctx, span.URIFromPath(target), fs)
+		if err != nil {
+			return "", err
+		}
+		if exists {
+			return span.URIFromPath(dir), nil
+		}
+		next, _ := filepath.Split(dir)
+		if next == dir {
+			break
+		}
+		dir = next
+	}
+	return "", nil
+}
+
 // OS-specific path case check, for case-insensitive filesystems.
 var checkPathCase = defaultCheckPathCase
 
@@ -693,7 +725,7 @@
 	return nil
 }
 
-func validBuildConfiguration(folder span.URI, ws *workspaceInformation, modules map[span.URI]*moduleRoot) bool {
+func validBuildConfiguration(folder span.URI, ws *workspaceInformation, modFiles map[span.URI]struct{}) bool {
 	// Since we only really understand the `go` command, if the user has a
 	// different GOPACKAGESDRIVER, assume that their configuration is valid.
 	if ws.hasGopackagesDriver {
@@ -701,24 +733,19 @@
 	}
 	// Check if the user is working within a module or if we have found
 	// multiple modules in the workspace.
-	if len(modules) > 0 {
+	if len(modFiles) > 0 {
 		return true
 	}
 	// The user may have a multiple directories in their GOPATH.
 	// Check if the workspace is within any of them.
 	for _, gp := range filepath.SplitList(ws.gopath) {
-		if isSubdirectory(filepath.Join(gp, "src"), folder.Filename()) {
+		if source.InDir(filepath.Join(gp, "src"), folder.Filename()) {
 			return true
 		}
 	}
 	return false
 }
 
-func isSubdirectory(root, leaf string) bool {
-	rel, err := filepath.Rel(root, leaf)
-	return err == nil && !strings.HasPrefix(rel, "..")
-}
-
 // getGoEnv gets the view's various GO* values.
 func (s *Session) getGoEnv(ctx context.Context, folder string, configEnv []string) (environmentVariables, map[string]string, error) {
 	envVars := environmentVariables{}
@@ -727,7 +754,6 @@
 		"GOPATH":     &envVars.gopath,
 		"GOPRIVATE":  &envVars.goprivate,
 		"GOMODCACHE": &envVars.gomodcache,
-		"GOMOD":      &envVars.gomod,
 	}
 	// We can save ~200 ms by requesting only the variables we care about.
 	args := append([]string{"-json"}, imports.RequiredGoEnvVars...)
diff --git a/internal/lsp/cache/view_test.go b/internal/lsp/cache/view_test.go
index 0dad594..39f2e86 100644
--- a/internal/lsp/cache/view_test.go
+++ b/internal/lsp/cache/view_test.go
@@ -4,10 +4,14 @@
 package cache
 
 import (
+	"context"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"testing"
+
+	"golang.org/x/tools/internal/lsp/fake"
+	"golang.org/x/tools/internal/span"
 )
 
 func TestCaseInsensitiveFilesystem(t *testing.T) {
@@ -43,3 +47,74 @@
 		}
 	}
 }
+
+func TestFindWorkspaceRoot(t *testing.T) {
+	workspace := `
+-- a/go.mod --
+module a
+-- a/x/x.go
+package x
+-- b/go.mod --
+module b
+-- b/c/go.mod --
+module bc
+-- d/gopls.mod --
+module d-goplsworkspace
+-- d/e/go.mod
+module de
+`
+	dir, err := fake.Tempdir(workspace)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dir)
+
+	tests := []struct {
+		folder, want string
+	}{
+		// no module at root.
+		{"", ""},
+		{"a", "a"},
+		{"a/x", "a"},
+		{"b/c", "b/c"},
+		{"d", "d"},
+		{"d/e", "d"},
+	}
+
+	for _, test := range tests {
+		ctx := context.Background()
+		rel := fake.RelativeTo(dir)
+		folderURI := span.URIFromPath(rel.AbsPath(test.folder))
+		got, err := findWorkspaceRoot(ctx, folderURI, osFileSource{})
+		if err != nil {
+			t.Fatal(err)
+		}
+		if rel.RelPath(got.Filename()) != test.want {
+			t.Errorf("fileWorkspaceRoot(%q) = %q, want %q", test.folder, got, test.want)
+		}
+	}
+}
+
+func TestInVendor(t *testing.T) {
+	for _, tt := range []struct {
+		path     string
+		inVendor bool
+	}{
+		{
+			path:     "foo/vendor/x.go",
+			inVendor: false,
+		},
+		{
+			path:     "foo/vendor/x/x.go",
+			inVendor: true,
+		},
+		{
+			path:     "foo/x.go",
+			inVendor: false,
+		},
+	} {
+		if got := inVendor(span.URIFromPath(tt.path)); got != tt.inVendor {
+			t.Errorf("expected %s inVendor %v, got %v", tt.path, tt.inVendor, got)
+		}
+	}
+}
diff --git a/internal/lsp/cache/workspace.go b/internal/lsp/cache/workspace.go
new file mode 100644
index 0000000..4d05adc
--- /dev/null
+++ b/internal/lsp/cache/workspace.go
@@ -0,0 +1,409 @@
+// 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 cache
+
+import (
+	"context"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+	"sync"
+
+	"golang.org/x/mod/modfile"
+	"golang.org/x/tools/internal/event"
+	"golang.org/x/tools/internal/lsp/source"
+	"golang.org/x/tools/internal/span"
+	"golang.org/x/tools/internal/xcontext"
+	errors "golang.org/x/xerrors"
+)
+
+type workspaceSource int
+
+const (
+	legacyWorkspace = iota
+	goplsModWorkspace
+	fileSystemWorkspace
+)
+
+func (s workspaceSource) String() string {
+	switch s {
+	case legacyWorkspace:
+		return "legacy"
+	case goplsModWorkspace:
+		return "gopls.mod"
+	case fileSystemWorkspace:
+		return "file system"
+	default:
+		return "!(unknown module source)"
+	}
+}
+
+// workspace tracks go.mod files in the workspace, along with the
+// gopls.mod file, to provide support for multi-module workspaces.
+//
+// Specifically, it provides:
+//  - the set of modules contained within in the workspace root considered to
+//    be 'active'
+//  - the workspace modfile, to be used for the go command `-modfile` flag
+//  - the set of workspace directories
+//
+// This type is immutable (or rather, idempotent), so that it may be shared
+// across multiple snapshots.
+type workspace struct {
+	root         span.URI
+	moduleSource workspaceSource
+
+	// modFiles holds the active go.mod files.
+	modFiles map[span.URI]struct{}
+
+	// The workspace module is lazily re-built once after being invalidated.
+	// buildMu+built guards this reconstruction.
+	//
+	// file and wsDirs may be non-nil even if built == false, if they were copied
+	// from the previous workspace module version. In this case, they will be
+	// preserved if building fails.
+	buildMu  sync.Mutex
+	built    bool
+	buildErr error
+	file     *modfile.File
+	wsDirs   map[span.URI]struct{}
+}
+
+func newWorkspace(ctx context.Context, root span.URI, fs source.FileSource, experimental bool) (*workspace, error) {
+	if !experimental {
+		modFiles, err := getLegacyModules(ctx, root, fs)
+		if err != nil {
+			return nil, err
+		}
+		return &workspace{
+			root:         root,
+			modFiles:     modFiles,
+			moduleSource: legacyWorkspace,
+		}, nil
+	}
+	goplsModFH, err := fs.GetFile(ctx, goplsModURI(root))
+	if err != nil {
+		return nil, err
+	}
+	contents, err := goplsModFH.Read()
+	if err == nil {
+		file, modFiles, err := parseGoplsMod(root, goplsModFH.URI(), contents)
+		if err != nil {
+			return nil, err
+		}
+		return &workspace{
+			root:         root,
+			modFiles:     modFiles,
+			file:         file,
+			moduleSource: goplsModWorkspace,
+		}, nil
+	}
+	modFiles, err := findAllModules(ctx, root)
+	if err != nil {
+		return nil, err
+	}
+	return &workspace{
+		root:         root,
+		modFiles:     modFiles,
+		moduleSource: fileSystemWorkspace,
+	}, nil
+}
+
+func (wm *workspace) activeModFiles() map[span.URI]struct{} {
+	return wm.modFiles
+}
+
+// modFile gets the workspace modfile associated with this workspace,
+// computing it if it doesn't exist.
+//
+// A fileSource must be passed in to solve a chicken-egg problem: it is not
+// correct to pass in the snapshot file source to newWorkspace when
+// invalidating, because at the time these are called the snapshot is locked.
+// So we must pass it in later on when actually using the modFile.
+func (wm *workspace) modFile(ctx context.Context, fs source.FileSource) (*modfile.File, error) {
+	wm.build(ctx, fs)
+	return wm.file, wm.buildErr
+}
+
+func (wm *workspace) build(ctx context.Context, fs source.FileSource) {
+	wm.buildMu.Lock()
+	defer wm.buildMu.Unlock()
+
+	if wm.built {
+		return
+	}
+	// Building should never be cancelled. Since the workspace module is shared
+	// across multiple snapshots, doing so would put us in a bad state, and it
+	// would not be obvious to the user how to recover.
+	ctx = xcontext.Detach(ctx)
+
+	// If our module source is not gopls.mod, try to build the workspace module
+	// from modules. Fall back on the pre-existing mod file if parsing fails.
+	if wm.moduleSource != goplsModWorkspace {
+		file, err := buildWorkspaceModFile(ctx, wm.modFiles, fs)
+		switch {
+		case err == nil:
+			wm.file = file
+		case wm.file != nil:
+			// Parsing failed, but we have a previous file version.
+			event.Error(ctx, "building workspace mod file", err)
+		default:
+			// No file to fall back on.
+			wm.buildErr = err
+		}
+	}
+	if wm.file != nil {
+		wm.wsDirs = map[span.URI]struct{}{
+			wm.root: {},
+		}
+		for _, r := range wm.file.Replace {
+			// We may be replacing a module with a different version, not a path
+			// on disk.
+			if r.New.Version != "" {
+				continue
+			}
+			wm.wsDirs[span.URIFromPath(r.New.Path)] = struct{}{}
+		}
+	}
+	// Ensure that there is always at least the root dir.
+	if len(wm.wsDirs) == 0 {
+		wm.wsDirs = map[span.URI]struct{}{
+			wm.root: {},
+		}
+	}
+	wm.built = true
+}
+
+// dirs returns the workspace directories for the loaded modules.
+func (wm *workspace) dirs(ctx context.Context, fs source.FileSource) []span.URI {
+	wm.build(ctx, fs)
+	var dirs []span.URI
+	for d := range wm.wsDirs {
+		dirs = append(dirs, d)
+	}
+	sort.Slice(dirs, func(i, j int) bool {
+		return span.CompareURI(dirs[i], dirs[j]) < 0
+	})
+	return dirs
+}
+
+// invalidate returns a (possibly) new workspaceModule after invalidating
+// changedURIs. If wm is still valid in the presence of changedURIs, it returns
+// itself unmodified.
+func (wm *workspace) invalidate(ctx context.Context, changes map[span.URI]*fileChange) (*workspace, bool) {
+	// Prevent races to wm.modFile or wm.wsDirs below, if wm has not yet been
+	// built.
+	wm.buildMu.Lock()
+	defer wm.buildMu.Unlock()
+	// Any gopls.mod change is processed first, followed by go.mod changes, as
+	// changes to gopls.mod may affect the set of active go.mod files.
+	var (
+		// New values. We return a new workspace module if and only if modFiles is
+		// non-nil.
+		modFiles     map[span.URI]struct{}
+		moduleSource = wm.moduleSource
+		modFile      = wm.file
+		err          error
+	)
+	if wm.moduleSource == goplsModWorkspace {
+		// If we are currently reading the modfile from gopls.mod, we default to
+		// preserving it even if module metadata changes (which may be the case if
+		// a go.sum file changes).
+		modFile = wm.file
+	}
+	// First handle changes to the gopls.mod file.
+	if wm.moduleSource != legacyWorkspace {
+		// If gopls.mod has changed we need to either re-read it if it exists or
+		// walk the filesystem if it doesn't exist.
+		gmURI := goplsModURI(wm.root)
+		if change, ok := changes[gmURI]; ok {
+			if change.exists {
+				// Only invalidate if the gopls.mod actually parses. Otherwise, stick with the current gopls.mod
+				parsedFile, parsedModules, err := parseGoplsMod(wm.root, gmURI, change.content)
+				if err == nil {
+					modFile = parsedFile
+					moduleSource = goplsModWorkspace
+					modFiles = parsedModules
+				} else {
+					// Note that modFile is not invalidated here.
+					event.Error(ctx, "parsing gopls.mod", err)
+				}
+			} else {
+				// gopls.mod is deleted. search for modules again.
+				moduleSource = fileSystemWorkspace
+				modFiles, err = findAllModules(ctx, wm.root)
+				// the modFile is no longer valid.
+				if err != nil {
+					event.Error(ctx, "finding file system modules", err)
+				}
+				modFile = nil
+			}
+		}
+	}
+
+	// Next, handle go.mod changes that could affect our set of tracked modules.
+	// If we're reading our tracked modules from the gopls.mod, there's nothing
+	// to do here.
+	if wm.moduleSource != goplsModWorkspace {
+		for uri, change := range changes {
+			// If a go.mod file has changed, we may need to update the set of active
+			// modules.
+			if !isGoMod(uri) {
+				continue
+			}
+			if wm.moduleSource == legacyWorkspace && !equalURI(modURI(wm.root), uri) {
+				// Legacy mode only considers a module a workspace root.
+				continue
+			}
+			if !source.InDir(wm.root.Filename(), uri.Filename()) {
+				// Otherwise, the module must be contained within the workspace root.
+				continue
+			}
+			if modFiles == nil {
+				modFiles = make(map[span.URI]struct{})
+				for k := range wm.modFiles {
+					modFiles[k] = struct{}{}
+				}
+			}
+			if change.exists {
+				modFiles[uri] = struct{}{}
+			} else {
+				delete(modFiles, uri)
+			}
+		}
+	}
+	if modFiles != nil {
+		// Any change to modules triggers a new version.
+		return &workspace{
+			root:         wm.root,
+			moduleSource: moduleSource,
+			modFiles:     modFiles,
+			file:         modFile,
+			wsDirs:       wm.wsDirs,
+		}, true
+	}
+	// No change. Just return wm, since it is immutable.
+	return wm, false
+}
+
+func equalURI(left, right span.URI) bool {
+	return span.CompareURI(left, right) == 0
+}
+
+// goplsModURI returns the URI for the gopls.mod file contained in root.
+func goplsModURI(root span.URI) span.URI {
+	return span.URIFromPath(filepath.Join(root.Filename(), "gopls.mod"))
+}
+
+// modURI returns the URI for the go.mod file contained in root.
+func modURI(root span.URI) span.URI {
+	return span.URIFromPath(filepath.Join(root.Filename(), "go.mod"))
+}
+
+// isGoMod reports if uri is a go.mod file.
+func isGoMod(uri span.URI) bool {
+	return filepath.Base(uri.Filename()) == "go.mod"
+}
+
+// isGoMod reports if uri is a go.sum file.
+func isGoSum(uri span.URI) bool {
+	return filepath.Base(uri.Filename()) == "go.sum"
+}
+
+// fileExists reports if the file uri exists within source.
+func fileExists(ctx context.Context, uri span.URI, source source.FileSource) (bool, error) {
+	fh, err := source.GetFile(ctx, uri)
+	if err != nil {
+		return false, err
+	}
+	return fileHandleExists(fh)
+}
+
+// fileHandleExists reports if the file underlying fh actually exits.
+func fileHandleExists(fh source.FileHandle) (bool, error) {
+	_, err := fh.Read()
+	if err == nil {
+		return true, nil
+	}
+	if os.IsNotExist(err) {
+		return false, nil
+	}
+	return false, err
+}
+
+// TODO(rFindley): replace this (and similar) with a uripath package analogous
+// to filepath.
+func dirURI(uri span.URI) span.URI {
+	return span.URIFromPath(filepath.Dir(uri.Filename()))
+}
+
+// getLegacyModules returns a module set containing at most the root module.
+func getLegacyModules(ctx context.Context, root span.URI, fs source.FileSource) (map[span.URI]struct{}, error) {
+	uri := span.URIFromPath(filepath.Join(root.Filename(), "go.mod"))
+	modules := make(map[span.URI]struct{})
+	exists, err := fileExists(ctx, uri, fs)
+	if err != nil {
+		return nil, err
+	}
+	if exists {
+		modules[uri] = struct{}{}
+	}
+	return modules, nil
+}
+
+func parseGoplsMod(root, uri span.URI, contents []byte) (*modfile.File, map[span.URI]struct{}, error) {
+	modFile, err := modfile.Parse(uri.Filename(), contents, nil)
+	if err != nil {
+		return nil, nil, errors.Errorf("parsing gopls.mod: %w", err)
+	}
+	modFiles := make(map[span.URI]struct{})
+	for _, replace := range modFile.Replace {
+		if replace.New.Version != "" {
+			return nil, nil, errors.Errorf("gopls.mod: replaced module %q@%q must not have version", replace.New.Path, replace.New.Version)
+		}
+		dirFP := filepath.FromSlash(replace.New.Path)
+		if !filepath.IsAbs(dirFP) {
+			dirFP = filepath.Join(root.Filename(), dirFP)
+			// The resulting modfile must use absolute paths, so that it can be
+			// written to a temp directory.
+			replace.New.Path = dirFP
+		}
+		modURI := span.URIFromPath(filepath.Join(dirFP, "go.mod"))
+		modFiles[modURI] = struct{}{}
+	}
+	return modFile, modFiles, nil
+}
+
+// findAllModules recursively walks the root directory looking for go.mod
+// files, returning the set of modules it discovers.
+// TODO(rfindley): consider overlays.
+func findAllModules(ctx context.Context, root span.URI) (map[span.URI]struct{}, error) {
+	// Walk the view's folder to find all modules in the view.
+	modFiles := make(map[span.URI]struct{})
+	return modFiles, filepath.Walk(root.Filename(), func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			// Probably a permission error. Keep looking.
+			return filepath.SkipDir
+		}
+		// For any path that is not the workspace folder, check if the path
+		// would be ignored by the go command. Vendor directories also do not
+		// contain workspace modules.
+		if info.IsDir() && path != root.Filename() {
+			suffix := strings.TrimPrefix(path, root.Filename())
+			switch {
+			case checkIgnored(suffix),
+				strings.Contains(filepath.ToSlash(suffix), "/vendor/"):
+				return filepath.SkipDir
+			}
+		}
+		// We're only interested in go.mod files.
+		uri := span.URIFromPath(path)
+		if isGoMod(uri) {
+			modFiles[uri] = struct{}{}
+		}
+		return nil
+	})
+}
diff --git a/internal/lsp/cache/workspace_test.go b/internal/lsp/cache/workspace_test.go
new file mode 100644
index 0000000..7cd0ece
--- /dev/null
+++ b/internal/lsp/cache/workspace_test.go
@@ -0,0 +1,257 @@
+// 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 cache
+
+import (
+	"context"
+	"os"
+	"testing"
+
+	"golang.org/x/tools/internal/lsp/fake"
+	"golang.org/x/tools/internal/lsp/source"
+	"golang.org/x/tools/internal/span"
+)
+
+// osFileSource is a fileSource that just reads from the operating system.
+type osFileSource struct{}
+
+func (s osFileSource) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
+	fi, statErr := os.Stat(uri.Filename())
+	if statErr != nil {
+		return &fileHandle{
+			err: statErr,
+			uri: uri,
+		}, nil
+	}
+	fh, err := readFile(ctx, uri, fi.ModTime())
+	if err != nil {
+		return nil, err
+	}
+	return fh, nil
+}
+
+func TestWorkspaceModule(t *testing.T) {
+	tests := []struct {
+		desc           string
+		initial        string // txtar-encoded
+		legacyMode     bool
+		initialSource  workspaceSource
+		initialModules []string
+		initialDirs    []string
+		updates        map[string]string
+		finalSource    workspaceSource
+		finalModules   []string
+		finalDirs      []string
+	}{
+		{
+			desc: "legacy mode",
+			initial: `
+-- go.mod --
+module mod.com
+-- a/go.mod --
+module moda.com`,
+			legacyMode:     true,
+			initialModules: []string{"./go.mod"},
+			initialSource:  legacyWorkspace,
+			initialDirs:    []string{"."},
+		},
+		{
+			desc: "nested module",
+			initial: `
+-- go.mod --
+module mod.com
+-- a/go.mod --
+module moda.com`,
+			initialModules: []string{"./go.mod", "a/go.mod"},
+			initialSource:  fileSystemWorkspace,
+			initialDirs:    []string{".", "a"},
+		},
+		{
+			desc: "removing module",
+			initial: `
+-- a/go.mod --
+module moda.com
+-- b/go.mod --
+module modb.com`,
+			initialModules: []string{"a/go.mod", "b/go.mod"},
+			initialSource:  fileSystemWorkspace,
+			initialDirs:    []string{".", "a", "b"},
+			updates: map[string]string{
+				"gopls.mod": `module gopls-workspace
+
+require moda.com v0.0.0-goplsworkspace
+replace moda.com => $SANDBOX_WORKDIR/a`,
+			},
+			finalModules: []string{"a/go.mod"},
+			finalSource:  goplsModWorkspace,
+			finalDirs:    []string{".", "a"},
+		},
+		{
+			desc: "adding module",
+			initial: `
+-- gopls.mod --
+require moda.com v0.0.0-goplsworkspace
+replace moda.com => $SANDBOX_WORKDIR/a
+-- a/go.mod --
+module moda.com
+-- b/go.mod --
+module modb.com`,
+			initialModules: []string{"a/go.mod"},
+			initialSource:  goplsModWorkspace,
+			initialDirs:    []string{".", "a"},
+			updates: map[string]string{
+				"gopls.mod": `module gopls-workspace
+
+require moda.com v0.0.0-goplsworkspace
+require modb.com v0.0.0-goplsworkspace
+
+replace moda.com => $SANDBOX_WORKDIR/a
+replace modb.com => $SANDBOX_WORKDIR/b`,
+			},
+			finalModules: []string{"a/go.mod", "b/go.mod"},
+			finalSource:  goplsModWorkspace,
+			finalDirs:    []string{".", "a", "b"},
+		},
+		{
+			desc: "deleting gopls.mod",
+			initial: `
+-- gopls.mod --
+module gopls-workspace
+
+require moda.com v0.0.0-goplsworkspace
+replace moda.com => $SANDBOX_WORKDIR/a
+-- a/go.mod --
+module moda.com
+-- b/go.mod --
+module modb.com`,
+			initialModules: []string{"a/go.mod"},
+			initialSource:  goplsModWorkspace,
+			initialDirs:    []string{".", "a"},
+			updates: map[string]string{
+				"gopls.mod": "",
+			},
+			finalModules: []string{"a/go.mod", "b/go.mod"},
+			finalSource:  fileSystemWorkspace,
+			finalDirs:    []string{".", "a", "b"},
+		},
+		{
+			desc: "broken module parsing",
+			initial: `
+-- a/go.mod --
+module moda.com
+
+require gopls.test v0.0.0-goplsworkspace
+replace gopls.test => ../../gopls.test // (this path shouldn't matter)
+-- b/go.mod --
+module modb.com`,
+			initialModules: []string{"a/go.mod", "b/go.mod"},
+			initialSource:  fileSystemWorkspace,
+			initialDirs:    []string{".", "a", "b", "../gopls.test"},
+			updates: map[string]string{
+				"a/go.mod": `modul moda.com
+
+require gopls.test v0.0.0-goplsworkspace
+replace gopls.test => ../../gopls.test2`,
+			},
+			finalModules: []string{"a/go.mod", "b/go.mod"},
+			finalSource:  fileSystemWorkspace,
+			// finalDirs should be unchanged: we should preserve dirs in the presence
+			// of a broken modfile.
+			finalDirs: []string{".", "a", "b", "../gopls.test"},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			ctx := context.Background()
+			dir, err := fake.Tempdir(test.initial)
+			if err != nil {
+				t.Fatal(err)
+			}
+			defer os.RemoveAll(dir)
+			root := span.URIFromPath(dir)
+
+			fs := osFileSource{}
+			wm, err := newWorkspace(ctx, root, fs, !test.legacyMode)
+			if err != nil {
+				t.Fatal(err)
+			}
+			rel := fake.RelativeTo(dir)
+			checkWorkspaceModule(t, rel, wm, test.initialSource, test.initialModules)
+			gotDirs := wm.dirs(ctx, fs)
+			checkWorkspaceDirs(t, rel, gotDirs, test.initialDirs)
+			if test.updates != nil {
+				changes := make(map[span.URI]*fileChange)
+				for k, v := range test.updates {
+					if v == "" {
+						// for convenience, use this to signal a deletion. TODO: more doc
+						err := os.Remove(rel.AbsPath(k))
+						if err != nil {
+							t.Fatal(err)
+						}
+					} else {
+						fake.WriteFileData(k, []byte(v), rel)
+					}
+					uri := span.URIFromPath(rel.AbsPath(k))
+					fh, err := fs.GetFile(ctx, uri)
+					if err != nil {
+						t.Fatal(err)
+					}
+					content, err := fh.Read()
+					changes[uri] = &fileChange{
+						content:    content,
+						exists:     err == nil,
+						fileHandle: &closedFile{fh},
+					}
+				}
+				wm, _ := wm.invalidate(ctx, changes)
+				checkWorkspaceModule(t, rel, wm, test.finalSource, test.finalModules)
+				gotDirs := wm.dirs(ctx, fs)
+				checkWorkspaceDirs(t, rel, gotDirs, test.finalDirs)
+			}
+		})
+	}
+}
+
+func checkWorkspaceModule(t *testing.T, rel fake.RelativeTo, got *workspace, wantSource workspaceSource, want []string) {
+	t.Helper()
+	if got.moduleSource != wantSource {
+		t.Errorf("module source = %v, want %v", got.moduleSource, wantSource)
+	}
+	modules := make(map[span.URI]struct{})
+	for k := range got.activeModFiles() {
+		modules[k] = struct{}{}
+	}
+	for _, modPath := range want {
+		path := rel.AbsPath(modPath)
+		uri := span.URIFromPath(path)
+		if _, ok := modules[uri]; !ok {
+			t.Errorf("missing module %q", uri)
+		}
+		delete(modules, uri)
+	}
+	for remaining := range modules {
+		t.Errorf("unexpected module %q", remaining)
+	}
+}
+
+func checkWorkspaceDirs(t *testing.T, rel fake.RelativeTo, got []span.URI, want []string) {
+	t.Helper()
+	gotM := make(map[span.URI]bool)
+	for _, dir := range got {
+		gotM[dir] = true
+	}
+	for _, dir := range want {
+		path := rel.AbsPath(dir)
+		uri := span.URIFromPath(path)
+		if !gotM[uri] {
+			t.Errorf("missing dir %q", uri)
+		}
+		delete(gotM, uri)
+	}
+	for remaining := range gotM {
+		t.Errorf("unexpected dir %q", remaining)
+	}
+}
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index 7225ae8..5f669f3 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -281,10 +281,10 @@
 		ContentFormat: []protocol.MarkupKind{opts.PreferredContentFormat},
 	}
 	params.Capabilities.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport = opts.HierarchicalDocumentSymbolSupport
-	params.Capabilities.TextDocument.SemanticTokens = &protocol.SemanticTokensClientCapabilities{}
+	params.Capabilities.TextDocument.SemanticTokens = protocol.SemanticTokensClientCapabilities{}
 	params.Capabilities.TextDocument.SemanticTokens.Formats = []string{"relative"}
 	params.Capabilities.TextDocument.SemanticTokens.Requests.Range = true
-	params.Capabilities.TextDocument.SemanticTokens.Requests.Full.Delta = true
+	params.Capabilities.TextDocument.SemanticTokens.Requests.Full = true
 	params.Capabilities.TextDocument.SemanticTokens.TokenTypes = lsp.SemanticTypes()
 	params.Capabilities.TextDocument.SemanticTokens.TokenModifiers = lsp.SemanticModifiers()
 	params.InitializationOptions = map[string]interface{}{
diff --git a/internal/lsp/cmd/cmd_test.go b/internal/lsp/cmd/cmd_test.go
index 46a862a..29816c8 100644
--- a/internal/lsp/cmd/cmd_test.go
+++ b/internal/lsp/cmd/cmd_test.go
@@ -5,14 +5,9 @@
 package cmd_test
 
 import (
-	"fmt"
 	"os"
-	"path/filepath"
-	"regexp"
-	"runtime"
 	"testing"
 
-	"golang.org/x/tools/internal/lsp/cmd"
 	cmdtest "golang.org/x/tools/internal/lsp/cmd/test"
 	"golang.org/x/tools/internal/lsp/tests"
 	"golang.org/x/tools/internal/testenv"
@@ -26,31 +21,3 @@
 func TestCommandLine(t *testing.T) {
 	cmdtest.TestCommandLine(t, "../testdata", tests.DefaultOptions)
 }
-
-func TestDefinitionHelpExample(t *testing.T) {
-	// TODO: https://golang.org/issue/32794.
-	t.Skip()
-	if runtime.GOOS == "android" {
-		t.Skip("not all source files are available on android")
-	}
-	dir, err := os.Getwd()
-	if err != nil {
-		t.Errorf("could not get wd: %v", err)
-		return
-	}
-	ctx := tests.Context(t)
-	ts := cmdtest.NewTestServer(ctx, nil)
-	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 {.*}$`)
-	for _, query := range []string{
-		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, 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/semantictokens.go b/internal/lsp/cmd/semantictokens.go
index 0bcb487..f3c4b12 100644
--- a/internal/lsp/cmd/semantictokens.go
+++ b/internal/lsp/cmd/semantictokens.go
@@ -74,9 +74,8 @@
 }
 
 // Run performs the semtok on the files specified by args and prints the
-// results to stdout. PJW: fix this description
+// results to stdout in the format described above.
 func (c *semtok) Run(ctx context.Context, args ...string) error {
-	log.SetFlags(log.Lshortfile)
 	if len(args) != 1 {
 		return fmt.Errorf("expected one file name, got %d", len(args))
 	}
@@ -122,7 +121,6 @@
 		Content:   buf,
 		Converter: tc,
 	}
-	memo = lsp.SemanticMemo
 	err = decorate(file.uri.Filename(), resp.Data)
 	if err != nil {
 		return err
@@ -130,8 +128,6 @@
 	return nil
 }
 
-var memo *lsp.SemMemo
-
 type mark struct {
 	line, offset int // 1-based, from RangeSpan
 	len          int // bytes, not runes
@@ -218,8 +214,8 @@
 			line:   spn.Start().Line(),
 			offset: spn.Start().Column(),
 			len:    spn.End().Column() - spn.Start().Column(),
-			typ:    memo.Type(int(d[5*i+3])),
-			mods:   memo.Mods(int(d[5*i+4])),
+			typ:    lsp.SemType(int(d[5*i+3])),
+			mods:   lsp.SemMods(int(d[5*i+4])),
 		}
 		ans = append(ans, m)
 	}
diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go
index cb45045..b3a3ce3 100644
--- a/internal/lsp/code_action.go
+++ b/internal/lsp/code_action.go
@@ -14,6 +14,7 @@
 	"golang.org/x/tools/internal/event"
 	"golang.org/x/tools/internal/imports"
 	"golang.org/x/tools/internal/lsp/debug/tag"
+	"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"
@@ -69,16 +70,6 @@
 			}
 			codeActions = append(codeActions, modQuickFixes...)
 		}
-		if wanted[protocol.SourceOrganizeImports] {
-			action, err := goModTidy(ctx, snapshot, fh)
-			if source.IsNonFatalGoModError(err) {
-				return nil, nil
-			}
-			if err != nil {
-				return nil, err
-			}
-			codeActions = append(codeActions, *action)
-		}
 	case source.Go:
 		// Don't suggest fixes for generated files, since they are generally
 		// not useful and some editors may apply them automatically on save.
@@ -486,12 +477,12 @@
 			return nil, err
 		}
 	}
-	tidied, err := snapshot.ModTidy(ctx, modFH)
+	errors, err := mod.ErrorsForMod(ctx, snapshot, modFH)
 	if err != nil {
 		return nil, err
 	}
 	var quickFixes []protocol.CodeAction
-	for _, e := range tidied.Errors {
+	for _, e := range errors {
 		var diag *protocol.Diagnostic
 		for _, d := range diagnostics {
 			if sameDiagnostic(d, e) {
@@ -508,6 +499,7 @@
 				Kind:        protocol.QuickFix,
 				Diagnostics: []protocol.Diagnostic{*diag},
 				Edit:        protocol.WorkspaceEdit{},
+				Command:     fix.Command,
 			}
 			for uri, edits := range fix.Edits {
 				if uri != modFH.URI() {
@@ -523,6 +515,13 @@
 					Edits: edits,
 				})
 			}
+			if fix.Command != nil {
+				action.Command = &protocol.Command{
+					Command:   fix.Command.Command,
+					Title:     fix.Command.Title,
+					Arguments: fix.Command.Arguments,
+				}
+			}
 			quickFixes = append(quickFixes, action)
 		}
 	}
@@ -533,38 +532,6 @@
 	return d.Message == e.Message && protocol.CompareRange(d.Range, e.Range) == 0 && d.Source == e.Category
 }
 
-func goModTidy(ctx context.Context, snapshot source.Snapshot, fh source.VersionedFileHandle) (*protocol.CodeAction, error) {
-	tidied, err := snapshot.ModTidy(ctx, fh)
-	if err != nil {
-		return nil, err
-	}
-	left, err := fh.Read()
-	if err != nil {
-		return nil, err
-	}
-	right := tidied.TidiedContent
-	edits := snapshot.View().Options().ComputeEdits(fh.URI(), string(left), string(right))
-	protocolEdits, err := source.ToProtocolEdits(tidied.Parsed.Mapper, edits)
-	if err != nil {
-		return nil, err
-	}
-	return &protocol.CodeAction{
-		Title: "Tidy",
-		Kind:  protocol.SourceOrganizeImports,
-		Edit: protocol.WorkspaceEdit{
-			DocumentChanges: []protocol.TextDocumentEdit{{
-				TextDocument: protocol.VersionedTextDocumentIdentifier{
-					Version: fh.Version(),
-					TextDocumentIdentifier: protocol.TextDocumentIdentifier{
-						URI: protocol.URIFromSpanURI(fh.URI()),
-					},
-				},
-				Edits: protocolEdits,
-			}},
-		},
-	}, err
-}
-
 func goTest(ctx context.Context, snapshot source.Snapshot, uri span.URI, rng protocol.Range) ([]protocol.CodeAction, error) {
 	fh, err := snapshot.GetFile(ctx, uri)
 	if err != nil {
diff --git a/internal/lsp/command.go b/internal/lsp/command.go
index 959a2a5..a622329 100644
--- a/internal/lsp/command.go
+++ b/internal/lsp/command.go
@@ -14,6 +14,8 @@
 	"path/filepath"
 
 	"golang.org/x/tools/internal/event"
+	"golang.org/x/tools/internal/gocommand"
+	"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/span"
@@ -42,10 +44,6 @@
 	if !match {
 		return nil, fmt.Errorf("%s is not a supported command", command.ID())
 	}
-	title := command.Title
-	if title == "" {
-		title = command.Name
-	}
 	// Some commands require that all files are saved to disk. If we detect
 	// unsaved files, warn the user instead of running the commands.
 	unsaved := false
@@ -57,48 +55,39 @@
 	}
 	if unsaved {
 		switch params.Command {
-		case source.CommandTest.ID(), source.CommandGenerate.ID(), source.CommandToggleDetails.ID():
+		case source.CommandTest.ID(),
+			source.CommandGenerate.ID(),
+			source.CommandToggleDetails.ID(),
+			source.CommandAddDependency.ID(),
+			source.CommandUpgradeDependency.ID(),
+			source.CommandRemoveDependency.ID(),
+			source.CommandVendor.ID():
 			// TODO(PJW): for Toggle, not an error if it is being disabled
-			err := errors.New("unsaved files in the view")
-			s.showCommandError(ctx, title, err)
+			err := errors.New("all files must be saved first")
+			s.showCommandError(ctx, command.Title, err)
 			return nil, err
 		}
 	}
-	// If the command has a suggested fix function available, use it and apply
-	// the edits to the workspace.
-	if command.IsSuggestedFix() {
-		err := s.runSuggestedFixCommand(ctx, command, params.Arguments)
-		if err != nil {
-			s.showCommandError(ctx, title, err)
-		}
-		return nil, err
-	}
 	ctx, cancel := context.WithCancel(xcontext.Detach(ctx))
-	// Start progress prior to spinning off a goroutine specifically so that
-	// clients are aware of the work item before the command completes. This
-	// matters for regtests, where having a continuous thread of work is
-	// convenient for assertions.
-	work := s.progress.start(ctx, title, "Running...", params.WorkDoneToken, cancel)
-	if command.Synchronous {
-		return nil, s.runCommand(ctx, work, command, params.Arguments)
+
+	var work *workDone
+	// Don't show progress for suggested fixes. They should be quick.
+	if !command.IsSuggestedFix() {
+		// Start progress prior to spinning off a goroutine specifically so that
+		// clients are aware of the work item before the command completes. This
+		// matters for regtests, where having a continuous thread of work is
+		// convenient for assertions.
+		work = s.progress.start(ctx, command.Title, "Running...", params.WorkDoneToken, cancel)
 	}
-	go func() {
-		defer cancel()
-		err := s.runCommand(ctx, work, command, params.Arguments)
-		switch {
-		case errors.Is(err, context.Canceled):
-			work.end(title + ": canceled")
-		case err != nil:
-			event.Error(ctx, fmt.Sprintf("%s: command error", title), err)
-			work.end(title + ": failed")
-			// Show a message when work completes with error, because the progress end
-			// message is typically dismissed immediately by LSP clients.
-			s.showCommandError(ctx, title, err)
-		default:
-			work.end(command.ID() + ": completed")
-		}
-	}()
-	return nil, nil
+	if command.Async {
+		go func() {
+			defer cancel()
+			s.runCommand(ctx, work, command, params.Arguments)
+		}()
+		return nil, nil
+	}
+	defer cancel()
+	return nil, s.runCommand(ctx, work, command, params.Arguments)
 }
 
 func (s *Server) runSuggestedFixCommand(ctx context.Context, command *source.Command, args []json.RawMessage) error {
@@ -141,7 +130,26 @@
 	}
 }
 
-func (s *Server) runCommand(ctx context.Context, work *workDone, command *source.Command, args []json.RawMessage) error {
+func (s *Server) runCommand(ctx context.Context, work *workDone, command *source.Command, args []json.RawMessage) (err error) {
+	defer func() {
+		switch {
+		case errors.Is(err, context.Canceled):
+			work.end(command.Title + ": canceled")
+		case err != nil:
+			event.Error(ctx, fmt.Sprintf("%s: command error", command.Title), err)
+			work.end(command.Title + ": failed")
+			// Show a message when work completes with error, because the progress end
+			// message is typically dismissed immediately by LSP clients.
+			s.showCommandError(ctx, command.Title, err)
+		default:
+			work.end(command.ID() + ": completed")
+		}
+	}()
+	// If the command has a suggested fix function available, use it and apply
+	// the edits to the workspace.
+	if command.IsSuggestedFix() {
+		return s.runSuggestedFixCommand(ctx, command, args)
+	}
 	switch command {
 	case source.CommandTest:
 		var uri protocol.DocumentURI
@@ -188,14 +196,24 @@
 		if command == source.CommandVendor {
 			a = "vendor"
 		}
-		return s.directGoModCommand(ctx, uri, "mod", []string{a}...)
-	case source.CommandUpgradeDependency:
+		return s.directGoModCommand(ctx, uri, "mod", a)
+	case source.CommandAddDependency, source.CommandUpgradeDependency, source.CommandRemoveDependency:
 		var uri protocol.DocumentURI
 		var goCmdArgs []string
-		if err := source.UnmarshalArgs(args, &uri, &goCmdArgs); err != nil {
+		var addRequire bool
+		if err := source.UnmarshalArgs(args, &uri, &addRequire, &goCmdArgs); err != nil {
 			return err
 		}
-		return s.directGoModCommand(ctx, uri, "get", goCmdArgs...)
+		if addRequire {
+			// Using go get to create a new dependency results in an
+			// `// indirect` comment we may not want. The only way to avoid it
+			// is to add the require as direct first. Then we can use go get to
+			// update go.sum and tidy up.
+			if err := s.directGoModCommand(ctx, uri, "mod", append([]string{"edit", "-require"}, goCmdArgs...)...); err != nil {
+				return err
+			}
+		}
+		return s.directGoModCommand(ctx, uri, "get", append([]string{"-d"}, goCmdArgs...)...)
 	case source.CommandToggleDetails:
 		var fileURI span.URI
 		if err := source.UnmarshalArgs(args, &fileURI); err != nil {
@@ -203,10 +221,10 @@
 		}
 		pkgDir := span.URIFromPath(filepath.Dir(fileURI.Filename()))
 		s.gcOptimizationDetailsMu.Lock()
-		if _, ok := s.gcOptimizatonDetails[pkgDir]; ok {
-			delete(s.gcOptimizatonDetails, pkgDir)
+		if _, ok := s.gcOptimizationDetails[pkgDir]; ok {
+			delete(s.gcOptimizationDetails, pkgDir)
 		} else {
-			s.gcOptimizatonDetails[pkgDir] = struct{}{}
+			s.gcOptimizationDetails[pkgDir] = struct{}{}
 		}
 		s.gcOptimizationDetailsMu.Unlock()
 		// need to recompute diagnostics.
@@ -239,7 +257,7 @@
 		}
 		snapshot, release := v.Snapshot(ctx)
 		defer release()
-		modFile, err := snapshot.BuildWorkspaceModFile(ctx)
+		modFile, err := cache.BuildGoplsMod(ctx, v.Folder(), snapshot)
 		if err != nil {
 			return errors.Errorf("getting workspace mod file: %w", err)
 		}
@@ -262,10 +280,14 @@
 	if err != nil {
 		return err
 	}
-	wdir := filepath.Dir(uri.SpanURI().Filename())
 	snapshot, release := view.Snapshot(ctx)
 	defer release()
-	return snapshot.RunGoCommandDirect(ctx, wdir, verb, args)
+	_, err = snapshot.RunGoCommandDirect(ctx, source.UpdateUserModFile, &gocommand.Invocation{
+		Verb:       verb,
+		Args:       args,
+		WorkingDir: filepath.Dir(uri.SpanURI().Filename()),
+	})
+	return err
 }
 
 func (s *Server) runTests(ctx context.Context, snapshot source.Snapshot, uri protocol.DocumentURI, work *workDone, tests, benchmarks []string) error {
@@ -283,13 +305,15 @@
 	ew := &eventWriter{ctx: ctx, operation: "test"}
 	out := io.MultiWriter(ew, workDoneWriter{work}, buf)
 
-	wdir := filepath.Dir(uri.SpanURI().Filename())
-
 	// Run `go test -run Func` on each test.
 	var failedTests int
 	for _, funcName := range tests {
-		args := []string{pkgPath, "-v", "-count=1", "-run", fmt.Sprintf("^%s$", funcName)}
-		if err := snapshot.RunGoCommandPiped(ctx, wdir, "test", args, out, out); err != nil {
+		inv := &gocommand.Invocation{
+			Verb:       "test",
+			Args:       []string{pkgPath, "-v", "-count=1", "-run", fmt.Sprintf("^%s$", funcName)},
+			WorkingDir: filepath.Dir(uri.SpanURI().Filename()),
+		}
+		if err := snapshot.RunGoCommandPiped(ctx, source.Normal, inv, out, out); err != nil {
 			if errors.Is(err, context.Canceled) {
 				return err
 			}
@@ -300,8 +324,12 @@
 	// Run `go test -run=^$ -bench Func` on each test.
 	var failedBenchmarks int
 	for _, funcName := range benchmarks {
-		args := []string{pkgPath, "-v", "-run=^$", "-bench", fmt.Sprintf("^%s$", funcName)}
-		if err := snapshot.RunGoCommandPiped(ctx, wdir, "test", args, out, out); err != nil {
+		inv := &gocommand.Invocation{
+			Verb:       "test",
+			Args:       []string{pkgPath, "-v", "-run=^$", "-bench", fmt.Sprintf("^%s$", funcName)},
+			WorkingDir: filepath.Dir(uri.SpanURI().Filename()),
+		}
+		if err := snapshot.RunGoCommandPiped(ctx, source.Normal, inv, out, out); err != nil {
 			if errors.Is(err, context.Canceled) {
 				return err
 			}
@@ -344,16 +372,19 @@
 	defer cancel()
 
 	er := &eventWriter{ctx: ctx, operation: "generate"}
-	args := []string{"-x"}
+
 	pattern := "."
 	if recursive {
 		pattern = "..."
 	}
-	args = append(args, pattern)
 
+	inv := &gocommand.Invocation{
+		Verb:       "generate",
+		Args:       []string{"-x", pattern},
+		WorkingDir: dir.Filename(),
+	}
 	stderr := io.MultiWriter(er, workDoneWriter{work})
-
-	if err := snapshot.RunGoCommandPiped(ctx, dir.Filename(), "generate", args, er, stderr); err != nil {
+	if err := snapshot.RunGoCommandPiped(ctx, source.Normal, inv, er, stderr); err != nil {
 		return err
 	}
 	return nil
diff --git a/internal/lsp/debounce_test.go b/internal/lsp/debounce_test.go
index a06af35..841b780 100644
--- a/internal/lsp/debounce_test.go
+++ b/internal/lsp/debounce_test.go
@@ -63,7 +63,7 @@
 			for i, e := range test.events {
 				wg.Add(1)
 				go func(e *event) {
-					d.debounce(e.key, e.order, 100*time.Millisecond, func() {
+					d.debounce(e.key, e.order, 500*time.Millisecond, func() {
 						e.fired = true
 					})
 					wg.Done()
diff --git a/internal/lsp/debug/info.1.11.go b/internal/lsp/debug/info.1.11.go
deleted file mode 100644
index 0dea1e9..0000000
--- a/internal/lsp/debug/info.1.11.go
+++ /dev/null
@@ -1,16 +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.
-
-// +build !go1.12
-
-package debug
-
-import (
-	"fmt"
-	"io"
-)
-
-func printBuildInfo(w io.Writer, verbose bool, mode PrintMode) {
-	fmt.Fprintf(w, "version %s, built in $GOPATH mode\n", Version)
-}
diff --git a/internal/lsp/debug/info.1.12.go b/internal/lsp/debug/info.1.12.go
deleted file mode 100644
index e8bae36..0000000
--- a/internal/lsp/debug/info.1.12.go
+++ /dev/null
@@ -1,38 +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.
-
-// +build go1.12
-
-package debug
-
-import (
-	"fmt"
-	"io"
-	"runtime/debug"
-)
-
-func printBuildInfo(w io.Writer, verbose bool, mode PrintMode) {
-	if info, ok := debug.ReadBuildInfo(); ok {
-		fmt.Fprintf(w, "%v %v\n", info.Path, Version)
-		printModuleInfo(w, &info.Main, mode)
-		if verbose {
-			for _, dep := range info.Deps {
-				printModuleInfo(w, dep, mode)
-			}
-		}
-	} else {
-		fmt.Fprintf(w, "version %s, built in $GOPATH mode\n", Version)
-	}
-}
-
-func printModuleInfo(w io.Writer, m *debug.Module, mode PrintMode) {
-	fmt.Fprintf(w, "    %s@%s", m.Path, m.Version)
-	if m.Sum != "" {
-		fmt.Fprintf(w, " %s", m.Sum)
-	}
-	if m.Replace != nil {
-		fmt.Fprintf(w, " => %v", m.Replace.Path)
-	}
-	fmt.Fprintf(w, "\n")
-}
diff --git a/internal/lsp/debug/info.go b/internal/lsp/debug/info.go
index c7770d1..53f5c11 100644
--- a/internal/lsp/debug/info.go
+++ b/internal/lsp/debug/info.go
@@ -9,6 +9,7 @@
 	"context"
 	"fmt"
 	"io"
+	"runtime/debug"
 	"strings"
 )
 
@@ -21,7 +22,72 @@
 )
 
 // Version is a manually-updated mechanism for tracking versions.
-var Version = "v0.5.2"
+var Version = "v0.5.3"
+
+// ServerVersion is the format used by gopls to report its version to the
+// client. This format is structured so that the client can parse it easily.
+type ServerVersion struct {
+	Module
+	Deps []*Module `json:"deps,omitempty"`
+}
+
+type Module struct {
+	ModuleVersion
+	Replace *ModuleVersion `json:"replace,omitempty"`
+}
+
+type ModuleVersion struct {
+	Path    string `json:"path,omitempty"`
+	Version string `json:"version,omitempty"`
+	Sum     string `json:"sum,omitempty"`
+}
+
+// VersionInfo returns the build info for the gopls process. If it was not
+// built in module mode, we return a GOPATH-specific message with the
+// hardcoded version.
+func VersionInfo() *ServerVersion {
+	if info, ok := debug.ReadBuildInfo(); ok {
+		return getVersion(info)
+	}
+	path := "gopls, built in GOPATH mode"
+	return &ServerVersion{
+		Module: Module{
+			ModuleVersion: ModuleVersion{
+				Path:    path,
+				Version: Version,
+			},
+		},
+	}
+}
+
+func getVersion(info *debug.BuildInfo) *ServerVersion {
+	serverVersion := ServerVersion{
+		Module: Module{
+			ModuleVersion: ModuleVersion{
+				Path:    info.Main.Path,
+				Version: info.Main.Version,
+				Sum:     info.Main.Sum,
+			},
+		},
+	}
+	for _, d := range info.Deps {
+		m := &Module{
+			ModuleVersion: ModuleVersion{
+				Path:    d.Path,
+				Version: d.Version,
+				Sum:     d.Sum,
+			},
+		}
+		if d.Replace != nil {
+			m.Replace = &ModuleVersion{
+				Path:    d.Replace.Path,
+				Version: d.Replace.Version,
+			}
+		}
+		serverVersion.Deps = append(serverVersion.Deps, m)
+	}
+	return &serverVersion
+}
 
 // PrintServerInfo writes HTML debug info to w for the Instance.
 func (i *Instance) PrintServerInfo(ctx context.Context, w io.Writer) {
@@ -39,12 +105,13 @@
 // specified by mode. verbose controls whether additional information is
 // written, including section headers.
 func PrintVersionInfo(ctx context.Context, w io.Writer, verbose bool, mode PrintMode) {
+	info := VersionInfo()
 	if !verbose {
-		printBuildInfo(w, false, mode)
+		printBuildInfo(w, info, false, mode)
 		return
 	}
 	section(w, mode, "Build info", func() {
-		printBuildInfo(w, true, mode)
+		printBuildInfo(w, info, true, mode)
 	})
 }
 
@@ -64,3 +131,25 @@
 		fmt.Fprint(w, "</pre>\n")
 	}
 }
+
+func printBuildInfo(w io.Writer, info *ServerVersion, verbose bool, mode PrintMode) {
+	fmt.Fprintf(w, "%v %v\n", info.Path, Version)
+	printModuleInfo(w, &info.Module, mode)
+	if !verbose {
+		return
+	}
+	for _, dep := range info.Deps {
+		printModuleInfo(w, dep, mode)
+	}
+}
+
+func printModuleInfo(w io.Writer, m *Module, mode PrintMode) {
+	fmt.Fprintf(w, "    %s@%s", m.Path, m.Version)
+	if m.Sum != "" {
+		fmt.Fprintf(w, " %s", m.Sum)
+	}
+	if m.Replace != nil {
+		fmt.Fprintf(w, " => %v", m.Replace.Path)
+	}
+	fmt.Fprintf(w, "\n")
+}
diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go
index d0dcb27..f878108 100644
--- a/internal/lsp/diagnostics.go
+++ b/internal/lsp/diagnostics.go
@@ -26,8 +26,8 @@
 // idWithAnalysis is used to track if the diagnostics for a given file were
 // computed with analyses.
 type idWithAnalysis struct {
-	id           source.VersionedFileIdentity
-	withAnalysis bool
+	id              source.VersionedFileIdentity
+	includeAnalysis bool
 }
 
 // A reportSet collects diagnostics for publication, sorting them by file and
@@ -38,15 +38,15 @@
 	reports map[idWithAnalysis]map[string]*source.Diagnostic
 }
 
-func (s *reportSet) add(id source.VersionedFileIdentity, withAnalysis bool, diags ...*source.Diagnostic) {
+func (s *reportSet) add(id source.VersionedFileIdentity, includeAnalysis bool, diags ...*source.Diagnostic) {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 	if s.reports == nil {
 		s.reports = make(map[idWithAnalysis]map[string]*source.Diagnostic)
 	}
 	key := idWithAnalysis{
-		id:           id,
-		withAnalysis: withAnalysis,
+		id:              id,
+		includeAnalysis: includeAnalysis,
 	}
 	if _, ok := s.reports[key]; !ok {
 		s.reports[key] = map[string]*source.Diagnostic{}
@@ -79,7 +79,7 @@
 		// If a view has been created or the configuration changed, warn the user.
 		s.client.ShowMessage(ctx, shows)
 	}
-	s.publishReports(ctx, snapshot, reports)
+	s.publishReports(ctx, snapshot, reports, false)
 }
 
 func (s *Server) diagnoseSnapshot(snapshot source.Snapshot, changedURIs []span.URI) {
@@ -99,17 +99,17 @@
 				event.Error(ctx, "diagnosing changed files", err)
 			}
 		}
-		s.publishReports(ctx, snapshot, reports)
+		s.publishReports(ctx, snapshot, reports, true)
 		s.debouncer.debounce(snapshot.View().Name(), snapshot.ID(), delay, func() {
 			reports, _ := s.diagnose(ctx, snapshot, false)
-			s.publishReports(ctx, snapshot, reports)
+			s.publishReports(ctx, snapshot, reports, false)
 		})
 		return
 	}
 
 	// Ignore possible workspace configuration warnings in the normal flow.
 	reports, _ := s.diagnose(ctx, snapshot, false)
-	s.publishReports(ctx, snapshot, reports)
+	s.publishReports(ctx, snapshot, reports, false)
 }
 
 func (s *Server) diagnoseChangedFiles(ctx context.Context, snapshot source.Snapshot, uris []span.URI) (*reportSet, error) {
@@ -215,16 +215,16 @@
 		go func(pkg source.Package) {
 			defer wg.Done()
 
-			withAnalysis := alwaysAnalyze // only run analyses for packages with open files
-			var gcDetailsDir span.URI     // find the package's optimization details, if available
+			includeAnalysis := alwaysAnalyze // only run analyses for packages with open files
+			var gcDetailsDir span.URI        // find the package's optimization details, if available
 			for _, pgf := range pkg.CompiledGoFiles() {
 				if snapshot.IsOpen(pgf.URI) {
-					withAnalysis = true
+					includeAnalysis = true
 				}
 				if gcDetailsDir == "" {
 					dirURI := span.URIFromPath(filepath.Dir(pgf.URI.Filename()))
 					s.gcOptimizationDetailsMu.Lock()
-					_, ok := s.gcOptimizatonDetails[dirURI]
+					_, ok := s.gcOptimizationDetails[dirURI]
 					s.gcOptimizationDetailsMu.Unlock()
 					if ok {
 						gcDetailsDir = dirURI
@@ -232,7 +232,7 @@
 				}
 			}
 
-			pkgReports, warn, err := source.Diagnostics(ctx, snapshot, pkg, withAnalysis)
+			pkgReports, warn, err := source.Diagnostics(ctx, snapshot, pkg, includeAnalysis)
 
 			// Check if might want to warn the user about their build configuration.
 			// Our caller decides whether to send the message.
@@ -251,7 +251,7 @@
 
 			// Add all reports to the global map, checking for duplicates.
 			for id, diags := range pkgReports {
-				reports.add(id, withAnalysis, diags...)
+				reports.add(id, includeAnalysis, diags...)
 			}
 			// If gc optimization details are available, add them to the
 			// diagnostic reports.
@@ -261,7 +261,7 @@
 					event.Error(ctx, "warning: gc details", err, tag.Snapshot.Of(snapshot.ID()))
 				}
 				for id, diags := range gcReports {
-					reports.add(id, withAnalysis, diags...)
+					reports.add(id, includeAnalysis, diags...)
 				}
 			}
 		}(pkg)
@@ -274,10 +274,10 @@
 			// Check if we already have diagnostic reports for the given file,
 			// meaning that we have already seen its package.
 			var seen bool
-			for _, withAnalysis := range []bool{true, false} {
+			for _, includeAnalysis := range []bool{true, false} {
 				_, ok := reports.reports[idWithAnalysis{
-					id:           o.VersionedFileIdentity(),
-					withAnalysis: withAnalysis,
+					id:              o.VersionedFileIdentity(),
+					includeAnalysis: includeAnalysis,
 				}]
 				seen = seen || ok
 			}
@@ -344,12 +344,12 @@
 		if err != nil {
 			return err
 		}
-		reports.add(fh.VersionedFileIdentity(), false, diagnostic)
+		reports.add(fh.VersionedFileIdentity(), true, diagnostic)
 	}
 	return nil
 }
 
-func (s *Server) publishReports(ctx context.Context, snapshot source.Snapshot, reports *reportSet) {
+func (s *Server) publishReports(ctx context.Context, snapshot source.Snapshot, reports *reportSet, isFirstPass bool) {
 	// Check for context cancellation before publishing diagnostics.
 	if ctx.Err() != nil || reports == nil {
 		return
@@ -370,10 +370,10 @@
 		}
 		source.SortDiagnostics(diagnostics)
 		toSend := sentDiagnostics{
-			id:           key.id,
-			sorted:       diagnostics,
-			withAnalysis: key.withAnalysis,
-			snapshotID:   snapshot.ID(),
+			id:              key.id,
+			sorted:          diagnostics,
+			includeAnalysis: key.includeAnalysis,
+			snapshotID:      snapshot.ID(),
 		}
 
 		// We use the zero values if this is an unknown file.
@@ -382,10 +382,11 @@
 		// Snapshot IDs are always increasing, so we use them instead of file
 		// versions to create the correct order for diagnostics.
 
-		// If we've already delivered diagnostics for a future snapshot for this file,
-		// do not deliver them.
+		// If we've already delivered diagnostics for a future snapshot for
+		// this file, do not deliver them.
 		if delivered.snapshotID > toSend.snapshotID {
-			// Do not update the delivered map since it already contains newer diagnostics.
+			// Do not update the delivered map since it already contains newer
+			// diagnostics.
 			continue
 		}
 
@@ -399,10 +400,18 @@
 		// If we've already delivered diagnostics for this file, at this
 		// snapshot, with analyses, do not send diagnostics without analyses.
 		if delivered.snapshotID == toSend.snapshotID && delivered.id == toSend.id &&
-			delivered.withAnalysis && !toSend.withAnalysis {
+			delivered.includeAnalysis && !toSend.includeAnalysis {
 			// Do not update the delivered map since it already contains better diagnostics.
 			continue
 		}
+
+		// If we've previously delivered non-empty diagnostics and this is a
+		// first diagnostic pass, wait for a subsequent pass to complete before
+		// sending empty diagnostics to avoid flickering diagnostics.
+		if isFirstPass && delivered.includeAnalysis && !toSend.includeAnalysis && len(toSend.sorted) == 0 {
+			continue
+		}
+
 		if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
 			Diagnostics: toProtocolDiagnostics(diagnostics),
 			URI:         protocol.URIFromSpanURI(key.id.URI),
@@ -469,55 +478,5 @@
 		hasGo = true
 		return errors.New("done")
 	})
-	if !hasGo {
-		return true
-	}
-
-	// All other workarounds are for errors associated with modules.
-	if len(snapshot.ModFiles()) == 0 {
-		return false
-	}
-
-	switch loadErr {
-	case source.InconsistentVendoring:
-		item, err := s.client.ShowMessageRequest(ctx, &protocol.ShowMessageRequestParams{
-			Type: protocol.Error,
-			Message: `Inconsistent vendoring detected. Please re-run "go mod vendor".
-See https://github.com/golang/go/issues/39164 for more detail on this issue.`,
-			Actions: []protocol.MessageActionItem{
-				{Title: "go mod vendor"},
-			},
-		})
-		// If the user closes the pop-up, don't show them further errors.
-		if item == nil {
-			return true
-		}
-		if err != nil {
-			event.Error(ctx, "go mod vendor ShowMessageRequest failed", err, tag.Directory.Of(snapshot.View().Folder().Filename()))
-			return true
-		}
-		// Right now, we don't have a good way of mapping the error message
-		// to a specific module, so this will re-run `go mod vendor` in every
-		// known module with a vendor directory.
-		// TODO(rstambler): Only re-run `go mod vendor` in the relevant module.
-		for _, uri := range snapshot.ModFiles() {
-			// Check that there is a vendor directory in the module before
-			// running `go mod vendor`.
-			if info, _ := os.Stat(filepath.Join(filepath.Dir(uri.Filename()), "vendor")); info == nil {
-				continue
-			}
-			if err := s.directGoModCommand(ctx, protocol.URIFromSpanURI(uri), "mod", []string{"vendor"}...); err != nil {
-				if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
-					Type:    protocol.Error,
-					Message: fmt.Sprintf(`"go mod vendor" failed with %v`, err),
-				}); err != nil {
-					if err != nil {
-						event.Error(ctx, "go mod vendor ShowMessage failed", err, tag.Directory.Of(snapshot.View().Folder().Filename()))
-					}
-				}
-			}
-		}
-		return true
-	}
-	return false
+	return !hasGo
 }
diff --git a/internal/lsp/fake/editor.go b/internal/lsp/fake/editor.go
index dba8997..3cc1c06 100644
--- a/internal/lsp/fake/editor.go
+++ b/internal/lsp/fake/editor.go
@@ -8,6 +8,7 @@
 	"bufio"
 	"context"
 	"fmt"
+	"os"
 	"path/filepath"
 	"regexp"
 	"strings"
@@ -46,6 +47,7 @@
 	version int
 	path    string
 	content []string
+	dirty   bool
 }
 
 func (b buffer) text() string {
@@ -89,6 +91,10 @@
 	// AllExperiments sets the "allExperiments" configuration, which enables
 	// all of gopls's opt-in settings.
 	AllExperiments bool
+
+	// Whether to send the current process ID, for testing data that is joined to
+	// the PID. This can only be set by one test.
+	SendPID bool
 }
 
 // NewEditor Creates a new Editor.
@@ -232,6 +238,9 @@
 	params.Capabilities.Window.WorkDoneProgress = true
 	// TODO: set client capabilities
 	params.InitializationOptions = e.configuration()
+	if e.Config.SendPID {
+		params.ProcessID = float64(os.Getpid())
+	}
 
 	// This is a bit of a hack, since the fake editor doesn't actually support
 	// watching changed files that match a specific glob pattern. However, the
@@ -262,10 +271,28 @@
 	if e.Server == nil {
 		return
 	}
+	e.mu.Lock()
 	var lspevts []protocol.FileEvent
 	for _, evt := range evts {
+		// Always send an on-disk change, even for events that seem useless
+		// because they're shadowed by an open buffer.
 		lspevts = append(lspevts, evt.ProtocolEvent)
+
+		if buf, ok := e.buffers[evt.Path]; ok {
+			// Following VS Code, don't honor deletions or changes to dirty buffers.
+			if buf.dirty || evt.ProtocolEvent.Type == protocol.Deleted {
+				continue
+			}
+
+			content, err := e.sandbox.Workdir.ReadFile(evt.Path)
+			if err != nil {
+				continue // A race with some other operation.
+			}
+			// During shutdown, this call will fail. Ignore the error.
+			_ = e.setBufferContentLocked(ctx, evt.Path, false, strings.Split(content, "\n"), nil)
+		}
 	}
+	e.mu.Unlock()
 	e.Server.DidChangeWatchedFiles(ctx, &protocol.DidChangeWatchedFilesParams{
 		Changes: lspevts,
 	})
@@ -277,15 +304,7 @@
 	if err != nil {
 		return err
 	}
-	return e.CreateBuffer(ctx, path, content)
-}
-
-func newBuffer(path, content string) buffer {
-	return buffer{
-		version: 1,
-		path:    path,
-		content: strings.Split(content, "\n"),
-	}
+	return e.createBuffer(ctx, path, false, content)
 }
 
 func textDocumentItem(wd *Workdir, buf buffer) protocol.TextDocumentItem {
@@ -306,7 +325,16 @@
 // CreateBuffer creates a new unsaved buffer corresponding to the workdir path,
 // containing the given textual content.
 func (e *Editor) CreateBuffer(ctx context.Context, path, content string) error {
-	buf := newBuffer(path, content)
+	return e.createBuffer(ctx, path, true, content)
+}
+
+func (e *Editor) createBuffer(ctx context.Context, path string, dirty bool, content string) error {
+	buf := buffer{
+		version: 1,
+		path:    path,
+		content: strings.Split(content, "\n"),
+		dirty:   dirty,
+	}
 	e.mu.Lock()
 	e.buffers[path] = buf
 	item := textDocumentItem(e.sandbox.Workdir, buf)
@@ -388,6 +416,12 @@
 	if err := e.sandbox.Workdir.WriteFile(ctx, path, content); err != nil {
 		return errors.Errorf("writing %q: %w", path, err)
 	}
+
+	e.mu.Lock()
+	buf.dirty = false
+	e.buffers[path] = buf
+	e.mu.Unlock()
+
 	if e.Server != nil {
 		params := &protocol.DidSaveTextDocumentParams{
 			TextDocument: protocol.VersionedTextDocumentIdentifier{
@@ -522,6 +556,13 @@
 	return e.editBufferLocked(ctx, path, edits)
 }
 
+func (e *Editor) SetBufferContent(ctx context.Context, path, content string) error {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+	lines := strings.Split(content, "\n")
+	return e.setBufferContentLocked(ctx, path, true, lines, nil)
+}
+
 // BufferText returns the content of the buffer with the given name.
 func (e *Editor) BufferText(name string) string {
 	e.mu.Lock()
@@ -542,24 +583,29 @@
 	if !ok {
 		return fmt.Errorf("unknown buffer %q", path)
 	}
-	var (
-		content = make([]string, len(buf.content))
-		err     error
-		evts    []protocol.TextDocumentContentChangeEvent
-	)
+	content := make([]string, len(buf.content))
 	copy(content, buf.content)
-	content, err = editContent(content, edits)
+	content, err := editContent(content, edits)
 	if err != nil {
 		return err
 	}
+	return e.setBufferContentLocked(ctx, path, true, content, edits)
+}
 
+func (e *Editor) setBufferContentLocked(ctx context.Context, path string, dirty bool, content []string, fromEdits []Edit) error {
+	buf, ok := e.buffers[path]
+	if !ok {
+		return fmt.Errorf("unknown buffer %q", path)
+	}
 	buf.content = content
 	buf.version++
+	buf.dirty = dirty
 	e.buffers[path] = buf
 	// A simple heuristic: if there is only one edit, send it incrementally.
 	// Otherwise, send the entire content.
-	if len(edits) == 1 {
-		evts = append(evts, edits[0].toProtocolChangeEvent())
+	var evts []protocol.TextDocumentContentChangeEvent
+	if len(fromEdits) == 1 {
+		evts = append(evts, fromEdits[0].toProtocolChangeEvent())
 	} else {
 		evts = append(evts, protocol.TextDocumentContentChangeEvent{
 			Text: buf.text(),
@@ -720,7 +766,16 @@
 	if !match {
 		return nil, fmt.Errorf("unsupported command %q", params.Command)
 	}
-	return e.Server.ExecuteCommand(ctx, params)
+	result, err := e.Server.ExecuteCommand(ctx, params)
+	if err != nil {
+		return nil, err
+	}
+	// Some commands use the go command, which writes directly to disk.
+	// For convenience, check for those changes.
+	if err := e.sandbox.Workdir.CheckForFileChanges(ctx); err != nil {
+		return nil, err
+	}
+	return result, nil
 }
 
 func convertEdits(protocolEdits []protocol.TextEdit) []Edit {
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index a6de385..620140c 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -7,6 +7,7 @@
 import (
 	"bytes"
 	"context"
+	"encoding/json"
 	"fmt"
 	"io"
 	"os"
@@ -33,6 +34,7 @@
 	s.state = serverInitializing
 	s.stateMu.Unlock()
 
+	s.clientPID = int(params.ProcessID)
 	s.progress.supportsWorkDoneProgress = params.Capabilities.Window.WorkDoneProgress
 
 	options := s.session.Options()
@@ -82,13 +84,11 @@
 		}
 	}
 
-	if st := params.Capabilities.TextDocument.SemanticTokens; st != nil {
-		rememberToks(st.TokenTypes, st.TokenModifiers)
+	goplsVersion, err := json.Marshal(debug.VersionInfo())
+	if err != nil {
+		return nil, err
 	}
 
-	goplsVer := &bytes.Buffer{}
-	debug.PrintVersionInfo(ctx, goplsVer, true, debug.PlainText)
-
 	return &protocol.InitializeResult{
 		Capabilities: protocol.ServerCapabilities{
 			CallHierarchyProvider: true,
@@ -133,7 +133,7 @@
 			Version string `json:"version,omitempty"`
 		}{
 			Name:    "gopls",
-			Version: goplsVer.String(),
+			Version: string(goplsVersion),
 		},
 	}, nil
 }
@@ -199,6 +199,8 @@
 		}()
 	}
 	dirsToWatch := map[span.URI]struct{}{}
+	// Only one view gets to have a workspace.
+	assignedWorkspace := false
 	for _, folder := range folders {
 		uri := span.URIFromURI(folder.URI)
 		// Ignore non-file URIs.
@@ -206,7 +208,22 @@
 			continue
 		}
 		work := s.progress.start(ctx, "Setting up workspace", "Loading packages...", nil, nil)
-		snapshot, release, err := s.addView(ctx, folder.Name, uri)
+		var workspaceURI span.URI = ""
+		if !assignedWorkspace && s.clientPID != 0 {
+			// For quick-and-dirty testing, set the temp workspace file to
+			// $TMPDIR/gopls-<client PID>.workspace.
+			//
+			// This has a couple limitations:
+			//  + If there are multiple workspace roots, only the first one gets
+			//    written to this dir (and the client has no way to know precisely
+			//    which one).
+			//  + If a single client PID spawns multiple gopls sessions, they will
+			//    clobber eachother's temp workspace.
+			wsdir := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.workspace", s.clientPID))
+			workspaceURI = span.URIFromPath(wsdir)
+			assignedWorkspace = true
+		}
+		snapshot, release, err := s.addView(ctx, folder.Name, uri, workspaceURI)
 		if err != nil {
 			viewErrors[uri] = err
 			work.end(fmt.Sprintf("Error loading packages: %s", err))
@@ -351,13 +368,20 @@
 	}}
 	for dir := range dirs {
 		filename := dir.Filename()
+
 		// If the directory is within a workspace folder, we're already
 		// watching it via the relative path above.
+		var matched bool
 		for _, view := range s.session.Views() {
-			if isSubdirectory(view.Folder().Filename(), filename) {
-				continue
+			if source.InDir(view.Folder().Filename(), filename) {
+				matched = true
+				break
 			}
 		}
+		if matched {
+			continue
+		}
+
 		// If microsoft/vscode#100870 is resolved before
 		// microsoft/vscode#104387, we will need a work-around for Windows
 		// drive letter casing.
diff --git a/internal/lsp/helper/helper.go b/internal/lsp/helper/helper.go
index 06b2457..4d1dc41 100644
--- a/internal/lsp/helper/helper.go
+++ b/internal/lsp/helper/helper.go
@@ -72,7 +72,11 @@
 				cm = ", "
 			}
 			t.Param += fmt.Sprintf("%s%s %s", cm, t.paramnames[i], p)
-			t.Invoke += fmt.Sprintf("%s%s", cm, t.paramnames[i])
+			this := t.paramnames[i]
+			if this == "_" {
+				this = "nil"
+			}
+			t.Invoke += fmt.Sprintf("%s%s", cm, this)
 		}
 		if len(t.Results) > 1 {
 			t.Result = "("
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index d94b8f0..0303491 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -49,7 +49,7 @@
 	tests.DefaultOptions(options)
 	session.SetOptions(options)
 	options.SetEnvSlice(datum.Config.Env)
-	view, snapshot, release, err := session.NewView(ctx, datum.Config.Dir, span.URIFromPath(datum.Config.Dir), options)
+	view, snapshot, release, err := session.NewView(ctx, datum.Config.Dir, span.URIFromPath(datum.Config.Dir), "", options)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -391,8 +391,6 @@
 }
 
 func (r *runner) SemanticTokens(t *testing.T, spn span.Span) {
-	// no client, so use default
-	rememberToks(SemanticTypes(), SemanticModifiers())
 	uri := spn.URI()
 	filename := uri.Filename()
 	// this is called solely for coverage in semantic.go
@@ -1161,7 +1159,7 @@
 
 	// Always run diagnostics with analysis.
 	reports, _ := r.server.diagnose(r.ctx, snapshot, true)
-	r.server.publishReports(r.ctx, snapshot, reports)
+	r.server.publishReports(r.ctx, snapshot, reports, false)
 	for uri, sent := range r.server.delivered {
 		var diagnostics []*source.Diagnostic
 		for _, d := range sent.sorted {
diff --git a/internal/lsp/lsprpc/lsprpc.go b/internal/lsp/lsprpc/lsprpc.go
index 448c0e5..dc3e29c 100644
--- a/internal/lsp/lsprpc/lsprpc.go
+++ b/internal/lsp/lsprpc/lsprpc.go
@@ -39,8 +39,8 @@
 // streams as a new LSP session, using a shared cache.
 type StreamServer struct {
 	cache *cache.Cache
-	// logConnections controls whether or not to log new connections.
-	logConnections bool
+	// daemon controls whether or not to log new connections.
+	daemon bool
 
 	// serverForTest may be set to a test fake for testing.
 	serverForTest protocol.Server
@@ -49,8 +49,8 @@
 // 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, logConnections bool) *StreamServer {
-	return &StreamServer{cache: cache, logConnections: logConnections}
+func NewStreamServer(cache *cache.Cache, daemon bool) *StreamServer {
+	return &StreamServer{cache: cache, daemon: daemon}
 }
 
 // ServeStream implements the jsonrpc2.StreamServer interface, by handling
@@ -78,10 +78,10 @@
 	ctx = protocol.WithClient(ctx, client)
 	conn.Go(ctx,
 		protocol.Handlers(
-			handshaker(session, executable, s.logConnections,
+			handshaker(session, executable, s.daemon,
 				protocol.ServerHandler(server,
 					jsonrpc2.MethodNotFound))))
-	if s.logConnections {
+	if s.daemon {
 		log.Printf("Session %s: connected", session.ID())
 		defer log.Printf("Session %s: exited", session.ID())
 	}
@@ -226,6 +226,8 @@
 		hreq.DebugAddr = di.ListenedDebugAddress
 	}
 	if err := protocol.Call(ctx, serverConn, handshakeMethod, hreq, &hresp); err != nil {
+		// TODO(rfindley): at some point in the future we should return an error
+		// here.  Handshakes have become functional in nature.
 		event.Error(ctx, "forwarder: gopls handshake failed", err)
 	}
 	if hresp.GoplsPath != f.goplsPath {
diff --git a/internal/lsp/mod/code_lens.go b/internal/lsp/mod/code_lens.go
index 3b3533d..db2aea2 100644
--- a/internal/lsp/mod/code_lens.go
+++ b/internal/lsp/mod/code_lens.go
@@ -52,7 +52,7 @@
 		if err != nil {
 			return nil, err
 		}
-		upgradeDepArgs, err := source.MarshalArgs(fh.URI(), []string{dep})
+		upgradeDepArgs, err := source.MarshalArgs(fh.URI(), false, []string{dep})
 		if err != nil {
 			return nil, err
 		}
@@ -69,7 +69,7 @@
 	// If there is at least 1 upgrade, add "Upgrade all dependencies" to
 	// the module statement.
 	if len(allUpgrades) > 0 {
-		upgradeDepArgs, err := source.MarshalArgs(fh.URI(), append([]string{"-u"}, allUpgrades...))
+		upgradeDepArgs, err := source.MarshalArgs(fh.URI(), false, append([]string{"-u"}, allUpgrades...))
 		if err != nil {
 			return nil, err
 		}
@@ -116,7 +116,7 @@
 	return []protocol.CodeLens{{
 		Range: rng,
 		Command: protocol.Command{
-			Title:     "Tidy module",
+			Title:     source.CommandTidy.Title,
 			Command:   source.CommandTidy.ID(),
 			Arguments: goModArgs,
 		},
diff --git a/internal/lsp/mod/diagnostics.go b/internal/lsp/mod/diagnostics.go
index 8904ba2..41a462c 100644
--- a/internal/lsp/mod/diagnostics.go
+++ b/internal/lsp/mod/diagnostics.go
@@ -26,30 +26,39 @@
 			return nil, err
 		}
 		reports[fh.VersionedFileIdentity()] = []*source.Diagnostic{}
-		tidied, err := snapshot.ModTidy(ctx, fh)
-		if err == source.ErrTmpModfileUnsupported {
-			return nil, nil
-		}
+		errors, err := ErrorsForMod(ctx, snapshot, fh)
 		if err != nil {
 			return nil, err
 		}
-		for _, e := range tidied.Errors {
-			diag := &source.Diagnostic{
+		for _, e := range errors {
+			d := &source.Diagnostic{
 				Message: e.Message,
 				Range:   e.Range,
 				Source:  e.Category,
 			}
 			if e.Category == "syntax" {
-				diag.Severity = protocol.SeverityError
+				d.Severity = protocol.SeverityError
 			} else {
-				diag.Severity = protocol.SeverityWarning
+				d.Severity = protocol.SeverityWarning
 			}
 			fh, err := snapshot.GetVersionedFile(ctx, e.URI)
 			if err != nil {
 				return nil, err
 			}
-			reports[fh.VersionedFileIdentity()] = append(reports[fh.VersionedFileIdentity()], diag)
+			reports[fh.VersionedFileIdentity()] = append(reports[fh.VersionedFileIdentity()], d)
 		}
 	}
 	return reports, nil
 }
+
+func ErrorsForMod(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]source.Error, error) {
+	tidied, err := snapshot.ModTidy(ctx, fh)
+
+	if source.IsNonFatalGoModError(err) {
+		return nil, nil
+	}
+	if err != nil {
+		return nil, err
+	}
+	return tidied.Errors, nil
+}
diff --git a/internal/lsp/mod/mod_test.go b/internal/lsp/mod/mod_test.go
index f7c3360..6ce8926 100644
--- a/internal/lsp/mod/mod_test.go
+++ b/internal/lsp/mod/mod_test.go
@@ -45,7 +45,7 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	_, _, release, err := session.NewView(ctx, "diagnostics_test", span.URIFromPath(folder), options)
+	_, _, release, err := session.NewView(ctx, "diagnostics_test", span.URIFromPath(folder), "", options)
 	release()
 	if err != nil {
 		t.Fatal(err)
diff --git a/internal/lsp/progress.go b/internal/lsp/progress.go
index d444ad1..4d18fd8 100644
--- a/internal/lsp/progress.go
+++ b/internal/lsp/progress.go
@@ -162,6 +162,9 @@
 
 // report reports an update on WorkDone report back to the client.
 func (wd *workDone) report(message string, percentage float64) {
+	if wd == nil {
+		return
+	}
 	wd.cancelMu.Lock()
 	cancelled := wd.cancelled
 	wd.cancelMu.Unlock()
@@ -192,6 +195,9 @@
 
 // end reports a workdone completion back to the client.
 func (wd *workDone) end(message string) {
+	if wd == nil {
+		return
+	}
 	var err error
 	switch {
 	case wd.err != nil:
diff --git a/internal/lsp/protocol/tsclient.go b/internal/lsp/protocol/tsclient.go
index d30594b..0670e11 100644
--- a/internal/lsp/protocol/tsclient.go
+++ b/internal/lsp/protocol/tsclient.go
@@ -2,8 +2,8 @@
 
 // Package protocol contains data types and code for LSP jsonrpcs
 // generated automatically from vscode-languageserver-node
-// commit: 60a5a7825e6f54f57917091f394fd8db7d1724bc
-// last fetched Thu Sep 10 2020 09:21:57 GMT-0400 (Eastern Daylight Time)
+// commit: 901fd40345060d159f07d234bbc967966a929a34
+// last fetched Mon Oct 26 2020 09:10:42 GMT-0400 (Eastern Daylight 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 c3532b5..09c1934 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: 60a5a7825e6f54f57917091f394fd8db7d1724bc
-// last fetched Thu Sep 10 2020 09:21:57 GMT-0400 (Eastern Daylight Time)
+// commit: 901fd40345060d159f07d234bbc967966a929a34
+// last fetched Mon Oct 26 2020 09:10:42 GMT-0400 (Eastern Daylight Time)
 package protocol
 
 // Code generated (see typescript/README.md) DO NOT EDIT.
@@ -122,6 +122,11 @@
 	 * Must be contained by the [`range`](#CallHierarchyItem.range).
 	 */
 	SelectionRange Range `json:"selectionRange"`
+	/**
+	 * A data entry field that is preserved between a call hierarchy prepare and
+	 * incoming calls or outgoing calls requests.
+	 */
+	Data interface{} `json:"data,omitempty"`
 }
 
 /**
@@ -219,8 +224,8 @@
 		 */
 		Window interface{} `json:"window,omitempty"`
 		/**
-		 * Whether client supports handling progress notifications. If set servers are allowed to
-		 * report in `workDoneProgress` property in the request specific server capabilities.
+		 * Whether client supports server initiated progress using the
+		 * `window/workDoneProgress/create` request.
 		 *
 		 * Since 3.15.0
 		 */
@@ -264,6 +269,31 @@
 	 */
 	IsPreferred bool `json:"isPreferred,omitempty"`
 	/**
+	 * Marks that the code action cannot currently be applied.
+	 *
+	 * Clients should follow the following guidelines regarding disabled code actions:
+	 *
+	 *   - Disabled code actions are not shown in automatic [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action)
+	 *     code action menu.
+	 *
+	 *   - Disabled actions are shown as faded out in the code action menu when the user request a more specific type
+	 *     of code action, such as refactorings.
+	 *
+	 *   - If the user has a [keybinding](https://code.visualstudio.com/docs/editor/refactoring#_keybindings-for-code-actions)
+	 *     that auto applies a code action and only a disabled code actions are returned, the client should show the user an
+	 *     error message with `reason` in the editor.
+	 *
+	 * @since 3.16.0
+	 */
+	Disabled struct {
+		/**
+		 * Human readable description of why the code action is currently disabled.
+		 *
+		 * This is displayed in the code actions UI.
+		 */
+		Reason string `json:"reason"`
+	} `json:"disabled,omitempty"`
+	/**
 	 * The workspace edit this code action performs.
 	 */
 	Edit WorkspaceEdit `json:"edit,omitempty"`
@@ -273,6 +303,13 @@
 	 * executed and then the command.
 	 */
 	Command *Command `json:"command,omitempty"`
+	/**
+	 * A data entry field that is preserved on a code action between
+	 * a `textDocument/codeAction` and a `codeAction/resolve` request.
+	 *
+	 * @since 3.16.0 - proposed state
+	 */
+	Data interface{} `json:"data,omitempty"`
 }
 
 /**
@@ -307,9 +344,36 @@
 	} `json:"codeActionLiteralSupport,omitempty"`
 	/**
 	 * Whether code action supports the `isPreferred` property.
+	 *
 	 * @since 3.15.0
 	 */
 	IsPreferredSupport bool `json:"isPreferredSupport,omitempty"`
+	/**
+	 * Whether code action supports the `disabled` property.
+	 *
+	 * @since 3.16.0 - proposed state
+	 */
+	DisabledSupport bool `json:"disabledSupport,omitempty"`
+	/**
+	 * Whether code action supports the `data` property which is
+	 * preserved between a `textDocument/codeAction` and a
+	 * `codeAction/resolve` request.
+	 *
+	 * @since 3.16.0 - proposed state
+	 */
+	DataSupport bool `json:"dataSupport,omitempty"`
+	/**
+	 * Whether the client support resolving additional code action
+	 * properties via a separate `codeAction/resolve` request.
+	 *
+	 * @since 3.16.0 - proposed state
+	 */
+	ResolveSupport struct {
+		/**
+		 * The properties that a client can resolve lazily.
+		 */
+		Properties []string `json:"properties"`
+	} `json:"resolveSupport,omitempty"`
 }
 
 /**
@@ -350,6 +414,13 @@
 	 * may list out every specific kind they provide.
 	 */
 	CodeActionKinds []CodeActionKind `json:"codeActionKinds,omitempty"`
+	/**
+	 * The server provides support to resolve additional
+	 * information for a code action.
+	 *
+	 * @since 3.16.0
+	 */
+	ResolveProvider bool `json:"resolveProvider,omitempty"`
 	WorkDoneProgressOptions
 }
 
@@ -374,6 +445,18 @@
 }
 
 /**
+ * Structure to capture a description for an error code.
+ *
+ * @since 3.16.0 - proposed state
+ */
+type CodeDescription struct {
+	/**
+	 * An URI to open with more information about the diagnostic error.
+	 */
+	Href URI `json:"href"`
+}
+
+/**
  * A code lens represents a [command](#Command) that should be shown along with
  * source text, like the number of references, a way to run tests, etc.
  *
@@ -585,16 +668,22 @@
 		 * Client support insert replace edit to control different behavior if a
 		 * completion item is inserted in the text or should replace text.
 		 *
-		 * @since 3.16.0 - Proposed state
+		 * @since 3.16.0 - proposed state
 		 */
 		InsertReplaceSupport bool `json:"insertReplaceSupport,omitempty"`
 		/**
-		 * Client supports to resolve `additionalTextEdits` in the `completionItem/resolve`
-		 * request. So servers can postpone computing them.
+		 * Indicates which properties a client can resolve lazily on a completion
+		 * item. Before version 3.16.0 only the predefined properties `documentation`
+		 * and `details` could be resolved lazily.
 		 *
-		 * @since 3.16.0 - Proposed state
+		 * @since 3.16.0 - proposed state
 		 */
-		ResolveAdditionalTextEditsSupport bool `json:"resolveAdditionalTextEditsSupport,omitempty"`
+		ResolveSupport struct {
+			/**
+			 * The properties that a client can resolve lazily.
+			 */
+			Properties []string `json:"properties"`
+		} `json:"resolveSupport,omitempty"`
 	} `json:"completionItem,omitempty"`
 	CompletionItemKind struct {
 		/**
@@ -722,7 +811,7 @@
 	 * *Note 2:* If an `InsertReplaceEdit` is returned the edit's insert range must be a prefix of
 	 * the edit's replace range, that means it must be contained and starting at the same position.
 	 *
-	 * @since 3.16.0 additional type `InsertReplaceEdit` - Proposed state
+	 * @since 3.16.0 additional type `InsertReplaceEdit` - proposed state
 	 */
 	TextEdit *TextEdit/*TextEdit | InsertReplaceEdit*/ `json:"textEdit,omitempty"`
 	/**
@@ -1041,10 +1130,14 @@
 	Severity DiagnosticSeverity `json:"severity,omitempty"`
 	/**
 	 * The diagnostic's code, which usually appear in the user interface.
-	 *
-	 * @since 3.16.0 Support for `DiagnosticCode` - Proposed state
 	 */
-	Code interface{}/* float64 | string | DiagnosticCode*/ `json:"code,omitempty"`
+	Code interface{}/*number | string*/ `json:"code,omitempty"`
+	/**
+	 * An optional property to describe the error code.
+	 *
+	 * @since 3.16.0 - proposed state
+	 */
+	CodeDescription *CodeDescription `json:"codeDescription,omitempty"`
 	/**
 	 * A human-readable string describing the source of this
 	 * diagnostic, e.g. 'typescript' or 'super lint'. It usually
@@ -1066,22 +1159,13 @@
 	 * a scope collide all definitions can be marked via this property.
 	 */
 	RelatedInformation []DiagnosticRelatedInformation `json:"relatedInformation,omitempty"`
-}
-
-/**
- * Structure to capture more complex diagnostic codes.
- *
- * @since 3.16.0 - Proposed state
- */
-type DiagnosticCode struct {
 	/**
-	 * The actual code
+	 * A data entry field that is preserved between a `textDocument/publishDiagnostics`
+	 * notification and `textDocument/codeAction` request.
+	 *
+	 * @since 3.16.0 - proposed state
 	 */
-	Value string/*string | number*/ `json:"value"`
-	/**
-	 * A target URI to open with more information about the diagnostic error.
-	 */
-	Target URI `json:"target"`
+	Data interface{} `json:"data,omitempty"`
 }
 
 /**
@@ -1545,7 +1629,7 @@
 	/**
 	 * Tags for this completion item.
 	 *
-	 * @since 3.16.0 - Proposed state
+	 * @since 3.16.0 - proposed state
 	 */
 	Tags []SymbolTag `json:"tags,omitempty"`
 	/**
@@ -1556,13 +1640,13 @@
 	Deprecated bool `json:"deprecated,omitempty"`
 	/**
 	 * The range enclosing this symbol not including leading/trailing whitespace but everything else
-	 * like comments. This information is typically used to determine if the clients cursor is
+	 * like comments. This information is typically used to determine if the the clients cursor is
 	 * inside the symbol to reveal in the symbol in the UI.
 	 */
 	Range Range `json:"range"`
 	/**
 	 * The range that should be selected and revealed when this symbol is being picked, e.g the name of a function.
-	 * Must be contained by the `range`.
+	 * Must be contained by the the `range`.
 	 */
 	SelectionRange Range `json:"selectionRange"`
 	/**
@@ -1604,7 +1688,7 @@
 	 * `DocumentSymbol` if `hierarchicalDocumentSymbolSupport` is set to true.
 	 * Clients supporting tags have to handle unknown tags gracefully.
 	 *
-	 * @since 3.16.0 - Proposed state
+	 * @since 3.16.0 - proposed state
 	 */
 	TagSupport struct {
 		/**
@@ -1612,12 +1696,26 @@
 		 */
 		ValueSet []SymbolTag `json:"valueSet"`
 	} `json:"tagSupport,omitempty"`
+	/**
+	 * The client supports an additional label presented in the UI when
+	 * registering a document symbol provider.
+	 *
+	 * @since 3.16.0
+	 */
+	LabelSupport bool `json:"labelSupport,omitempty"`
 }
 
 /**
  * Provider options for a [DocumentSymbolRequest](#DocumentSymbolRequest).
  */
 type DocumentSymbolOptions struct {
+	/**
+	 * A human-readable string that is shown when multiple outlines trees
+	 * are shown for the same document.
+	 *
+	 * @since 3.16.0 - proposed state
+	 */
+	Label string `json:"label,omitempty"`
 	WorkDoneProgressOptions
 }
 
@@ -2114,13 +2212,13 @@
 	/**
 	 * The server provides Call Hierarchy support.
 	 *
-	 * @since 3.16.0 - Proposed state
+	 * @since 3.16.0 - proposed state
 	 */
 	CallHierarchyProvider interface{}/* bool | CallHierarchyOptions | CallHierarchyRegistrationOptions*/ `json:"callHierarchyProvider,omitempty"`
 	/**
 	 * The server provides semantic tokens support.
 	 *
-	 * @since 3.16.0 - Proposed state
+	 * @since 3.16.0 - proposed state
 	 */
 	SemanticTokensProvider interface{}/*SemanticTokensOptions | SemanticTokensRegistrationOptions*/ `json:"semanticTokensProvider,omitempty"`
 	/**
@@ -2132,7 +2230,7 @@
 /**
  * A special text edit to provide an insert and a replace operation.
  *
- * @since 3.16.0 - Proposed state
+ * @since 3.16.0 - proposed state
  */
 type InsertReplaceEdit struct {
 	/**
@@ -2188,7 +2286,7 @@
 	TargetRange Range `json:"targetRange"`
 	/**
 	 * The range that should be selected and revealed when this link is being followed, e.g the name of a function.
-	 * Must be contained by the `targetRange`. See also `DocumentSymbol#range`
+	 * Must be contained by the the `targetRange`. See also `DocumentSymbol#range`
 	 */
 	TargetSelectionRange Range `json:"targetSelectionRange"`
 }
@@ -2285,6 +2383,44 @@
 type MessageType float64
 
 /**
+ * Moniker definition to match LSIF 0.5 moniker definition.
+ *
+ * @since 3.16.0
+ */
+type Moniker struct {
+	/**
+	 * The scheme of the moniker. For example tsc or .Net
+	 */
+	Scheme string `json:"scheme"`
+	/**
+	 * The identifier of the moniker. The value is opaque in LSIF however
+	 * schema owners are allowed to define the structure if they want.
+	 */
+	Identifier string `json:"identifier"`
+	/**
+	 * The scope in which the moniker is unique
+	 */
+	Unique UniquenessLevel `json:"unique"`
+	/**
+	 * The moniker kind if known.
+	 */
+	Kind MonikerKind `json:"kind,omitempty"`
+}
+
+/**
+ * The moniker kind.
+ *
+ * @since 3.16.0
+ */
+type MonikerKind string
+
+type MonikerParams struct {
+	TextDocumentPositionParams
+	WorkDoneProgressParams
+	PartialResultParams
+}
+
+/**
  * Represents a parameter of a callable-signature. A parameter can
  * have a label and a doc-comment.
  */
@@ -2390,11 +2526,19 @@
 	 */
 	VersionSupport bool `json:"versionSupport,omitempty"`
 	/**
-	 * Clients support complex diagnostic codes (e.g. code and target URI).
+	 * Client supports a codeDescription property
 	 *
-	 * @since 3.16.0 - Proposed state
+	 * @since 3.16.0 - proposed state
 	 */
-	ComplexDiagnosticCodeSupport bool `json:"complexDiagnosticCodeSupport,omitempty"`
+	CodeDescriptionSupport bool `json:"codeDescriptionSupport,omitempty"`
+	/**
+	 * Whether code action supports the `data` property which is
+	 * preserved between a `textDocument/publishDiagnostics` and
+	 * `textDocument/codeAction` request.
+	 *
+	 * @since 3.16.0 - proposed state
+	 */
+	DataSupport bool `json:"dataSupport,omitempty"`
 }
 
 /**
@@ -2679,6 +2823,45 @@
 /**
  * @since 3.16.0 - Proposed state
  */
+type SemanticTokensClientCapabilities struct {
+	/**
+	 * Whether implementation supports dynamic registration. If this is set to `true`
+	 * the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
+	 * return value for the corresponding server capability as well.
+	 */
+	DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
+	/**
+	 * Which requests the client supports and might send to the server
+	 */
+	Requests struct {
+		/**
+		 * The client will send the `textDocument/semanticTokens/range` request if
+		 * the server provides a corresponding handler.
+		 */
+		Range bool/*boolean | {		}*/ `json:"range,omitempty"`
+		/**
+		 * The client will send the `textDocument/semanticTokens/full` request if
+		 * the server provides a corresponding handler.
+		 */
+		Full interface{}/*boolean | <elided struct>*/ `json:"full,omitempty"`
+	} `json:"requests"`
+	/**
+	 * The token types that the client supports.
+	 */
+	TokenTypes []string `json:"tokenTypes"`
+	/**
+	 * The token modifiers that the client supports.
+	 */
+	TokenModifiers []string `json:"tokenModifiers"`
+	/**
+	 * The formats the clients supports.
+	 */
+	Formats []TokenFormat `json:"formats"`
+}
+
+/**
+ * @since 3.16.0 - Proposed state
+ */
 type SemanticTokensDelta struct {
 	ResultID string `json:"resultId,omitempty"`
 	/**
@@ -2752,7 +2935,7 @@
 	/**
 	 * Server supports providing semantic tokens for a full document.
 	 */
-	Full bool/*boolean | <elided struct>*/ `json:"full,omitempty"`
+	Full interface{}/*boolean | <elided struct>*/ `json:"full,omitempty"`
 	WorkDoneProgressOptions
 }
 
@@ -2793,6 +2976,16 @@
 	StaticRegistrationOptions
 }
 
+type SemanticTokensWorkspaceClientCapabilities struct {
+	/**
+	 * Whether the client implementation supports a refresh request send from the server
+	 * to the client. This is useful if a server detects a project wide configuration change
+	 * which requires a re-calculation of all semantic tokens provided by the server issuing
+	 * the request.
+	 */
+	RefreshSupport bool `json:"refreshSupport,omitempty"`
+}
+
 type ServerCapabilities = struct {
 	/**
 	 * Defines how text documents are synced. Is either a detailed structure defining each notification or
@@ -2894,13 +3087,13 @@
 	/**
 	 * The server provides Call Hierarchy support.
 	 *
-	 * @since 3.16.0 - Proposed state
+	 * @since 3.16.0 - proposed state
 	 */
 	CallHierarchyProvider interface{}/* bool | CallHierarchyOptions | CallHierarchyRegistrationOptions*/ `json:"callHierarchyProvider,omitempty"`
 	/**
 	 * The server provides semantic tokens support.
 	 *
-	 * @since 3.16.0 - Proposed state
+	 * @since 3.16.0 - proposed state
 	 */
 	SemanticTokensProvider interface{}/*SemanticTokensOptions | SemanticTokensRegistrationOptions*/ `json:"semanticTokensProvider,omitempty"`
 	/**
@@ -3149,7 +3342,7 @@
 	/**
 	 * Tags for this completion item.
 	 *
-	 * @since 3.16.0 - Proposed state
+	 * @since 3.16.0 - proposed state
 	 */
 	Tags []SymbolTag `json:"tags,omitempty"`
 	/**
@@ -3289,23 +3482,28 @@
 	 */
 	SelectionRange SelectionRangeClientCapabilities `json:"selectionRange,omitempty"`
 	/**
-	 * Capabilities specific to `textDocument/publishDiagnostics`.
+	 * Capabilities specific to `textDocument/publishDiagnostics` notification.
 	 */
 	PublishDiagnostics PublishDiagnosticsClientCapabilities `json:"publishDiagnostics,omitempty"`
 	/**
-	 * Capabilities specific to the `textDocument/callHierarchy`.
+	 * Capabilities specific to the various call hierarchy requests.
 	 *
 	 * @since 3.16.0
 	 */
 	CallHierarchy CallHierarchyClientCapabilities `json:"callHierarchy,omitempty"`
-
-	// missing in source, generated
-	SemanticTokens *SemanticTokensClientCapabilities `json:"semanticTokens,omitempty"`
+	/**
+	 * Capabilities specific to the various semantic token requsts.
+	 *
+	 * @since 3.16.0 - Proposed state
+	 */
+	SemanticTokens SemanticTokensClientCapabilities `json:"semanticTokens,omitempty"`
 }
 
 /**
  * An event describing a change to a text document. If range and rangeLength are omitted
  * the new text is considered to be the full content of the document.
+ *
+ * @deprecated Use the text document from the new vscode-languageserver-textdocument package.
  */
 type TextDocumentContentChangeEvent = struct {
 	/**
@@ -3477,6 +3675,8 @@
 	NewText string `json:"newText"`
 }
 
+type TokenFormat = string
+
 type TraceValues = string /*'off' | 'messages' | 'verbose'*/
 
 /**
@@ -3516,11 +3716,18 @@
 /**
  * A tagging type for string properties that are actually URIs
  *
- * @since 3.16.0 - Proposed state
+ * @since 3.16.0 - proposed state
  */
 type URI = string
 
 /**
+ * Moniker uniqueness level to define scope of the moniker.
+ *
+ * @since 3.16.0
+ */
+type UniquenessLevel string
+
+/**
  * General parameters to unregister a request or notification.
  */
 type Unregistration struct {
@@ -3617,8 +3824,8 @@
 	 */
 	Window struct {
 		/**
-		 * Whether client supports handling progress notifications. If set servers are allowed to
-		 * report in `workDoneProgress` property in the request specific server capabilities.
+		 * Whether client supports server initiated progress using the
+		 * `window/workDoneProgress/create` request.
 		 *
 		 * Since 3.15.0
 		 */
@@ -3711,6 +3918,13 @@
 	 * Capabilities specific to the `workspace/executeCommand` request.
 	 */
 	ExecuteCommand ExecuteCommandClientCapabilities `json:"executeCommand,omitempty"`
+	/**
+	 * Capabilities specific to the semantic token requsts scoped to the
+	 * workspace.
+	 *
+	 * @since 3.16.0 - proposed state.
+	 */
+	SemanticTokens SemanticTokensWorkspaceClientCapabilities `json:"semanticTokens,omitempty"`
 }
 
 /**
@@ -3834,7 +4048,7 @@
 	 * The client supports tags on `SymbolInformation`.
 	 * Clients supporting tags have to handle unknown tags gracefully.
 	 *
-	 * @since 3.16.0 - Proposed state
+	 * @since 3.16.0 - proposed state
 	 */
 	TagSupport struct {
 		/**
@@ -3864,20 +4078,6 @@
 	PartialResultParams
 }
 
-// generated
-type SemanticTokensClientCapabilities struct {
-	TokenModifiers []string
-	Formats        []string
-	Requests       struct {
-		Range bool
-		Full  struct {
-			Delta bool
-		}
-	}
-	DynamicRegistration bool
-	TokenTypes          []string
-}
-
 const (
 	/**
 	 * Empty kind.
@@ -4159,6 +4359,19 @@
 
 	Log MessageType = 4
 	/**
+	 * The moniker represent a symbol that is imported into a project
+	 */
+	Import MonikerKind = "import"
+	/**
+	 * The moniker represents a symbol that is exported from a project
+	 */
+	Export MonikerKind = "export"
+	/**
+	 * The moniker represents a symbol that is local to a project (e.g. a local
+	 * variable of a function, a class not visible outside the project, ...)
+	 */
+	Local MonikerKind = "local"
+	/**
 	 * Supports creating new files and folders.
 	 */
 
@@ -4254,6 +4467,26 @@
 
 	Incremental TextDocumentSyncKind = 2
 	/**
+	 * The moniker is only unique inside a document
+	 */
+	Document UniquenessLevel = "document"
+	/**
+	 * The moniker is unique inside a project for which a dump got created
+	 */
+	Project UniquenessLevel = "project"
+	/**
+	 * The moniker is unique inside the group to which a project belongs
+	 */
+	Group UniquenessLevel = "group"
+	/**
+	 * The moniker is unique inside the moniker scheme.
+	 */
+	Scheme UniquenessLevel = "scheme"
+	/**
+	 * The moniker is globally unique
+	 */
+	Global UniquenessLevel = "global"
+	/**
 	 * Interested in create events.
 	 */
 
diff --git a/internal/lsp/protocol/tsserver.go b/internal/lsp/protocol/tsserver.go
index d1dada3..c6db497 100644
--- a/internal/lsp/protocol/tsserver.go
+++ b/internal/lsp/protocol/tsserver.go
@@ -2,8 +2,8 @@
 
 // Package protocol contains data types and code for LSP jsonrpcs
 // generated automatically from vscode-languageserver-node
-// commit: 60a5a7825e6f54f57917091f394fd8db7d1724bc
-// last fetched Thu Sep 10 2020 09:21:57 GMT-0400 (Eastern Daylight Time)
+// commit: 901fd40345060d159f07d234bbc967966a929a34
+// last fetched Mon Oct 26 2020 09:10:42 GMT-0400 (Eastern Daylight Time)
 
 // Code generated (see typescript/README.md) DO NOT EDIT.
 
@@ -18,7 +18,6 @@
 type Server interface {
 	DidChangeWorkspaceFolders(context.Context, *DidChangeWorkspaceFoldersParams) error
 	WorkDoneProgressCancel(context.Context, *WorkDoneProgressCancelParams) error
-	SemanticTokensRefresh(context.Context) error
 	Initialized(context.Context, *InitializedParams) error
 	Exit(context.Context) error
 	DidChangeConfiguration(context.Context, *DidChangeConfigurationParams) error
@@ -43,6 +42,7 @@
 	SemanticTokensFull(context.Context, *SemanticTokensParams) (*SemanticTokens /*SemanticTokens | null*/, error)
 	SemanticTokensFullDelta(context.Context, *SemanticTokensDeltaParams) (interface{} /* SemanticTokens | SemanticTokensDelta | nil*/, error)
 	SemanticTokensRange(context.Context, *SemanticTokensRangeParams) (*SemanticTokens /*SemanticTokens | null*/, error)
+	SemanticTokensRefresh(context.Context) error
 	Initialize(context.Context, *ParamInitialize) (*InitializeResult, error)
 	Shutdown(context.Context) error
 	WillSaveWaitUntil(context.Context, *WillSaveTextDocumentParams) ([]TextEdit /*TextEdit[] | null*/, error)
@@ -55,6 +55,7 @@
 	DocumentHighlight(context.Context, *DocumentHighlightParams) ([]DocumentHighlight /*DocumentHighlight[] | null*/, error)
 	DocumentSymbol(context.Context, *DocumentSymbolParams) ([]interface{} /*SymbolInformation[] | DocumentSymbol[] | null*/, error)
 	CodeAction(context.Context, *CodeActionParams) ([]CodeAction /*(Command | CodeAction)[] | null*/, error)
+	ResolveCodeAction(context.Context, *CodeAction) (*CodeAction, error)
 	Symbol(context.Context, *WorkspaceSymbolParams) ([]SymbolInformation /*SymbolInformation[] | null*/, error)
 	CodeLens(context.Context, *CodeLensParams) ([]CodeLens /*CodeLens[] | null*/, error)
 	ResolveCodeLens(context.Context, *CodeLens) (*CodeLens, error)
@@ -66,6 +67,7 @@
 	Rename(context.Context, *RenameParams) (*WorkspaceEdit /*WorkspaceEdit | null*/, error)
 	PrepareRename(context.Context, *PrepareRenameParams) (*Range /*Range | { range: Range, placeholder: string } | { defaultBehavior: boolean } | null*/, error)
 	ExecuteCommand(context.Context, *ExecuteCommandParams) (interface{} /*any | null*/, error)
+	Moniker(context.Context, *MonikerParams) ([]Moniker /*Moniker[] | null*/, error)
 	NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error)
 }
 
@@ -85,9 +87,6 @@
 		}
 		err := server.WorkDoneProgressCancel(ctx, &params)
 		return true, reply(ctx, nil, err)
-	case "workspace/semanticTokens/refresh": // notif
-		err := server.SemanticTokensRefresh(ctx)
-		return true, reply(ctx, nil, err)
 	case "initialized": // notif
 		var params InitializedParams
 		if err := json.Unmarshal(r.Params(), &params); err != nil {
@@ -252,6 +251,12 @@
 		}
 		resp, err := server.SemanticTokensRange(ctx, &params)
 		return true, reply(ctx, resp, err)
+	case "workspace/semanticTokens/refresh": // req
+		if len(r.Params()) > 0 {
+			return true, reply(ctx, nil, errors.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams))
+		}
+		err := server.SemanticTokensRefresh(ctx)
+		return true, reply(ctx, nil, err)
 	case "initialize": // req
 		var params ParamInitialize
 		if err := json.Unmarshal(r.Params(), &params); err != nil {
@@ -335,6 +340,13 @@
 		}
 		resp, err := server.CodeAction(ctx, &params)
 		return true, reply(ctx, resp, err)
+	case "codeAction/resolve": // req
+		var params CodeAction
+		if err := json.Unmarshal(r.Params(), &params); err != nil {
+			return true, sendParseError(ctx, reply, err)
+		}
+		resp, err := server.ResolveCodeAction(ctx, &params)
+		return true, reply(ctx, resp, err)
 	case "workspace/symbol": // req
 		var params WorkspaceSymbolParams
 		if err := json.Unmarshal(r.Params(), &params); err != nil {
@@ -412,6 +424,13 @@
 		}
 		resp, err := server.ExecuteCommand(ctx, &params)
 		return true, reply(ctx, resp, err)
+	case "textDocument/moniker": // req
+		var params MonikerParams
+		if err := json.Unmarshal(r.Params(), &params); err != nil {
+			return true, sendParseError(ctx, reply, err)
+		}
+		resp, err := server.Moniker(ctx, &params)
+		return true, reply(ctx, resp, err)
 
 	default:
 		return false, nil
@@ -426,10 +445,6 @@
 	return s.Conn.Notify(ctx, "window/workDoneProgress/cancel", params)
 }
 
-func (s *serverDispatcher) SemanticTokensRefresh(ctx context.Context) error {
-	return s.Conn.Notify(ctx, "workspace/semanticTokens/refresh", nil)
-}
-
 func (s *serverDispatcher) Initialized(ctx context.Context, params *InitializedParams) error {
 	return s.Conn.Notify(ctx, "initialized", params)
 }
@@ -577,6 +592,10 @@
 	return result, nil
 }
 
+func (s *serverDispatcher) SemanticTokensRefresh(ctx context.Context) error {
+	return Call(ctx, s.Conn, "workspace/semanticTokens/refresh", nil, nil)
+}
+
 func (s *serverDispatcher) Initialize(ctx context.Context, params *ParamInitialize) (*InitializeResult, error) {
 	var result *InitializeResult
 	if err := Call(ctx, s.Conn, "initialize", params, &result); err != nil {
@@ -669,6 +688,14 @@
 	return result, nil
 }
 
+func (s *serverDispatcher) ResolveCodeAction(ctx context.Context, params *CodeAction) (*CodeAction, error) {
+	var result *CodeAction
+	if err := Call(ctx, s.Conn, "codeAction/resolve", params, &result); err != nil {
+		return nil, err
+	}
+	return result, nil
+}
+
 func (s *serverDispatcher) Symbol(ctx context.Context, params *WorkspaceSymbolParams) ([]SymbolInformation /*SymbolInformation[] | null*/, error) {
 	var result []SymbolInformation /*SymbolInformation[] | null*/
 	if err := Call(ctx, s.Conn, "workspace/symbol", params, &result); err != nil {
@@ -757,6 +784,14 @@
 	return result, nil
 }
 
+func (s *serverDispatcher) Moniker(ctx context.Context, params *MonikerParams) ([]Moniker /*Moniker[] | null*/, error) {
+	var result []Moniker /*Moniker[] | null*/
+	if err := Call(ctx, s.Conn, "textDocument/moniker", params, &result); err != nil {
+		return nil, err
+	}
+	return result, nil
+}
+
 func (s *serverDispatcher) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
 	var result interface{}
 	if err := Call(ctx, s.Conn, method, params, &result); err != nil {
diff --git a/internal/lsp/protocol/typescript/code.ts b/internal/lsp/protocol/typescript/code.ts
index 39cea15..5eb081a 100644
--- a/internal/lsp/protocol/typescript/code.ts
+++ b/internal/lsp/protocol/typescript/code.ts
@@ -185,9 +185,7 @@
   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}`
+  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}`
 }
 
 let data = new Map<string, Data>();            // parsed data types
@@ -401,6 +399,7 @@
     n.kind == ts.SyntaxKind.StringKeyword ||
     n.kind == ts.SyntaxKind.NumberKeyword ||
     n.kind == ts.SyntaxKind.AnyKeyword ||
+    n.kind == ts.SyntaxKind.UnknownKeyword ||
     n.kind == ts.SyntaxKind.NullKeyword ||
     n.kind == ts.SyntaxKind.BooleanKeyword ||
     n.kind == ts.SyntaxKind.ObjectKeyword ||
@@ -433,7 +432,7 @@
     if (ts.isStringLiteral(n.initializer)) return;
     throw new Error(`EnumMember ${strKind(n.initializer)} ${n.name.getText()}`)
   } else {
-    throw new Error(`saw ${strKind(n)} in underlying. ${n.getText()}`)
+    throw new Error(`saw ${strKind(n)} in underlying. ${n.getText()} at ${loc(n)}`)
   }
 }
 
@@ -510,7 +509,8 @@
 // these fields need a *
 var starred: [string, string][] = [
   ['TextDocumentContentChangeEvent', 'range'], ['CodeAction', 'command'],
-  ['DidSaveTextDocumentParams', 'text'], ['CompletionItem', 'command']
+  ['DidSaveTextDocumentParams', 'text'], ['CompletionItem', 'command'],
+  ['Diagnostic', 'codeDescription']
 ];
 
 // generate Go code for an interface
@@ -532,7 +532,7 @@
         gt = '*' + gt;
       };
     })
-    ans = ans.concat(`${goName(n.name.getText())} ${gt}`, json, '\n')
+    ans = ans.concat(`${goName(n.name.getText())} ${gt}`, json, '\n');
   };
   d.properties.forEach(g)
   // heritage clauses become embedded types
@@ -607,8 +607,7 @@
 function goTypeAlias(d: Data, nm: string) {
   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()}`);
+      throw new Error(`${nm} has extra fields(${d.as.length},${d.generics.length}) ${d.me.getText()}`);
   }
   typesOut.push(getComments(d.me))
   // d.alias doesn't seem to have comments
@@ -631,7 +630,7 @@
     return 'float64';
   } else if (strKind(n) == 'BooleanKeyword') {
     return 'bool';
-  } else if (strKind(n) == 'AnyKeyword') {
+  } else if (strKind(n) == 'AnyKeyword' || strKind(n) == 'UnknownKeyword') {
     return 'interface{}';
   } else if (strKind(n) == 'NullKeyword') {
     return 'nil'
@@ -669,9 +668,9 @@
   // range?: boolean | {\n	};
   // full?: boolean | {\n		/**\n		 * The server supports deltas for full documents.\n		 */\n		delta?: boolean;\n	}
   // These are handled specially:
-  if (parent == 'SemanticTokensOptions') {
-    if (nm == 'range') help = help.replace(/\n/, '');
-    if (nm == 'full') help = '/*boolean | <elided struct>*/';
+  if (nm == 'range') help = help.replace(/\n/, '');
+  if (nm == 'full' && help.indexOf('\n') != -1) {
+    help = '/*boolean | <elided struct>*/';
   }
   // handle all the special cases
   switch (n.types.length) {
@@ -693,6 +692,7 @@
       if (a == 'BooleanKeyword') {  // usually want bool
         if (nm == 'codeActionProvider') return `interface{} ${help}`;
         if (nm == 'renameProvider') return `interface{} ${help}`;
+        if (nm == 'full') return `interface{} ${help}`; // there's a struct
         if (nm == 'save') return `${goType(n.types[1], '680')} ${help}`;
         return `${goType(n.types[0], 'b')} ${help}`
       }
@@ -1003,8 +1003,7 @@
     const p2 = a == '' ? 'nil' : 'params';
     const returnType = indirect(b) ? `*${b}` : b;
     callBody = `var result ${returnType}
-			if err := Call(ctx, s.Conn, "${m}", ${
-      p2}, &result); err != nil {
+			if err := Call(ctx, s.Conn, "${m}", ${p2}, &result); err != nil {
 				return nil, err
       }
       return result, nil
diff --git a/internal/lsp/protocol/typescript/util.ts b/internal/lsp/protocol/typescript/util.ts
index 77b2023..5fdd563 100644
--- a/internal/lsp/protocol/typescript/util.ts
+++ b/internal/lsp/protocol/typescript/util.ts
@@ -14,7 +14,7 @@
   `${dir}/${srcDir}/protocol/src/browser/main.ts`, `${dir}${srcDir}/types/src/main.ts`,
   `${dir}${srcDir}/jsonrpc/src/node/main.ts`
 ];
-export const gitHash = '60a5a7825e6f54f57917091f394fd8db7d1724bc'
+export const gitHash = '901fd40345060d159f07d234bbc967966a929a34'
 let outFname = 'tsprotocol.go';
 let fda: number, fdb: number, fde: number;  // file descriptors
 
diff --git a/internal/lsp/semantic.go b/internal/lsp/semantic.go
index b0f42d6..b669fab 100644
--- a/internal/lsp/semantic.go
+++ b/internal/lsp/semantic.go
@@ -69,11 +69,13 @@
 		return nil, pgf.ParseErr
 	}
 	e := &encoded{
-		ctx:  ctx,
-		pgf:  pgf,
-		rng:  rng,
-		ti:   info,
-		fset: snapshot.FileSet(),
+		ctx:      ctx,
+		pgf:      pgf,
+		rng:      rng,
+		ti:       info,
+		fset:     snapshot.FileSet(),
+		tokTypes: s.session.Options().SemanticTypes,
+		tokMods:  s.session.Options().SemanticMods,
 	}
 	if err := e.init(); err != nil {
 		return nil, err
@@ -174,11 +176,12 @@
 	// the generated data
 	items []semItem
 
-	ctx  context.Context
-	pgf  *source.ParsedGoFile
-	rng  *protocol.Range
-	ti   *types.Info
-	fset *token.FileSet
+	ctx               context.Context
+	tokTypes, tokMods []string
+	pgf               *source.ParsedGoFile
+	rng               *protocol.Range
+	ti                *types.Info
+	fset              *token.FileSet
 	// allowed starting and ending token.Pos, set by init
 	// used to avoid looking at declarations not in range
 	start, end token.Pos
@@ -509,6 +512,7 @@
 		}
 		return e.items[i].start < e.items[j].start
 	})
+	typeMap, modMap := e.maps()
 	// each semantic token needs five values
 	// (see Integer Encoding for Tokens in the LSP spec)
 	x := make([]float64, 5*len(e.items))
@@ -524,10 +528,10 @@
 			x[j+1] = e.items[i].start - e.items[i-1].start
 		}
 		x[j+2] = e.items[i].len
-		x[j+3] = float64(SemanticMemo.TypeMap[e.items[i].typeStr])
+		x[j+3] = float64(typeMap[e.items[i].typeStr])
 		mask := 0
 		for _, s := range e.items[i].mods {
-			mask |= SemanticMemo.ModMap[s]
+			mask |= modMap[s]
 		}
 		x[j+4] = float64(mask)
 	}
@@ -565,51 +569,38 @@
 	panic(msg)
 }
 
-// SemMemo supports semantic token translations between numbers and strings
-type SemMemo struct {
-	tokTypes, tokMods []string
-	// these exported fields are used in the 'gopls semtok' command
-	TypeMap map[tokenType]int
-	ModMap  map[string]int
-}
-
-var SemanticMemo *SemMemo
-
-// Type returns a string equivalent of the type, for gopls semtok
-func (m *SemMemo) Type(n int) string {
-	if n >= 0 && n < len(m.tokTypes) {
-		return m.tokTypes[n]
+// SemType returns a string equivalent of the type, for gopls semtok
+func SemType(n int) string {
+	tokTypes := SemanticTypes()
+	tokMods := SemanticModifiers()
+	if n >= 0 && n < len(tokTypes) {
+		return tokTypes[n]
 	}
-	return fmt.Sprintf("?%d[%d,%d]?", n, len(m.tokTypes), len(m.tokMods))
+	return fmt.Sprintf("?%d[%d,%d]?", n, len(tokTypes), len(tokMods))
 }
 
-// Mods returns the []string equivalent of the mods, for gopls semtok.
-func (m *SemMemo) Mods(n int) []string {
+// SemMods returns the []string equivalent of the mods, for gopls semtok.
+func SemMods(n int) []string {
+	tokMods := SemanticModifiers()
 	mods := []string{}
-	for i := 0; i < len(m.tokMods); i++ {
+	for i := 0; i < len(tokMods); i++ {
 		if (n & (1 << uint(i))) != 0 {
-			mods = append(mods, m.tokMods[i])
+			mods = append(mods, tokMods[i])
 		}
 	}
 	return mods
 }
 
-// save what the client sent
-func rememberToks(toks []string, mods []string) {
-	SemanticMemo = &SemMemo{
-		tokTypes: toks,
-		tokMods:  mods,
-		TypeMap:  make(map[tokenType]int),
-		ModMap:   make(map[string]int),
+func (e *encoded) maps() (map[tokenType]int, map[string]int) {
+	tmap := make(map[tokenType]int)
+	mmap := make(map[string]int)
+	for i, t := range e.tokTypes {
+		tmap[tokenType(t)] = i
 	}
-	for i, t := range toks {
-		SemanticMemo.TypeMap[tokenType(t)] = i
+	for i, m := range e.tokMods {
+		mmap[m] = 1 << uint(i) // go 1.12 compatibility
 	}
-	for i, m := range mods {
-		SemanticMemo.ModMap[m] = 1 << uint(i)
-	}
-	// we could have pruned or rearranged them.
-	// But then change the list in cmd.go too
+	return tmap, mmap
 }
 
 // SemanticTypes to use in case there is no client, as in the command line, or tests
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index dceb2a7..c2f5004 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -23,15 +23,15 @@
 // messages on on the supplied stream.
 func NewServer(session source.Session, client protocol.Client) *Server {
 	return &Server{
-		delivered:            make(map[span.URI]sentDiagnostics),
-		gcOptimizatonDetails: make(map[span.URI]struct{}),
-		watchedDirectories:   make(map[span.URI]struct{}),
-		changedFiles:         make(map[span.URI]struct{}),
-		session:              session,
-		client:               client,
-		diagnosticsSema:      make(chan struct{}, concurrentAnalyses),
-		progress:             newProgressTracker(client),
-		debouncer:            newDebouncer(),
+		delivered:             make(map[span.URI]sentDiagnostics),
+		gcOptimizationDetails: make(map[span.URI]struct{}),
+		watchedDirectories:    make(map[span.URI]struct{}),
+		changedFiles:          make(map[span.URI]struct{}),
+		session:               session,
+		client:                client,
+		diagnosticsSema:       make(chan struct{}, concurrentAnalyses),
+		progress:              newProgressTracker(client),
+		debouncer:             newDebouncer(),
 	}
 }
 
@@ -65,7 +65,8 @@
 	stateMu sync.Mutex
 	state   serverState
 
-	session source.Session
+	session   source.Session
+	clientPID int
 
 	// notifications generated before serverInitialized
 	notifications []*protocol.ShowMessageParams
@@ -93,7 +94,7 @@
 	// optimization details to be included in the diagnostics. The key is the
 	// directory of the package.
 	gcOptimizationDetailsMu sync.Mutex
-	gcOptimizatonDetails    map[span.URI]struct{}
+	gcOptimizationDetails   map[span.URI]struct{}
 
 	// diagnosticsSema limits the concurrency of diagnostics runs, which can be expensive.
 	diagnosticsSema chan struct{}
@@ -106,10 +107,10 @@
 
 // sentDiagnostics is used to cache diagnostics that have been sent for a given file.
 type sentDiagnostics struct {
-	id           source.VersionedFileIdentity
-	sorted       []*source.Diagnostic
-	withAnalysis bool
-	snapshotID   uint64
+	id              source.VersionedFileIdentity
+	sorted          []*source.Diagnostic
+	includeAnalysis bool
+	snapshotID      uint64
 }
 
 func (s *Server) workDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error {
diff --git a/internal/lsp/server_gen.go b/internal/lsp/server_gen.go
index 99fc6b0..258e922 100644
--- a/internal/lsp/server_gen.go
+++ b/internal/lsp/server_gen.go
@@ -36,8 +36,8 @@
 	return s.didChange(ctx, params)
 }
 
-func (s *Server) DidChangeConfiguration(ctx context.Context, changed *protocol.DidChangeConfigurationParams) error {
-	return s.didChangeConfiguration(ctx, changed)
+func (s *Server) DidChangeConfiguration(ctx context.Context, _ *protocol.DidChangeConfigurationParams) error {
+	return s.didChangeConfiguration(ctx, nil)
 }
 
 func (s *Server) DidChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error {
@@ -116,6 +116,10 @@
 	return notImplemented("LogTrace")
 }
 
+func (s *Server) Moniker(context.Context, *protocol.MonikerParams) ([]protocol.Moniker, error) {
+	return nil, notImplemented("Moniker")
+}
+
 func (s *Server) NonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
 	return s.nonstandardRequest(ctx, method, params)
 }
@@ -152,6 +156,10 @@
 	return nil, notImplemented("Resolve")
 }
 
+func (s *Server) ResolveCodeAction(context.Context, *protocol.CodeAction) (*protocol.CodeAction, error) {
+	return nil, notImplemented("ResolveCodeAction")
+}
+
 func (s *Server) ResolveCodeLens(context.Context, *protocol.CodeLens) (*protocol.CodeLens, error) {
 	return nil, notImplemented("ResolveCodeLens")
 }
diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go
index 69b749c..de5493f 100755
--- a/internal/lsp/source/api_json.go
+++ b/internal/lsp/source/api_json.go
@@ -2,4 +2,4 @@
 
 package source
 
-const GeneratedAPIJSON = "{\"Options\":{\"Debugging\":[{\"Name\":\"verboseOutput\",\"Type\":\"bool\",\"Doc\":\"verboseOutput enables additional debug logging.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"completionBudget\",\"Type\":\"time.Duration\",\"Doc\":\"completionBudget is the soft latency goal for completion requests. Most\\nrequests finish in a couple milliseconds, but in some cases deep\\ncompletions can take much longer. As we use up our budget we\\ndynamically reduce the search scope to ensure we return timely\\nresults. Zero means unlimited.\\n\",\"EnumValues\":null,\"Default\":\"\\\"100ms\\\"\"}],\"Experimental\":[{\"Name\":\"analyses\",\"Type\":\"map[string]bool\",\"Doc\":\"analyses specify analyses that the user would like to enable or disable.\\nA map of the names of analysis passes that should be enabled/disabled.\\nA full list of analyzers that gopls uses can be found [here](analyzers.md)\\n\\nExample Usage:\\n```json5\\n...\\n\\\"analyses\\\": {\\n  \\\"unreachable\\\": false, // Disable the unreachable analyzer.\\n  \\\"unusedparams\\\": true  // Enable the unusedparams analyzer.\\n}\\n...\\n```\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"codelens\",\"Type\":\"map[string]bool\",\"Doc\":\"codelens overrides the enabled/disabled state of code lenses. See the \\\"Code Lenses\\\"\\nsection of settings.md for the list of supported lenses.\\n\\nExample Usage:\\n```json5\\n\\\"gopls\\\": {\\n...\\n  \\\"codelens\\\": {\\n    \\\"generate\\\": false,  // Don't show the `go generate` lens.\\n    \\\"gc_details\\\": true  // Show a code lens toggling the display of gc's choices.\\n  }\\n...\\n}\\n```\\n\",\"EnumValues\":null,\"Default\":\"{\\\"gc_details\\\":false,\\\"generate\\\":true,\\\"regenerate_cgo\\\":true,\\\"tidy\\\":true,\\\"upgrade_dependency\\\":true,\\\"vendor\\\":true}\"},{\"Name\":\"completionDocumentation\",\"Type\":\"bool\",\"Doc\":\"completionDocumentation enables documentation with completion results.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"completeUnimported\",\"Type\":\"bool\",\"Doc\":\"completeUnimported enables completion for packages that you do not currently import.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"deepCompletion\",\"Type\":\"bool\",\"Doc\":\"deepCompletion enables the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.\\n\\nConsider this example:\\n\\n```go\\npackage main\\n\\nimport \\\"fmt\\\"\\n\\ntype wrapString struct {\\n    str string\\n}\\n\\nfunc main() {\\n    x := wrapString{\\\"hello world\\\"}\\n    fmt.Printf(\\u003c\\u003e)\\n}\\n```\\n\\nAt the location of the `\\u003c\\u003e` in this program, deep completion would suggest the result `x.str`.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"matcher\",\"Type\":\"enum\",\"Doc\":\"matcher sets the algorithm that is used when calculating completion candidates.\\n\",\"EnumValues\":[{\"Value\":\"\\\"CaseInsensitive\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"CaseSensitive\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"Fuzzy\\\"\",\"Doc\":\"\"}],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"annotations\",\"Type\":\"map[string]bool\",\"Doc\":\"annotations suppress various kinds of optimization diagnostics\\nthat would be reported by the gc_details command.\\n * noNilcheck suppresses display of nilchecks.\\n * noEscape suppresses escape choices.\\n * noInline suppresses inlining choices.\\n * noBounds suppresses bounds checking diagnostics.\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"staticcheck\",\"Type\":\"bool\",\"Doc\":\"staticcheck enables additional analyses from staticcheck.io.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"symbolMatcher\",\"Type\":\"enum\",\"Doc\":\"symbolMatcher sets the algorithm that is used when finding workspace symbols.\\n\",\"EnumValues\":[{\"Value\":\"\\\"CaseInsensitive\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"CaseSensitive\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"Fuzzy\\\"\",\"Doc\":\"\"}],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"symbolStyle\",\"Type\":\"enum\",\"Doc\":\"symbolStyle controls how symbols are qualified in symbol responses.\\n\\nExample Usage:\\n```json5\\n\\\"gopls\\\": {\\n...\\n  \\\"symbolStyle\\\": \\\"dynamic\\\",\\n...\\n}\\n```\\n\",\"EnumValues\":[{\"Value\":\"\\\"Dynamic\\\"\",\"Doc\":\"`\\\"Dynamic\\\"` uses whichever qualifier results in the highest scoring\\nmatch for the given symbol query. Here a \\\"qualifier\\\" is any \\\"/\\\" or \\\".\\\"\\ndelimited suffix of the fully qualified symbol. i.e. \\\"to/pkg.Foo.Field\\\" or\\njust \\\"Foo.Field\\\".\\n\"},{\"Value\":\"\\\"Full\\\"\",\"Doc\":\"`\\\"Full\\\"` is fully qualified symbols, i.e.\\n\\\"path/to/pkg.Foo.Field\\\".\\n\"},{\"Value\":\"\\\"Package\\\"\",\"Doc\":\"`\\\"Package\\\"` is package qualified symbols i.e.\\n\\\"pkg.Foo.Field\\\".\\n\"}],\"Default\":\"\\\"Package\\\"\"},{\"Name\":\"linksInHover\",\"Type\":\"bool\",\"Doc\":\"linksInHover toggles the presence of links to documentation in hover.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"tempModfile\",\"Type\":\"bool\",\"Doc\":\"tempModfile controls the use of the -modfile flag in Go 1.14.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"importShortcut\",\"Type\":\"enum\",\"Doc\":\"importShortcut specifies whether import statements should link to\\ndocumentation or go to definitions.\\n\",\"EnumValues\":[{\"Value\":\"\\\"Both\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"Definition\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"Link\\\"\",\"Doc\":\"\"}],\"Default\":\"\\\"Both\\\"\"},{\"Name\":\"verboseWorkDoneProgress\",\"Type\":\"bool\",\"Doc\":\"verboseWorkDoneProgress controls whether the LSP server should send\\nprogress reports for all work done outside the scope of an RPC.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"semanticTokens\",\"Type\":\"bool\",\"Doc\":\"semanticTokens controls whether the LSP server will send\\nsemantic tokens to the client.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"expandWorkspaceToModule\",\"Type\":\"bool\",\"Doc\":\"expandWorkspaceToModule instructs `gopls` to expand the scope of the workspace to include the\\nmodules containing the workspace folders. Set this to false to avoid loading\\nyour entire module. This is particularly useful for those working in a monorepo.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"experimentalWorkspaceModule\",\"Type\":\"bool\",\"Doc\":\"experimentalWorkspaceModule opts a user into the experimental support\\nfor multi-module workspaces.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"experimentalDiagnosticsDelay\",\"Type\":\"time.Duration\",\"Doc\":\"experimentalDiagnosticsDelay controls the amount of time that gopls waits\\nafter the most recent file modification before computing deep diagnostics.\\nSimple diagnostics (parsing and type-checking) are always run immediately\\non recently modified packages.\\n\\nThis option must be set to a valid duration string, for example `\\\"250ms\\\"`.\\n\",\"EnumValues\":null,\"Default\":\"\\\"0s\\\"\"},{\"Name\":\"experimentalPackageCacheKey\",\"Type\":\"bool\",\"Doc\":\"experimentalPackageCacheKey controls whether to use a coarser cache key\\nfor package type information to increase cache hits. This setting removes\\nthe user's environment, build flags, and working directory from the cache\\nkey, which should be a safe change as all relevant inputs into the type\\nchecking pass are already hashed into the key. This is temporarily guarded\\nby an experiment because caching behavior is subtle and difficult to\\ncomprehensively test.\\n\",\"EnumValues\":null,\"Default\":\"false\"}],\"User\":[{\"Name\":\"buildFlags\",\"Type\":\"[]string\",\"Doc\":\"buildFlags is the set of flags passed on to the build system when invoked.\\nIt is applied to queries like `go list`, which is used when discovering files.\\nThe most common use is to set `-tags`.\\n\",\"EnumValues\":null,\"Default\":\"[]\"},{\"Name\":\"env\",\"Type\":\"map[string]string\",\"Doc\":\"env adds environment variables to external commands run by `gopls`, most notably `go list`.\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"hoverKind\",\"Type\":\"enum\",\"Doc\":\"hoverKind controls the information that appears in the hover text.\\nSingleLine and Structured are intended for use only by authors of editor plugins.\\n\",\"EnumValues\":[{\"Value\":\"\\\"FullDocumentation\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"NoDocumentation\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"SingleLine\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"Structured\\\"\",\"Doc\":\"`\\\"Structured\\\"` is an experimental setting that returns a structured hover format.\\nThis format separates the signature from the documentation, so that the client\\ncan do more manipulation of these fields.\\n\\nThis should only be used by clients that support this behavior.\\n\"},{\"Value\":\"\\\"SynopsisDocumentation\\\"\",\"Doc\":\"\"}],\"Default\":\"\\\"FullDocumentation\\\"\"},{\"Name\":\"usePlaceholders\",\"Type\":\"bool\",\"Doc\":\"placeholders enables placeholders for function parameters or struct fields in completion responses.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"linkTarget\",\"Type\":\"string\",\"Doc\":\"linkTarget controls where documentation links go.\\nIt might be one of:\\n\\n* `\\\"godoc.org\\\"`\\n* `\\\"pkg.go.dev\\\"`\\n\\nIf company chooses to use its own `godoc.org`, its address can be used as well.\\n\",\"EnumValues\":null,\"Default\":\"\\\"pkg.go.dev\\\"\"},{\"Name\":\"local\",\"Type\":\"string\",\"Doc\":\"local is the equivalent of the `goimports -local` flag, which puts imports beginning with this string after 3rd-party packages.\\nIt should be the prefix of the import path whose imports should be grouped separately.\\n\",\"EnumValues\":null,\"Default\":\"\\\"\\\"\"},{\"Name\":\"gofumpt\",\"Type\":\"bool\",\"Doc\":\"gofumpt indicates if we should run gofumpt formatting.\\n\",\"EnumValues\":null,\"Default\":\"false\"}]},\"Commands\":[{\"Command\":\"gopls.generate\",\"Title\":\"Run go generate\",\"Doc\":\"generate runs `go generate` for a given directory.\\n\"},{\"Command\":\"gopls.fill_struct\",\"Title\":\"Fill struct\",\"Doc\":\"fill_struct is a gopls command to fill a struct with default\\nvalues.\\n\"},{\"Command\":\"gopls.regenerate_cgo\",\"Title\":\"Regenerate cgo\",\"Doc\":\"regenerate_cgo regenerates cgo definitions.\\n\"},{\"Command\":\"gopls.test\",\"Title\":\"Run test(s)\",\"Doc\":\"test runs `go test` for a specific test function.\\n\"},{\"Command\":\"gopls.tidy\",\"Title\":\"Run go mod tidy\",\"Doc\":\"tidy runs `go mod tidy` for a module.\\n\"},{\"Command\":\"gopls.undeclared_name\",\"Title\":\"Undeclared name\",\"Doc\":\"undeclared_name adds a variable declaration for an undeclared\\nname.\\n\"},{\"Command\":\"gopls.upgrade_dependency\",\"Title\":\"Upgrade dependency\",\"Doc\":\"upgrade_dependency upgrades a dependency.\\n\"},{\"Command\":\"gopls.vendor\",\"Title\":\"Run go mod vendor\",\"Doc\":\"vendor runs `go mod vendor` for a module.\\n\"},{\"Command\":\"gopls.extract_variable\",\"Title\":\"Extract to variable\",\"Doc\":\"extract_variable extracts an expression to a variable.\\n\"},{\"Command\":\"gopls.extract_function\",\"Title\":\"Extract to function\",\"Doc\":\"extract_function extracts statements to a function.\\n\"},{\"Command\":\"gopls.gc_details\",\"Title\":\"Toggle gc_details\",\"Doc\":\"gc_details controls calculation of gc annotations.\\n\"},{\"Command\":\"gopls.generate_gopls_mod\",\"Title\":\"Generate gopls.mod\",\"Doc\":\"generate_gopls_mod (re)generates the gopls.mod file.\\n\"}],\"Lenses\":[{\"Lens\":\"generate\",\"Title\":\"Run go generate\",\"Doc\":\"generate runs `go generate` for a given directory.\\n\"},{\"Lens\":\"regenerate_cgo\",\"Title\":\"Regenerate cgo\",\"Doc\":\"regenerate_cgo regenerates cgo definitions.\\n\"},{\"Lens\":\"test\",\"Title\":\"Run test(s)\",\"Doc\":\"test runs `go test` for a specific test function.\\n\"},{\"Lens\":\"tidy\",\"Title\":\"Run go mod tidy\",\"Doc\":\"tidy runs `go mod tidy` for a module.\\n\"},{\"Lens\":\"upgrade_dependency\",\"Title\":\"Upgrade dependency\",\"Doc\":\"upgrade_dependency upgrades a dependency.\\n\"},{\"Lens\":\"vendor\",\"Title\":\"Run go mod vendor\",\"Doc\":\"vendor runs `go mod vendor` for a module.\\n\"},{\"Lens\":\"gc_details\",\"Title\":\"Toggle gc_details\",\"Doc\":\"gc_details controls calculation of gc annotations.\\n\"}]}"
+const GeneratedAPIJSON = "{\"Options\":{\"Debugging\":[{\"Name\":\"verboseOutput\",\"Type\":\"bool\",\"Doc\":\"verboseOutput enables additional debug logging.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"completionBudget\",\"Type\":\"time.Duration\",\"Doc\":\"completionBudget is the soft latency goal for completion requests. Most\\nrequests finish in a couple milliseconds, but in some cases deep\\ncompletions can take much longer. As we use up our budget we\\ndynamically reduce the search scope to ensure we return timely\\nresults. Zero means unlimited.\\n\",\"EnumValues\":null,\"Default\":\"\\\"100ms\\\"\"}],\"Experimental\":[{\"Name\":\"analyses\",\"Type\":\"map[string]bool\",\"Doc\":\"analyses specify analyses that the user would like to enable or disable.\\nA map of the names of analysis passes that should be enabled/disabled.\\nA full list of analyzers that gopls uses can be found [here](analyzers.md)\\n\\nExample Usage:\\n```json5\\n...\\n\\\"analyses\\\": {\\n  \\\"unreachable\\\": false, // Disable the unreachable analyzer.\\n  \\\"unusedparams\\\": true  // Enable the unusedparams analyzer.\\n}\\n...\\n```\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"codelens\",\"Type\":\"map[string]bool\",\"Doc\":\"codelens overrides the enabled/disabled state of code lenses. See the \\\"Code Lenses\\\"\\nsection of settings.md for the list of supported lenses.\\n\\nExample Usage:\\n```json5\\n\\\"gopls\\\": {\\n...\\n  \\\"codelens\\\": {\\n    \\\"generate\\\": false,  // Don't show the `go generate` lens.\\n    \\\"gc_details\\\": true  // Show a code lens toggling the display of gc's choices.\\n  }\\n...\\n}\\n```\\n\",\"EnumValues\":null,\"Default\":\"{\\\"gc_details\\\":false,\\\"generate\\\":true,\\\"regenerate_cgo\\\":true,\\\"tidy\\\":true,\\\"upgrade_dependency\\\":true,\\\"vendor\\\":true}\"},{\"Name\":\"completionDocumentation\",\"Type\":\"bool\",\"Doc\":\"completionDocumentation enables documentation with completion results.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"completeUnimported\",\"Type\":\"bool\",\"Doc\":\"completeUnimported enables completion for packages that you do not currently import.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"deepCompletion\",\"Type\":\"bool\",\"Doc\":\"deepCompletion enables the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.\\n\\nConsider this example:\\n\\n```go\\npackage main\\n\\nimport \\\"fmt\\\"\\n\\ntype wrapString struct {\\n    str string\\n}\\n\\nfunc main() {\\n    x := wrapString{\\\"hello world\\\"}\\n    fmt.Printf(\\u003c\\u003e)\\n}\\n```\\n\\nAt the location of the `\\u003c\\u003e` in this program, deep completion would suggest the result `x.str`.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"matcher\",\"Type\":\"enum\",\"Doc\":\"matcher sets the algorithm that is used when calculating completion candidates.\\n\",\"EnumValues\":[{\"Value\":\"\\\"CaseInsensitive\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"CaseSensitive\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"Fuzzy\\\"\",\"Doc\":\"\"}],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"annotations\",\"Type\":\"map[string]bool\",\"Doc\":\"annotations suppress various kinds of optimization diagnostics\\nthat would be reported by the gc_details command.\\n * noNilcheck suppresses display of nilchecks.\\n * noEscape suppresses escape choices.\\n * noInline suppresses inlining choices.\\n * noBounds suppresses bounds checking diagnostics.\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"staticcheck\",\"Type\":\"bool\",\"Doc\":\"staticcheck enables additional analyses from staticcheck.io.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"symbolMatcher\",\"Type\":\"enum\",\"Doc\":\"symbolMatcher sets the algorithm that is used when finding workspace symbols.\\n\",\"EnumValues\":[{\"Value\":\"\\\"CaseInsensitive\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"CaseSensitive\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"Fuzzy\\\"\",\"Doc\":\"\"}],\"Default\":\"\\\"Fuzzy\\\"\"},{\"Name\":\"symbolStyle\",\"Type\":\"enum\",\"Doc\":\"symbolStyle controls how symbols are qualified in symbol responses.\\n\\nExample Usage:\\n```json5\\n\\\"gopls\\\": {\\n...\\n  \\\"symbolStyle\\\": \\\"dynamic\\\",\\n...\\n}\\n```\\n\",\"EnumValues\":[{\"Value\":\"\\\"Dynamic\\\"\",\"Doc\":\"`\\\"Dynamic\\\"` uses whichever qualifier results in the highest scoring\\nmatch for the given symbol query. Here a \\\"qualifier\\\" is any \\\"/\\\" or \\\".\\\"\\ndelimited suffix of the fully qualified symbol. i.e. \\\"to/pkg.Foo.Field\\\" or\\njust \\\"Foo.Field\\\".\\n\"},{\"Value\":\"\\\"Full\\\"\",\"Doc\":\"`\\\"Full\\\"` is fully qualified symbols, i.e.\\n\\\"path/to/pkg.Foo.Field\\\".\\n\"},{\"Value\":\"\\\"Package\\\"\",\"Doc\":\"`\\\"Package\\\"` is package qualified symbols i.e.\\n\\\"pkg.Foo.Field\\\".\\n\"}],\"Default\":\"\\\"Package\\\"\"},{\"Name\":\"linksInHover\",\"Type\":\"bool\",\"Doc\":\"linksInHover toggles the presence of links to documentation in hover.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"tempModfile\",\"Type\":\"bool\",\"Doc\":\"tempModfile controls the use of the -modfile flag in Go 1.14.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"importShortcut\",\"Type\":\"enum\",\"Doc\":\"importShortcut specifies whether import statements should link to\\ndocumentation or go to definitions.\\n\",\"EnumValues\":[{\"Value\":\"\\\"Both\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"Definition\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"Link\\\"\",\"Doc\":\"\"}],\"Default\":\"\\\"Both\\\"\"},{\"Name\":\"verboseWorkDoneProgress\",\"Type\":\"bool\",\"Doc\":\"verboseWorkDoneProgress controls whether the LSP server should send\\nprogress reports for all work done outside the scope of an RPC.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"semanticTokens\",\"Type\":\"bool\",\"Doc\":\"semanticTokens controls whether the LSP server will send\\nsemantic tokens to the client.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"expandWorkspaceToModule\",\"Type\":\"bool\",\"Doc\":\"expandWorkspaceToModule instructs `gopls` to expand the scope of the workspace to include the\\nmodules containing the workspace folders. Set this to false to avoid loading\\nyour entire module. This is particularly useful for those working in a monorepo.\\n\",\"EnumValues\":null,\"Default\":\"true\"},{\"Name\":\"experimentalWorkspaceModule\",\"Type\":\"bool\",\"Doc\":\"experimentalWorkspaceModule opts a user into the experimental support\\nfor multi-module workspaces.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"experimentalDiagnosticsDelay\",\"Type\":\"time.Duration\",\"Doc\":\"experimentalDiagnosticsDelay controls the amount of time that gopls waits\\nafter the most recent file modification before computing deep diagnostics.\\nSimple diagnostics (parsing and type-checking) are always run immediately\\non recently modified packages.\\n\\nThis option must be set to a valid duration string, for example `\\\"250ms\\\"`.\\n\",\"EnumValues\":null,\"Default\":\"\\\"0s\\\"\"},{\"Name\":\"experimentalPackageCacheKey\",\"Type\":\"bool\",\"Doc\":\"experimentalPackageCacheKey controls whether to use a coarser cache key\\nfor package type information to increase cache hits. This setting removes\\nthe user's environment, build flags, and working directory from the cache\\nkey, which should be a safe change as all relevant inputs into the type\\nchecking pass are already hashed into the key. This is temporarily guarded\\nby an experiment because caching behavior is subtle and difficult to\\ncomprehensively test.\\n\",\"EnumValues\":null,\"Default\":\"false\"}],\"User\":[{\"Name\":\"buildFlags\",\"Type\":\"[]string\",\"Doc\":\"buildFlags is the set of flags passed on to the build system when invoked.\\nIt is applied to queries like `go list`, which is used when discovering files.\\nThe most common use is to set `-tags`.\\n\",\"EnumValues\":null,\"Default\":\"[]\"},{\"Name\":\"env\",\"Type\":\"map[string]string\",\"Doc\":\"env adds environment variables to external commands run by `gopls`, most notably `go list`.\\n\",\"EnumValues\":null,\"Default\":\"{}\"},{\"Name\":\"hoverKind\",\"Type\":\"enum\",\"Doc\":\"hoverKind controls the information that appears in the hover text.\\nSingleLine and Structured are intended for use only by authors of editor plugins.\\n\",\"EnumValues\":[{\"Value\":\"\\\"FullDocumentation\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"NoDocumentation\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"SingleLine\\\"\",\"Doc\":\"\"},{\"Value\":\"\\\"Structured\\\"\",\"Doc\":\"`\\\"Structured\\\"` is an experimental setting that returns a structured hover format.\\nThis format separates the signature from the documentation, so that the client\\ncan do more manipulation of these fields.\\n\\nThis should only be used by clients that support this behavior.\\n\"},{\"Value\":\"\\\"SynopsisDocumentation\\\"\",\"Doc\":\"\"}],\"Default\":\"\\\"FullDocumentation\\\"\"},{\"Name\":\"usePlaceholders\",\"Type\":\"bool\",\"Doc\":\"placeholders enables placeholders for function parameters or struct fields in completion responses.\\n\",\"EnumValues\":null,\"Default\":\"false\"},{\"Name\":\"linkTarget\",\"Type\":\"string\",\"Doc\":\"linkTarget controls where documentation links go.\\nIt might be one of:\\n\\n* `\\\"godoc.org\\\"`\\n* `\\\"pkg.go.dev\\\"`\\n\\nIf company chooses to use its own `godoc.org`, its address can be used as well.\\n\",\"EnumValues\":null,\"Default\":\"\\\"pkg.go.dev\\\"\"},{\"Name\":\"local\",\"Type\":\"string\",\"Doc\":\"local is the equivalent of the `goimports -local` flag, which puts imports beginning with this string after 3rd-party packages.\\nIt should be the prefix of the import path whose imports should be grouped separately.\\n\",\"EnumValues\":null,\"Default\":\"\\\"\\\"\"},{\"Name\":\"gofumpt\",\"Type\":\"bool\",\"Doc\":\"gofumpt indicates if we should run gofumpt formatting.\\n\",\"EnumValues\":null,\"Default\":\"false\"}]},\"Commands\":[{\"Command\":\"gopls.generate\",\"Title\":\"Run go generate\",\"Doc\":\"generate runs `go generate` for a given directory.\\n\"},{\"Command\":\"gopls.fill_struct\",\"Title\":\"Fill struct\",\"Doc\":\"fill_struct is a gopls command to fill a struct with default\\nvalues.\\n\"},{\"Command\":\"gopls.regenerate_cgo\",\"Title\":\"Regenerate cgo\",\"Doc\":\"regenerate_cgo regenerates cgo definitions.\\n\"},{\"Command\":\"gopls.test\",\"Title\":\"Run test(s)\",\"Doc\":\"test runs `go test` for a specific test function.\\n\"},{\"Command\":\"gopls.tidy\",\"Title\":\"Run go mod tidy\",\"Doc\":\"tidy runs `go mod tidy` for a module.\\n\"},{\"Command\":\"gopls.undeclared_name\",\"Title\":\"Undeclared name\",\"Doc\":\"undeclared_name adds a variable declaration for an undeclared\\nname.\\n\"},{\"Command\":\"gopls.add_dependency\",\"Title\":\"Add dependency\",\"Doc\":\"add_dependency adds a dependency.\\n\"},{\"Command\":\"gopls.upgrade_dependency\",\"Title\":\"Upgrade dependency\",\"Doc\":\"upgrade_dependency upgrades a dependency.\\n\"},{\"Command\":\"gopls.remove_dependency\",\"Title\":\"Remove dependency\",\"Doc\":\"remove_dependency removes a dependency.\\n\"},{\"Command\":\"gopls.vendor\",\"Title\":\"Run go mod vendor\",\"Doc\":\"vendor runs `go mod vendor` for a module.\\n\"},{\"Command\":\"gopls.extract_variable\",\"Title\":\"Extract to variable\",\"Doc\":\"extract_variable extracts an expression to a variable.\\n\"},{\"Command\":\"gopls.extract_function\",\"Title\":\"Extract to function\",\"Doc\":\"extract_function extracts statements to a function.\\n\"},{\"Command\":\"gopls.gc_details\",\"Title\":\"Toggle gc_details\",\"Doc\":\"gc_details controls calculation of gc annotations.\\n\"},{\"Command\":\"gopls.generate_gopls_mod\",\"Title\":\"Generate gopls.mod\",\"Doc\":\"generate_gopls_mod (re)generates the gopls.mod file.\\n\"}],\"Lenses\":[{\"Lens\":\"generate\",\"Title\":\"Run go generate\",\"Doc\":\"generate runs `go generate` for a given directory.\\n\"},{\"Lens\":\"regenerate_cgo\",\"Title\":\"Regenerate cgo\",\"Doc\":\"regenerate_cgo regenerates cgo definitions.\\n\"},{\"Lens\":\"test\",\"Title\":\"Run test(s)\",\"Doc\":\"test runs `go test` for a specific test function.\\n\"},{\"Lens\":\"tidy\",\"Title\":\"Run go mod tidy\",\"Doc\":\"tidy runs `go mod tidy` for a module.\\n\"},{\"Lens\":\"upgrade_dependency\",\"Title\":\"Upgrade dependency\",\"Doc\":\"upgrade_dependency upgrades a dependency.\\n\"},{\"Lens\":\"vendor\",\"Title\":\"Run go mod vendor\",\"Doc\":\"vendor runs `go mod vendor` for a module.\\n\"},{\"Lens\":\"gc_details\",\"Title\":\"Toggle gc_details\",\"Doc\":\"gc_details controls calculation of gc annotations.\\n\"}]}"
diff --git a/internal/lsp/source/command.go b/internal/lsp/source/command.go
index 4b85b53..c6488f9 100644
--- a/internal/lsp/source/command.go
+++ b/internal/lsp/source/command.go
@@ -23,9 +23,8 @@
 	Title string
 	Name  string
 
-	// Synchronous controls whether the command executes synchronously within the
-	// ExecuteCommand request (applying suggested fixes is always synchronous).
-	Synchronous bool
+	// Async controls whether the command executes asynchronously.
+	Async bool
 
 	// appliesFn is an optional field to indicate whether or not a command can
 	// be applied to the given inputs. If it returns false, we should not
@@ -64,7 +63,9 @@
 	CommandTest,
 	CommandTidy,
 	CommandUndeclaredName,
+	CommandAddDependency,
 	CommandUpgradeDependency,
+	CommandRemoveDependency,
 	CommandVendor,
 	CommandExtractVariable,
 	CommandExtractFunction,
@@ -77,6 +78,7 @@
 	CommandTest = &Command{
 		Name:  "test",
 		Title: "Run test(s)",
+		Async: true,
 	}
 
 	// CommandGenerate runs `go generate` for a given directory.
@@ -97,12 +99,24 @@
 		Title: "Run go mod vendor",
 	}
 
+	// CommandAddDependency adds a dependency.
+	CommandAddDependency = &Command{
+		Name:  "add_dependency",
+		Title: "Add dependency",
+	}
+
 	// CommandUpgradeDependency upgrades a dependency.
 	CommandUpgradeDependency = &Command{
 		Name:  "upgrade_dependency",
 		Title: "Upgrade dependency",
 	}
 
+	// CommandRemoveDependency removes a dependency.
+	CommandRemoveDependency = &Command{
+		Name:  "remove_dependency",
+		Title: "Remove dependency",
+	}
+
 	// CommandRegenerateCgo regenerates cgo definitions.
 	CommandRegenerateCgo = &Command{
 		Name:  "regenerate_cgo",
@@ -155,9 +169,8 @@
 
 	// CommandGenerateGoplsMod (re)generates the gopls.mod file.
 	CommandGenerateGoplsMod = &Command{
-		Name:        "generate_gopls_mod",
-		Title:       "Generate gopls.mod",
-		Synchronous: true,
+		Name:  "generate_gopls_mod",
+		Title: "Generate gopls.mod",
 	}
 )
 
diff --git a/internal/lsp/source/completion/completion_builtin.go b/internal/lsp/source/completion/builtin.go
similarity index 100%
rename from internal/lsp/source/completion/completion_builtin.go
rename to internal/lsp/source/completion/builtin.go
diff --git a/internal/lsp/source/completion/completion.go b/internal/lsp/source/completion/completion.go
index 417b287..78dac5b 100644
--- a/internal/lsp/source/completion/completion.go
+++ b/internal/lsp/source/completion/completion.go
@@ -510,7 +510,7 @@
 		opts: &completionOptions{
 			matcher:           opts.Matcher,
 			unimported:        opts.CompleteUnimported,
-			documentation:     opts.CompletionDocumentation,
+			documentation:     opts.CompletionDocumentation && opts.HoverKind != source.NoDocumentation,
 			fullDocumentation: opts.HoverKind == source.FullDocumentation,
 			placeholders:      opts.UsePlaceholders,
 			literal:           opts.LiteralCompletions && opts.InsertTextFormat == protocol.SnippetTextFormat,
diff --git a/internal/lsp/source/completion/completion_format.go b/internal/lsp/source/completion/format.go
similarity index 100%
rename from internal/lsp/source/completion/completion_format.go
rename to internal/lsp/source/completion/format.go
diff --git a/internal/lsp/source/completion/completion_keywords.go b/internal/lsp/source/completion/keywords.go
similarity index 100%
rename from internal/lsp/source/completion/completion_keywords.go
rename to internal/lsp/source/completion/keywords.go
diff --git a/internal/lsp/source/completion/completion_labels.go b/internal/lsp/source/completion/labels.go
similarity index 100%
rename from internal/lsp/source/completion/completion_labels.go
rename to internal/lsp/source/completion/labels.go
diff --git a/internal/lsp/source/completion/completion_literal.go b/internal/lsp/source/completion/literal.go
similarity index 100%
rename from internal/lsp/source/completion/completion_literal.go
rename to internal/lsp/source/completion/literal.go
diff --git a/internal/lsp/source/completion/completion_package.go b/internal/lsp/source/completion/package.go
similarity index 100%
rename from internal/lsp/source/completion/completion_package.go
rename to internal/lsp/source/completion/package.go
diff --git a/internal/lsp/source/completion/completion_printf.go b/internal/lsp/source/completion/printf.go
similarity index 100%
rename from internal/lsp/source/completion/completion_printf.go
rename to internal/lsp/source/completion/printf.go
diff --git a/internal/lsp/source/completion/completion_printf_test.go b/internal/lsp/source/completion/printf_test.go
similarity index 100%
rename from internal/lsp/source/completion/completion_printf_test.go
rename to internal/lsp/source/completion/printf_test.go
diff --git a/internal/lsp/source/completion/completion_snippet.go b/internal/lsp/source/completion/snippet.go
similarity index 100%
rename from internal/lsp/source/completion/completion_snippet.go
rename to internal/lsp/source/completion/snippet.go
diff --git a/internal/lsp/source/completion/completion_statements.go b/internal/lsp/source/completion/statements.go
similarity index 100%
rename from internal/lsp/source/completion/completion_statements.go
rename to internal/lsp/source/completion/statements.go
diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
index 2f0e7d6..33693a8 100644
--- a/internal/lsp/source/diagnostics.go
+++ b/internal/lsp/source/diagnostics.go
@@ -28,8 +28,9 @@
 }
 
 type SuggestedFix struct {
-	Title string
-	Edits map[span.URI][]protocol.TextEdit
+	Title   string
+	Edits   map[span.URI][]protocol.TextEdit
+	Command *protocol.Command
 }
 
 type RelatedInformation struct {
diff --git a/internal/lsp/source/gc_annotations.go b/internal/lsp/source/gc_annotations.go
index 6494b6a..4aac541 100644
--- a/internal/lsp/source/gc_annotations.go
+++ b/internal/lsp/source/gc_annotations.go
@@ -14,6 +14,7 @@
 	"path/filepath"
 	"strings"
 
+	"golang.org/x/tools/internal/gocommand"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/span"
 )
@@ -36,12 +37,16 @@
 	if !strings.HasPrefix(outDir, "/") {
 		outDirURI = span.URI(strings.Replace(string(outDirURI), "file:///", "file://", 1))
 	}
-	args := []string{
-		fmt.Sprintf("-gcflags=-json=0,%s", outDirURI),
-		fmt.Sprintf("-o=%s", tmpFile.Name()),
-		".",
+	inv := &gocommand.Invocation{
+		Verb: "build",
+		Args: []string{
+			fmt.Sprintf("-gcflags=-json=0,%s", outDirURI),
+			fmt.Sprintf("-o=%s", tmpFile.Name()),
+			".",
+		},
+		WorkingDir: pkgDir.Filename(),
 	}
-	err = snapshot.RunGoCommandDirect(ctx, pkgDir.Filename(), "build", args)
+	_, err = snapshot.RunGoCommandDirect(ctx, Normal, inv)
 	if err != nil {
 		return nil, err
 	}
@@ -62,6 +67,12 @@
 		if fh == nil {
 			continue
 		}
+		if pkgDir.Filename() != filepath.Dir(fh.URI().Filename()) {
+			// https://github.com/golang/go/issues/42198
+			// sometimes the detail diagnostics generated for files
+			// outside the package can never be taken back.
+			continue
+		}
 		reports[fh.VersionedFileIdentity()] = diagnostics
 	}
 	return reports, parseError
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 900d17a..7126fb4 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -25,6 +25,7 @@
 	"golang.org/x/tools/go/analysis/passes/deepequalerrors"
 	"golang.org/x/tools/go/analysis/passes/errorsas"
 	"golang.org/x/tools/go/analysis/passes/httpresponse"
+	"golang.org/x/tools/go/analysis/passes/ifaceassert"
 	"golang.org/x/tools/go/analysis/passes/loopclosure"
 	"golang.org/x/tools/go/analysis/passes/lostcancel"
 	"golang.org/x/tools/go/analysis/passes/nilfunc"
@@ -32,6 +33,7 @@
 	"golang.org/x/tools/go/analysis/passes/shift"
 	"golang.org/x/tools/go/analysis/passes/sortslice"
 	"golang.org/x/tools/go/analysis/passes/stdmethods"
+	"golang.org/x/tools/go/analysis/passes/stringintconv"
 	"golang.org/x/tools/go/analysis/passes/structtag"
 	"golang.org/x/tools/go/analysis/passes/testinggoroutine"
 	"golang.org/x/tools/go/analysis/passes/tests"
@@ -162,6 +164,8 @@
 	PreferredContentFormat            protocol.MarkupKind
 	LineFoldingOnly                   bool
 	HierarchicalDocumentSymbolSupport bool
+	SemanticTypes                     []string
+	SemanticMods                      []string
 }
 
 // ServerOptions holds LSP-specific configuration that is provided by the
@@ -535,6 +539,12 @@
 	o.LineFoldingOnly = fr.LineFoldingOnly
 	// Check if the client supports hierarchical document symbols.
 	o.HierarchicalDocumentSymbolSupport = caps.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport
+	// Check if the client supports semantic tokens
+	o.SemanticTypes = caps.TextDocument.SemanticTokens.TokenTypes
+	o.SemanticMods = caps.TextDocument.SemanticTokens.TokenModifiers
+	// we don't need Requests, as we support full functionality
+	// we don't need Formats, as there is only one, for now
+
 }
 
 func (o *Options) Clone() *Options {
@@ -951,31 +961,33 @@
 func defaultAnalyzers() map[string]Analyzer {
 	return map[string]Analyzer{
 		// The traditional vet suite:
-		asmdecl.Analyzer.Name:      {Analyzer: asmdecl.Analyzer, Enabled: true},
-		assign.Analyzer.Name:       {Analyzer: assign.Analyzer, Enabled: true},
-		atomic.Analyzer.Name:       {Analyzer: atomic.Analyzer, Enabled: true},
-		atomicalign.Analyzer.Name:  {Analyzer: atomicalign.Analyzer, Enabled: true},
-		bools.Analyzer.Name:        {Analyzer: bools.Analyzer, Enabled: true},
-		buildtag.Analyzer.Name:     {Analyzer: buildtag.Analyzer, Enabled: true},
-		cgocall.Analyzer.Name:      {Analyzer: cgocall.Analyzer, Enabled: true},
-		composite.Analyzer.Name:    {Analyzer: composite.Analyzer, Enabled: true},
-		copylock.Analyzer.Name:     {Analyzer: copylock.Analyzer, Enabled: true},
-		errorsas.Analyzer.Name:     {Analyzer: errorsas.Analyzer, Enabled: true},
-		httpresponse.Analyzer.Name: {Analyzer: httpresponse.Analyzer, Enabled: true},
-		loopclosure.Analyzer.Name:  {Analyzer: loopclosure.Analyzer, Enabled: true},
-		lostcancel.Analyzer.Name:   {Analyzer: lostcancel.Analyzer, Enabled: true},
-		nilfunc.Analyzer.Name:      {Analyzer: nilfunc.Analyzer, Enabled: true},
-		printf.Analyzer.Name:       {Analyzer: printf.Analyzer, Enabled: true},
-		shift.Analyzer.Name:        {Analyzer: shift.Analyzer, Enabled: true},
-		stdmethods.Analyzer.Name:   {Analyzer: stdmethods.Analyzer, Enabled: true},
-		structtag.Analyzer.Name:    {Analyzer: structtag.Analyzer, Enabled: true},
-		tests.Analyzer.Name:        {Analyzer: tests.Analyzer, Enabled: true},
-		unmarshal.Analyzer.Name:    {Analyzer: unmarshal.Analyzer, Enabled: true},
-		unreachable.Analyzer.Name:  {Analyzer: unreachable.Analyzer, Enabled: true},
-		unsafeptr.Analyzer.Name:    {Analyzer: unsafeptr.Analyzer, Enabled: true},
-		unusedresult.Analyzer.Name: {Analyzer: unusedresult.Analyzer, Enabled: true},
+		asmdecl.Analyzer.Name:       {Analyzer: asmdecl.Analyzer, Enabled: true},
+		assign.Analyzer.Name:        {Analyzer: assign.Analyzer, Enabled: true},
+		atomic.Analyzer.Name:        {Analyzer: atomic.Analyzer, Enabled: true},
+		bools.Analyzer.Name:         {Analyzer: bools.Analyzer, Enabled: true},
+		buildtag.Analyzer.Name:      {Analyzer: buildtag.Analyzer, Enabled: true},
+		cgocall.Analyzer.Name:       {Analyzer: cgocall.Analyzer, Enabled: true},
+		composite.Analyzer.Name:     {Analyzer: composite.Analyzer, Enabled: true},
+		copylock.Analyzer.Name:      {Analyzer: copylock.Analyzer, Enabled: true},
+		errorsas.Analyzer.Name:      {Analyzer: errorsas.Analyzer, Enabled: true},
+		httpresponse.Analyzer.Name:  {Analyzer: httpresponse.Analyzer, Enabled: true},
+		ifaceassert.Analyzer.Name:   {Analyzer: ifaceassert.Analyzer, Enabled: true},
+		loopclosure.Analyzer.Name:   {Analyzer: loopclosure.Analyzer, Enabled: true},
+		lostcancel.Analyzer.Name:    {Analyzer: lostcancel.Analyzer, Enabled: true},
+		nilfunc.Analyzer.Name:       {Analyzer: nilfunc.Analyzer, Enabled: true},
+		printf.Analyzer.Name:        {Analyzer: printf.Analyzer, Enabled: true},
+		shift.Analyzer.Name:         {Analyzer: shift.Analyzer, Enabled: true},
+		stdmethods.Analyzer.Name:    {Analyzer: stdmethods.Analyzer, Enabled: true},
+		stringintconv.Analyzer.Name: {Analyzer: stringintconv.Analyzer, Enabled: true},
+		structtag.Analyzer.Name:     {Analyzer: structtag.Analyzer, Enabled: true},
+		tests.Analyzer.Name:         {Analyzer: tests.Analyzer, Enabled: true},
+		unmarshal.Analyzer.Name:     {Analyzer: unmarshal.Analyzer, Enabled: true},
+		unreachable.Analyzer.Name:   {Analyzer: unreachable.Analyzer, Enabled: true},
+		unsafeptr.Analyzer.Name:     {Analyzer: unsafeptr.Analyzer, Enabled: true},
+		unusedresult.Analyzer.Name:  {Analyzer: unusedresult.Analyzer, Enabled: true},
 
 		// Non-vet analyzers:
+		atomicalign.Analyzer.Name:      {Analyzer: atomicalign.Analyzer, Enabled: true},
 		deepequalerrors.Analyzer.Name:  {Analyzer: deepequalerrors.Analyzer, Enabled: true},
 		sortslice.Analyzer.Name:        {Analyzer: sortslice.Analyzer, Enabled: true},
 		testinggoroutine.Analyzer.Name: {Analyzer: testinggoroutine.Analyzer, Enabled: true},
diff --git a/internal/lsp/source/references.go b/internal/lsp/source/references.go
index 32db1d0..3010043 100644
--- a/internal/lsp/source/references.go
+++ b/internal/lsp/source/references.go
@@ -127,7 +127,11 @@
 		}
 	}
 
-	if includeInterfaceRefs {
+	// When searching on type name, don't include interface references -- they
+	// would be things like all references to Stringer for any type that
+	// happened to have a String method.
+	_, isType := declIdent.Declaration.obj.(*types.TypeName)
+	if includeInterfaceRefs && !isType {
 		declRange, err := declIdent.Range()
 		if err != nil {
 			return nil, err
diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go
index 7fdcc70..26c705a 100644
--- a/internal/lsp/source/rename.go
+++ b/internal/lsp/source/rename.go
@@ -216,6 +216,9 @@
 		// go/parser strips out \r\n returns from the comment text, so go
 		// line-by-line through the comment text to get the correct positions.
 		for _, comment := range doc.List {
+			if isDirective(comment.Text) {
+				continue
+			}
 			lines := strings.Split(comment.Text, "\n")
 			tok := r.fset.File(comment.Pos())
 			commentLine := tok.Position(comment.Pos()).Line
diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go
index b49880d..03ac4e8 100644
--- a/internal/lsp/source/signature_help.go
+++ b/internal/lsp/source/signature_help.go
@@ -7,7 +7,6 @@
 import (
 	"context"
 	"go/ast"
-	"go/doc"
 	"go/token"
 	"go/types"
 
@@ -123,7 +122,7 @@
 	}
 	return &protocol.SignatureInformation{
 		Label:         name + s.Format(),
-		Documentation: doc.Synopsis(s.doc),
+		Documentation: s.doc,
 		Parameters:    paramInfo,
 	}, activeParam, nil
 }
@@ -140,7 +139,7 @@
 	activeParam := activeParameter(callExpr, len(sig.params), sig.variadic, pos)
 	return &protocol.SignatureInformation{
 		Label:         sig.name + sig.Format(),
-		Documentation: doc.Synopsis(sig.doc),
+		Documentation: sig.doc,
 		Parameters:    paramInfo,
 	}, activeParam, nil
 
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index ada58b5..932351e 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -51,7 +51,7 @@
 	options := source.DefaultOptions().Clone()
 	tests.DefaultOptions(options)
 	options.SetEnvSlice(datum.Config.Env)
-	view, _, release, err := session.NewView(ctx, "source_test", span.URIFromPath(datum.Config.Dir), options)
+	view, _, release, err := session.NewView(ctx, "source_test", span.URIFromPath(datum.Config.Dir), "", options)
 	release()
 	if err != nil {
 		t.Fatal(err)
diff --git a/internal/lsp/source/types_format.go b/internal/lsp/source/types_format.go
index 2455bdf..5ddd8e0 100644
--- a/internal/lsp/source/types_format.go
+++ b/internal/lsp/source/types_format.go
@@ -9,6 +9,7 @@
 	"context"
 	"fmt"
 	"go/ast"
+	"go/doc"
 	"go/printer"
 	"go/token"
 	"go/types"
@@ -79,8 +80,8 @@
 
 // NewBuiltinSignature returns signature for the builtin object with a given
 // name, if a builtin object with the name exists.
-func NewBuiltinSignature(ctx context.Context, snapshot Snapshot, name string) (*signature, error) {
-	builtin, err := snapshot.BuiltinPackage(ctx)
+func NewBuiltinSignature(ctx context.Context, s Snapshot, name string) (*signature, error) {
+	builtin, err := s.BuiltinPackage(ctx)
 	if err != nil {
 		return nil, err
 	}
@@ -103,10 +104,17 @@
 			variadic = true
 		}
 	}
-	params, _ := formatFieldList(ctx, snapshot, decl.Type.Params, variadic)
-	results, needResultParens := formatFieldList(ctx, snapshot, decl.Type.Results, false)
+	params, _ := formatFieldList(ctx, s, decl.Type.Params, variadic)
+	results, needResultParens := formatFieldList(ctx, s, decl.Type.Results, false)
+	d := decl.Doc.Text()
+	switch s.View().Options().HoverKind {
+	case SynopsisDocumentation:
+		d = doc.Synopsis(d)
+	case NoDocumentation:
+		d = ""
+	}
 	return &signature{
-		doc:              decl.Doc.Text(),
+		doc:              d,
 		name:             name,
 		needResultParens: needResultParens,
 		params:           params,
@@ -188,12 +196,18 @@
 			results = append(results, el.Name()+" "+typ)
 		}
 	}
-	var doc string
+	var d string
 	if comment != nil {
-		doc = comment.Text()
+		d = comment.Text()
+	}
+	switch s.View().Options().HoverKind {
+	case SynopsisDocumentation:
+		d = doc.Synopsis(d)
+	case NoDocumentation:
+		d = ""
 	}
 	return &signature{
-		doc:              doc,
+		doc:              d,
 		params:           params,
 		results:          results,
 		variadic:         sig.Variadic(),
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
index 1e0ac9a..b3d87f9 100644
--- a/internal/lsp/source/util.go
+++ b/internal/lsp/source/util.go
@@ -401,3 +401,117 @@
 		return p.Name()
 	}
 }
+
+// isDirective reports whether c is a comment directive.
+//
+// Copied and adapted from go/src/go/ast/ast.go.
+func isDirective(c string) bool {
+	if len(c) < 3 {
+		return false
+	}
+	if c[1] != '/' {
+		return false
+	}
+	//-style comment (no newline at the end)
+	c = c[2:]
+	if len(c) == 0 {
+		// empty line
+		return false
+	}
+	// "//line " is a line directive.
+	// (The // has been removed.)
+	if strings.HasPrefix(c, "line ") {
+		return true
+	}
+
+	// "//[a-z0-9]+:[a-z0-9]"
+	// (The // has been removed.)
+	colon := strings.Index(c, ":")
+	if colon <= 0 || colon+1 >= len(c) {
+		return false
+	}
+	for i := 0; i <= colon+1; i++ {
+		if i == colon {
+			continue
+		}
+		b := c[i]
+		if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') {
+			return false
+		}
+	}
+	return true
+}
+
+// InDir checks whether path is in the file tree rooted at dir.
+// If so, InDir returns an equivalent path relative to dir.
+// If not, InDir returns an empty string.
+// InDir makes some effort to succeed even in the presence of symbolic links.
+//
+// Copied and slightly adjusted from go/src/cmd/go/internal/search/search.go.
+func InDir(dir, path string) bool {
+	if rel := inDirLex(path, dir); rel != "" {
+		return true
+	}
+	xpath, err := filepath.EvalSymlinks(path)
+	if err != nil || xpath == path {
+		xpath = ""
+	} else {
+		if rel := inDirLex(xpath, dir); rel != "" {
+			return true
+		}
+	}
+
+	xdir, err := filepath.EvalSymlinks(dir)
+	if err == nil && xdir != dir {
+		if rel := inDirLex(path, xdir); rel != "" {
+			return true
+		}
+		if xpath != "" {
+			if rel := inDirLex(xpath, xdir); rel != "" {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+// Copied from go/src/cmd/go/internal/search/search.go.
+//
+// inDirLex is like inDir but only checks the lexical form of the file names.
+// It does not consider symbolic links.
+// TODO(rsc): This is a copy of str.HasFilePathPrefix, modified to
+// return the suffix. Most uses of str.HasFilePathPrefix should probably
+// be calling InDir instead.
+func inDirLex(path, dir string) string {
+	pv := strings.ToUpper(filepath.VolumeName(path))
+	dv := strings.ToUpper(filepath.VolumeName(dir))
+	path = path[len(pv):]
+	dir = dir[len(dv):]
+	switch {
+	default:
+		return ""
+	case pv != dv:
+		return ""
+	case len(path) == len(dir):
+		if path == dir {
+			return "."
+		}
+		return ""
+	case dir == "":
+		return path
+	case len(path) > len(dir):
+		if dir[len(dir)-1] == filepath.Separator {
+			if path[:len(dir)] == dir {
+				return path[len(dir):]
+			}
+			return ""
+		}
+		if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir {
+			if len(path) == len(dir)+1 {
+				return "."
+			}
+			return path[len(dir)+1:]
+		}
+		return ""
+	}
+}
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index c84ff06..fa307df 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -5,6 +5,7 @@
 package source
 
 import (
+	"bytes"
 	"context"
 	"fmt"
 	"go/ast"
@@ -16,6 +17,7 @@
 	"golang.org/x/mod/modfile"
 	"golang.org/x/mod/module"
 	"golang.org/x/tools/go/analysis"
+	"golang.org/x/tools/internal/gocommand"
 	"golang.org/x/tools/internal/imports"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/span"
@@ -80,16 +82,13 @@
 	// Analyze runs the analyses for the given package at this snapshot.
 	Analyze(ctx context.Context, pkgID string, analyzers ...*analysis.Analyzer) ([]*Error, error)
 
-	// RunGoCommandPiped runs the given `go` command in the view, using the
-	// provided stdout and stderr. It will use the -modfile flag, if possible.
-	// If the provided working directory is empty, the snapshot's root folder
-	// will be used as the working directory.
-	RunGoCommandPiped(ctx context.Context, wd, verb string, args []string, stdout, stderr io.Writer) error
+	// RunGoCommandPiped runs the given `go` command, writing its output
+	// to stdout and stderr. Verb, Args, and WorkingDir must be specified.
+	RunGoCommandPiped(ctx context.Context, mode InvocationMode, inv *gocommand.Invocation, stdout, stderr io.Writer) error
 
-	// RunGoCommandDirect runs the given `go` command, never using the
-	// -modfile flag. If the provided working directory is empty, the
-	// snapshot's root folder will be used as the working directory.
-	RunGoCommandDirect(ctx context.Context, wd, verb string, args []string) error
+	// RunGoCommandDirect runs the given `go` command. Verb, Args, and
+	// WorkingDir must be specified.
+	RunGoCommandDirect(ctx context.Context, mode InvocationMode, inv *gocommand.Invocation) (*bytes.Buffer, error)
 
 	// RunProcessEnvFunc runs fn with the process env for this snapshot's view.
 	// Note: the process env contains cached module and filesystem state.
@@ -117,10 +116,6 @@
 	// GoModForFile returns the URI of the go.mod file for the given URI.
 	GoModForFile(ctx context.Context, uri span.URI) span.URI
 
-	// BuildWorkspaceModFile builds the contents of mod file to be used for
-	// multi-module workspace.
-	BuildWorkspaceModFile(ctx context.Context) (*modfile.File, error)
-
 	// BuiltinPackage returns information about the special builtin package.
 	BuiltinPackage(ctx context.Context) (*BuiltinPackage, error)
 
@@ -170,6 +165,24 @@
 	WidestPackage
 )
 
+// InvocationMode represents the goal of a particular go command invocation.
+type InvocationMode int
+
+const (
+	// Normal is appropriate for commands that might be run by a user and don't
+	// deliberately modify go.mod files, e.g. `go test`.
+	Normal = iota
+	// UpdateUserModFile is for commands that intend to update the user's real
+	// go.mod file, e.g. `go mod tidy` in response to a user's request to tidy.
+	UpdateUserModFile
+	// WriteTemporaryModFile is for commands that need information from a
+	// modified version of the user's go.mod file, e.g. `go mod tidy` used to
+	// generate diagnostics.
+	WriteTemporaryModFile
+	// ForTypeChecking is for packages.Load.
+	ForTypeChecking
+)
+
 // View represents a single workspace.
 // This is the level at which we maintain configuration like working directory
 // and build tags.
@@ -256,7 +269,7 @@
 // A session may have many active views at any given time.
 type Session interface {
 	// NewView creates a new View, returning it and its first snapshot.
-	NewView(ctx context.Context, name string, folder span.URI, options *Options) (View, Snapshot, func(), error)
+	NewView(ctx context.Context, name string, folder, tempWorkspaceDir span.URI, options *Options) (View, Snapshot, func(), error)
 
 	// Cache returns the cache that created this session, for debugging only.
 	Cache() interface{}
@@ -529,14 +542,19 @@
 	return b.String()
 }
 
+// An Error corresponds to an LSP Diagnostic.
+// https://microsoft.github.io/language-server-protocol/specification#diagnostic
 type Error struct {
-	URI            span.URI
-	Range          protocol.Range
-	Kind           ErrorKind
-	Message        string
-	Category       string // only used by analysis errors so far
+	URI      span.URI
+	Range    protocol.Range
+	Kind     ErrorKind
+	Message  string
+	Category string // only used by analysis errors so far
+	Related  []RelatedInformation
+
+	// SuggestedFixes is used to generate quick fixes for a CodeAction request.
+	// It isn't part of the Diagnostic type.
 	SuggestedFixes []SuggestedFix
-	Related        []RelatedInformation
 }
 
 // GoModTidy is the source for a diagnostic computed by running `go mod tidy`.
@@ -558,8 +576,7 @@
 }
 
 var (
-	InconsistentVendoring = errors.New("inconsistent vendoring")
-	PackagesLoadError     = errors.New("packages.Load error")
+	PackagesLoadError = errors.New("packages.Load error")
 )
 
 // WorkspaceModuleVersion is the nonexistent pseudoversion suffix used in the
diff --git a/internal/lsp/source/workspace_symbol.go b/internal/lsp/source/workspace_symbol.go
index 25010de..6ca476e 100644
--- a/internal/lsp/source/workspace_symbol.go
+++ b/internal/lsp/source/workspace_symbol.go
@@ -25,13 +25,13 @@
 // sent in response to a client.
 const maxSymbols = 100
 
-// WorkspaceSymbols matches symbols across views using the given query,
-// according to the SymbolMatcher matcher.
+// WorkspaceSymbols matches symbols across all views using the given query,
+// according to the match semantics parameterized by matcherType and style.
 //
 // The workspace symbol method is defined in the spec as follows:
 //
-//  > The workspace symbol request is sent from the client to the server to
-//  > list project-wide symbols matching the query string.
+//   The workspace symbol request is sent from the client to the server to
+//   list project-wide symbols matching the query string.
 //
 // It is unclear what "project-wide" means here, but given the parameters of
 // workspace/symbol do not include any workspace identifier, then it has to be
@@ -64,9 +64,9 @@
 type symbolizer func(name string, pkg Package, m matcherFunc) (string, float64)
 
 func fullyQualifiedSymbolMatch(name string, pkg Package, matcher matcherFunc) (string, float64) {
-	fullyQualified := pkg.PkgPath() + "." + name
-	if matcher(fullyQualified) > 0 {
-		return fullyQualified, 1
+	_, score := dynamicSymbolMatch(name, pkg, matcher)
+	if score > 0 {
+		return pkg.PkgPath() + "." + name, score
 	}
 	return "", 0
 }
diff --git a/internal/lsp/testdata/rename/c/c2.go b/internal/lsp/testdata/rename/c/c2.go
new file mode 100644
index 0000000..4fc484a
--- /dev/null
+++ b/internal/lsp/testdata/rename/c/c2.go
@@ -0,0 +1,4 @@
+package c
+
+//go:embed Static/*
+var Static embed.FS //@rename("Static", "static")
\ No newline at end of file
diff --git a/internal/lsp/testdata/rename/c/c2.go.golden b/internal/lsp/testdata/rename/c/c2.go.golden
new file mode 100644
index 0000000..e509227
--- /dev/null
+++ b/internal/lsp/testdata/rename/c/c2.go.golden
@@ -0,0 +1,5 @@
+-- static-rename --
+package c
+
+//go:embed Static/*
+var static embed.FS //@rename("Static", "static")
diff --git a/internal/lsp/testdata/summary.txt.golden b/internal/lsp/testdata/summary.txt.golden
index 6e2da0b..3771c85 100644
--- a/internal/lsp/testdata/summary.txt.golden
+++ b/internal/lsp/testdata/summary.txt.golden
@@ -19,7 +19,7 @@
 TypeDefinitionsCount = 2
 HighlightsCount = 69
 ReferencesCount = 25
-RenamesCount = 29
+RenamesCount = 30
 PrepareRenamesCount = 7
 SymbolsCount = 5
 WorkspaceSymbolsCount = 2
diff --git a/internal/lsp/text_synchronization.go b/internal/lsp/text_synchronization.go
index 805393c..205331d 100644
--- a/internal/lsp/text_synchronization.go
+++ b/internal/lsp/text_synchronization.go
@@ -229,11 +229,15 @@
 		viewURIs[view] = append(viewURIs[view], uri)
 	}
 	for view, uris := range viewURIs {
+		snapshot := snapshots[view]
+		if snapshot == nil {
+			panic(fmt.Sprintf("no snapshot assigned for files %v", uris))
+		}
 		diagnosticWG.Add(1)
 		go func(snapshot source.Snapshot, uris []span.URI) {
 			defer diagnosticWG.Done()
 			s.diagnoseSnapshot(snapshot, uris)
-		}(snapshots[view], uris)
+		}(snapshot, uris)
 	}
 
 	go func() {
diff --git a/internal/lsp/workspace.go b/internal/lsp/workspace.go
index beced5a..9b29668 100644
--- a/internal/lsp/workspace.go
+++ b/internal/lsp/workspace.go
@@ -26,7 +26,7 @@
 	return s.addFolders(ctx, event.Added)
 }
 
-func (s *Server) addView(ctx context.Context, name string, uri span.URI) (source.Snapshot, func(), error) {
+func (s *Server) addView(ctx context.Context, name string, uri, tempWorkspace span.URI) (source.Snapshot, func(), error) {
 	s.stateMu.Lock()
 	state := s.state
 	s.stateMu.Unlock()
@@ -37,7 +37,7 @@
 	if err := s.fetchConfig(ctx, name, uri, options); err != nil {
 		return nil, func() {}, err
 	}
-	_, snapshot, release, err := s.session.NewView(ctx, name, uri, options)
+	_, snapshot, release, err := s.session.NewView(ctx, name, uri, tempWorkspace, options)
 	return snapshot, release, err
 }
 
@@ -91,9 +91,15 @@
 	return nil
 }
 
+// This is a work-around for
+// https://github.com/microsoft/language-server-protocol/issues/1107. Once
+// https://golang.org/cl/266497 has been released for ~1 month, we can probably
+// remove this function and use the only correct method name, which is
+// "textDocument/semanticTokens".
 func semanticTokenRegistrations() []protocol.Registration {
 	var registrations []protocol.Registration
 	for _, method := range []string{
+		"textDocument/semanticTokens",
 		"textDocument/semanticTokens/full",
 		"textDocument/semanticTokens/full/delta",
 		"textDocument/semanticTokens/range",
@@ -105,8 +111,8 @@
 				Legend: protocol.SemanticTokensLegend{
 					// TODO(pjw): trim these to what we use (and an unused one
 					// at position 0 of TokTypes, to catch typos)
-					TokenTypes:     SemanticMemo.tokTypes,
-					TokenModifiers: SemanticMemo.tokMods,
+					TokenTypes:     SemanticTypes(),
+					TokenModifiers: SemanticModifiers(),
 				},
 			},
 		})
diff --git a/internal/memoize/memoize.go b/internal/memoize/memoize.go
index 3c7d3c1..d4b8773 100644
--- a/internal/memoize/memoize.go
+++ b/internal/memoize/memoize.go
@@ -82,6 +82,9 @@
 			if len(e.generations) == 0 {
 				delete(g.store.handles, k)
 				e.state = stateDestroyed
+				if e.cleanup != nil && e.value != nil {
+					e.cleanup(e.value)
+				}
 			}
 		}
 		e.mu.Unlock()
@@ -150,16 +153,22 @@
 	function Function
 	// value is set in completed state.
 	value interface{}
+	// cleanup, if non-nil, is used to perform any necessary clean-up on values
+	// produced by function.
+	cleanup func(interface{})
 }
 
 // Bind returns a handle for the given key and function.
 //
-// Each call to bind will return the same handle if it is already bound.
-// Bind will always return a valid handle, creating one if needed.
-// Each key can only have one handle at any given time.
-// The value will be held at least until the associated generation is destroyed.
-// Bind does not cause the value to be generated.
-func (g *Generation) Bind(key interface{}, function Function) *Handle {
+// Each call to bind will return the same handle if it is already bound. Bind
+// will always return a valid handle, creating one if needed. Each key can
+// only have one handle at any given time. The value will be held at least
+// until the associated generation is destroyed. Bind does not cause the value
+// to be generated.
+//
+// If cleanup is non-nil, it will be called on any non-nil values produced by
+// function when they are no longer referenced.
+func (g *Generation) Bind(key interface{}, function Function, cleanup func(interface{})) *Handle {
 	// panic early if the function is nil
 	// it would panic later anyway, but in a way that was much harder to debug
 	if function == nil {
@@ -176,6 +185,7 @@
 			key:         key,
 			function:    function,
 			generations: map[*Generation]struct{}{g: {}},
+			cleanup:     cleanup,
 		}
 		g.store.handles[key] = h
 		return h
@@ -220,17 +230,19 @@
 	}
 }
 
-func (g *Generation) Inherit(h *Handle) {
-	if atomic.LoadUint32(&g.destroyed) != 0 {
-		panic("inherit on destroyed generation " + g.name)
-	}
+func (g *Generation) Inherit(hs ...*Handle) {
+	for _, h := range hs {
+		if atomic.LoadUint32(&g.destroyed) != 0 {
+			panic("inherit on destroyed generation " + g.name)
+		}
 
-	h.mu.Lock()
-	defer h.mu.Unlock()
-	if h.state == stateDestroyed {
-		panic(fmt.Sprintf("inheriting destroyed handle %#v (type %T) into generation %v", h.key, h.key, g.name))
+		h.mu.Lock()
+		defer h.mu.Unlock()
+		if h.state == stateDestroyed {
+			panic(fmt.Sprintf("inheriting destroyed handle %#v (type %T) into generation %v", h.key, h.key, g.name))
+		}
+		h.generations[g] = struct{}{}
 	}
-	h.generations[g] = struct{}{}
 }
 
 // Cached returns the value associated with a handle.
@@ -309,6 +321,11 @@
 		}
 		v := function(childCtx, arg)
 		if childCtx.Err() != nil {
+			// It's possible that v was computed despite the context cancellation. In
+			// this case we should ensure that it is cleaned up.
+			if h.cleanup != nil && v != nil {
+				h.cleanup(v)
+			}
 			return
 		}
 
@@ -319,8 +336,13 @@
 		// checked childCtx above. Even so, that should be harmless, since each
 		// run should produce the same results.
 		if h.state != stateRunning {
+			// v will never be used, so ensure that it is cleaned up.
+			if h.cleanup != nil && v != nil {
+				h.cleanup(v)
+			}
 			return
 		}
+		// At this point v will be cleaned up whenever h is destroyed.
 		h.value = v
 		h.function = nil
 		h.state = stateCompleted
diff --git a/internal/memoize/memoize_test.go b/internal/memoize/memoize_test.go
index e6e7b0b..41f20d0 100644
--- a/internal/memoize/memoize_test.go
+++ b/internal/memoize/memoize_test.go
@@ -21,7 +21,7 @@
 	h := g.Bind("key", func(context.Context, memoize.Arg) interface{} {
 		evaled++
 		return "res"
-	})
+	}, nil)
 	expectGet(t, h, g, "res")
 	expectGet(t, h, g, "res")
 	if evaled != 1 {
@@ -30,6 +30,7 @@
 }
 
 func expectGet(t *testing.T, h *memoize.Handle, g *memoize.Generation, wantV interface{}) {
+	t.Helper()
 	gotV, gotErr := h.Get(context.Background(), g, nil)
 	if gotV != wantV || gotErr != nil {
 		t.Fatalf("Get() = %v, %v, wanted %v, nil", gotV, gotErr, wantV)
@@ -42,11 +43,12 @@
 		t.Fatalf("Get() = %v, %v, wanted err %q", gotV, gotErr, substr)
 	}
 }
+
 func TestGenerations(t *testing.T) {
 	s := &memoize.Store{}
 	// Evaluate key in g1.
 	g1 := s.Generation("g1")
-	h1 := g1.Bind("key", func(context.Context, memoize.Arg) interface{} { return "res" })
+	h1 := g1.Bind("key", func(context.Context, memoize.Arg) interface{} { return "res" }, nil)
 	expectGet(t, h1, g1, "res")
 
 	// Get key in g2. It should inherit the value from g1.
@@ -54,7 +56,7 @@
 	h2 := g2.Bind("key", func(context.Context, memoize.Arg) interface{} {
 		t.Fatal("h2 should not need evaluation")
 		return "error"
-	})
+	}, nil)
 	expectGet(t, h2, g2, "res")
 
 	// With g1 destroyed, g2 should still work.
@@ -64,6 +66,42 @@
 	// With all generations destroyed, key should be re-evaluated.
 	g2.Destroy()
 	g3 := s.Generation("g3")
-	h3 := g3.Bind("key", func(context.Context, memoize.Arg) interface{} { return "new res" })
+	h3 := g3.Bind("key", func(context.Context, memoize.Arg) interface{} { return "new res" }, nil)
 	expectGet(t, h3, g3, "new res")
 }
+
+func TestCleanup(t *testing.T) {
+	s := &memoize.Store{}
+	g1 := s.Generation("g1")
+	v1 := false
+	v2 := false
+	cleanup := func(v interface{}) {
+		*(v.(*bool)) = true
+	}
+	h1 := g1.Bind("key1", func(context.Context, memoize.Arg) interface{} {
+		return &v1
+	}, nil)
+	h2 := g1.Bind("key2", func(context.Context, memoize.Arg) interface{} {
+		return &v2
+	}, cleanup)
+	expectGet(t, h1, g1, &v1)
+	expectGet(t, h2, g1, &v2)
+	g2 := s.Generation("g2")
+	g2.Inherit(h1, h2)
+
+	g1.Destroy()
+	expectGet(t, h1, g2, &v1)
+	expectGet(t, h2, g2, &v2)
+	for k, v := range map[string]*bool{"key1": &v1, "key2": &v2} {
+		if got, want := *v, false; got != want {
+			t.Errorf("after destroying g1, bound value %q is cleaned up", k)
+		}
+	}
+	g2.Destroy()
+	if got, want := v1, false; got != want {
+		t.Error("after destroying g2, v1 is cleaned up")
+	}
+	if got, want := v2, true; got != want {
+		t.Error("after destroying g2, v2 is not cleaned up")
+	}
+}