internal/lsp: use the go command to fix go.mod files

Modifying go.mod files directly leaves go.sum unchanged, and therefore
in need of updates later. Leaving work for the users to clean up isn't
ideal, so it'd be better to use the go command to make modifications.

Unfortunately, the go command has something of a mind of its own. The
most obvious problem is that using go get to add a new require adds a
// indirect comment to that new require, and there's no way to prevent
it. The only thing we can do is add the require first, then use go get
to do nothing but update the go.sum file.

The other inherent problem is that the go command operates on files as
they exist on disk, not the in-memory versions. As discussed, we issue
an error for this case. The alternative would be to work on temporary
files based on the in-memory contents, but that would be much larger
change, so I'd rather not at least right now.

To support Commands for quick fixes, add a new Command field to
source.SuggestedFix, and use it when forming the CodeAction response.

Fixes golang/go#38209.

Change-Id: I0c13ea39199368623e7494e222ba38587323d417
Reviewed-on: https://go-review.googlesource.com/c/tools/+/265981
Trust: Heschi Kreinick <heschi@google.com>
Run-TryBot: Heschi Kreinick <heschi@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
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/internal/regtest/codelens_test.go b/gopls/internal/regtest/codelens_test.go
index 1c2e709..9dfafde 100644
--- a/gopls/internal/regtest/codelens_test.go
+++ b/gopls/internal/regtest/codelens_test.go
@@ -109,25 +109,7 @@
 `
 	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())
+		env.ExecuteCodeLensCommand("go.mod", source.CommandUpgradeDependency)
 		got := env.ReadWorkspaceFile("go.mod")
 		const wantGoMod = `module mod.com
 
@@ -182,7 +164,6 @@
 	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")
 		const wantGoMod = `module mod.com
 
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..a69c260 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,6 +319,8 @@
 	}, 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)
 
@@ -369,6 +369,8 @@
 -- go.mod --
 module mod.com
 
+go 1.12
+
 require (
 	example.com/blah/v2 v2.0.0
 )
@@ -394,12 +396,17 @@
 		env.ApplyQuickFixes("main.go", d.Diagnostics)
 		const want = `module mod.com
 
+go 1.12
+
 require (
 	example.com/blah v1.0.0
 	example.com/blah/v2 v2.0.0
 )
 `
-		if got := env.Editor.BufferText("go.mod"); got != want {
+		// We need to read from disk even though go.mod is open -- the regtests
+		// currently don't apply on-disk changes to open but unmodified buffers
+		// like most editors would.
+		if got := env.ReadWorkspaceFile("go.mod"); got != want {
 			t.Fatalf("suggested fixes failed:\n%s", tests.Diff(want, got))
 		}
 	}, WithProxyFiles(badModule))
diff --git a/internal/lsp/cache/mod_tidy.go b/internal/lsp/cache/mod_tidy.go
index 9e92287..3cb55ab 100644
--- a/internal/lsp/cache/mod_tidy.go
+++ b/internal/lsp/cache/mod_tidy.go
@@ -306,7 +306,7 @@
 	if err != nil {
 		return source.Error{}, err
 	}
-	edits, err := dropDependency(req, m, computeEdits)
+	args, err := source.MarshalArgs(m.URI, []string{req.Mod.Path + "@none"})
 	if err != nil {
 		return source.Error{}, err
 	}
@@ -317,8 +317,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
@@ -371,48 +373,27 @@
 	if err != nil {
 		return source.Error{}, err
 	}
-	edits, err := addRequireFix(pm.Mapper, req, snapshot.View().Options().ComputeEdits)
+	args, err := source.MarshalArgs(pm.Mapper.URI, []string{req.Mod.Path + "@" + req.Mod.Version})
 	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) {
@@ -470,28 +451,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/code_action.go b/internal/lsp/code_action.go
index cb45045..4068935 100644
--- a/internal/lsp/code_action.go
+++ b/internal/lsp/code_action.go
@@ -508,6 +508,7 @@
 				Kind:        protocol.QuickFix,
 				Diagnostics: []protocol.Diagnostic{*diag},
 				Edit:        protocol.WorkspaceEdit{},
+				Command:     fix.Command,
 			}
 			for uri, edits := range fix.Edits {
 				if uri != modFH.URI() {
diff --git a/internal/lsp/command.go b/internal/lsp/command.go
index 134d3fd..6dd5045 100644
--- a/internal/lsp/command.go
+++ b/internal/lsp/command.go
@@ -54,9 +54,10 @@
 	}
 	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():
 			// TODO(PJW): for Toggle, not an error if it is being disabled
-			err := errors.New("unsaved files in the view")
+			err := errors.New("all files must be saved first")
 			s.showCommandError(ctx, command.Title, err)
 			return nil, err
 		}
@@ -189,14 +190,23 @@
 		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 {
 			return err
 		}
-		return s.directGoModCommand(ctx, uri, "get", goCmdArgs...)
+		if command == source.CommandAddDependency {
+			// Using go get to create a new dependency results in an
+			// `// indirect` comment we don't 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 {
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 8467cf4..c6488f9 100644
--- a/internal/lsp/source/command.go
+++ b/internal/lsp/source/command.go
@@ -63,7 +63,9 @@
 	CommandTest,
 	CommandTidy,
 	CommandUndeclaredName,
+	CommandAddDependency,
 	CommandUpgradeDependency,
+	CommandRemoveDependency,
 	CommandVendor,
 	CommandExtractVariable,
 	CommandExtractFunction,
@@ -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",
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/view.go b/internal/lsp/source/view.go
index 8a12e0c..bdb388b 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -546,14 +546,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`.