internal/lsp/command: add missing doc and support for result parameters

This CL cleans up doc/commands.md to include missing command
documentation and add support for result parameters.

Included are some quick-and-dirty extensions to the command metadata
loader that handle slices and arrays of struct types.

Change-Id: I43a85e88c9c53e21b790d89a45a9de444addcc63
Reviewed-on: https://go-review.googlesource.com/c/tools/+/326909
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@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 9073d97..dbbc4b5 100644
--- a/gopls/doc/commands.md
+++ b/gopls/doc/commands.md
@@ -3,7 +3,7 @@
 This document describes the LSP-level commands supported by `gopls`. They cannot be invoked directly by users, and all the details are subject to change, so nobody should rely on this information.
 
 <!-- BEGIN Commands: DO NOT MANUALLY EDIT THIS SECTION -->
-### **Add dependency**
+### **Add a dependency**
 Identifier: `gopls.add_dependency`
 
 Adds a dependency to the go.mod file for a module.
@@ -21,11 +21,12 @@
 }
 ```
 
-### **asks the server to add an import path to a given Go file.**
+### **Add an import**
 Identifier: `gopls.add_import`
 
-The method will call applyEdit on the client so that clients don't have
-to apply the edit themselves.
+Ask the server to add an import path to a given Go file.  The method will
+call applyEdit on the client so that clients don't have to apply the edit
+themselves.
 
 Args:
 
@@ -124,7 +125,7 @@
 }
 ```
 
-### **go get package**
+### **go get a package**
 Identifier: `gopls.go_get_package`
 
 Runs `go get` to fetch a package.
@@ -141,10 +142,10 @@
 }
 ```
 
-### **retrieves a list of packages**
+### **List known packages**
 Identifier: `gopls.list_known_packages`
 
-that are importable from the given URI.
+Retrieve a list of packages that are importable from the given URI.
 
 Args:
 
@@ -155,6 +156,19 @@
 }
 ```
 
+Result:
+
+```
+{
+	// Packages is a list of packages relative
+	// to the URIArg passed by the command request.
+	// In other words, it omits paths that are already
+	// imported or cannot be imported due to compiler
+	// restrictions.
+	"Packages": []string,
+}
+```
+
 ### **Regenerate cgo**
 Identifier: `gopls.regenerate_cgo`
 
@@ -169,7 +183,7 @@
 }
 ```
 
-### **Remove dependency**
+### **Remove a dependency**
 Identifier: `gopls.remove_dependency`
 
 Removes a dependency from the go.mod file of a module.
@@ -204,10 +218,10 @@
 }
 ```
 
-### ****
+### **Start the gopls debug server if it isn't running, and**
 Identifier: `gopls.start_debugging`
 
-
+return the debug address.
 
 Args:
 
@@ -230,6 +244,24 @@
 }
 ```
 
+Result:
+
+```
+{
+	// The URLs to use to access the debug servers, for all gopls instances in
+	// the serving path. For the common case of a single gopls instance (i.e. no
+	// daemon), this will be exactly one address.
+	// 
+	// In the case of one or more gopls instances forwarding the LSP to a daemon,
+	// URLs will contain debug addresses for each server in the serving path, in
+	// serving order. The daemon debug address will be the last entry in the
+	// slice. If any intermediate gopls instance fails to start debugging, no
+	// error will be returned but the debug URL for that server in the URLs slice
+	// will be empty.
+	"URLs": []string,
+}
+```
+
 ### **Run test(s) (legacy)**
 Identifier: `gopls.test`
 
@@ -285,7 +317,7 @@
 }
 ```
 
-### **Upgrade dependency**
+### **Upgrade a dependency**
 Identifier: `gopls.upgrade_dependency`
 
 Upgrades a dependency in the go.mod file for a module.
@@ -317,9 +349,21 @@
 }
 ```
 
-### ****
+### **Query workspace metadata**
 Identifier: `gopls.workspace_metadata`
 
+Query the server for information about active workspaces.
 
+Result:
+
+```
+{
+	// All workspaces for this session.
+	"Workspaces": []{
+		"Name": string,
+		"ModuleDir": string,
+	},
+}
+```
 
 <!-- END Commands: DO NOT MANUALLY EDIT THIS SECTION -->
diff --git a/gopls/doc/generate.go b/gopls/doc/generate.go
index ed42647..91d45ba 100644
--- a/gopls/doc/generate.go
+++ b/gopls/doc/generate.go
@@ -379,12 +379,16 @@
 	}
 	// Parse the objects it contains.
 	for _, cmd := range cmds {
-		commands = append(commands, &source.CommandJSON{
+		cmdjson := &source.CommandJSON{
 			Command: cmd.Name,
 			Title:   cmd.Title,
 			Doc:     cmd.Doc,
 			ArgDoc:  argsDoc(cmd.Args),
-		})
+		}
+		if cmd.Result != nil {
+			cmdjson.ResultDoc = typeDoc(cmd.Result, 0)
+		}
+		commands = append(commands, cmdjson)
 	}
 	return commands, nil
 }
@@ -392,7 +396,7 @@
 func argsDoc(args []*commandmeta.Field) string {
 	var b strings.Builder
 	for i, arg := range args {
-		b.WriteString(argDoc(arg, 0))
+		b.WriteString(typeDoc(arg, 0))
 		if i != len(args)-1 {
 			b.WriteString(",\n")
 		}
@@ -400,12 +404,12 @@
 	return b.String()
 }
 
-func argDoc(arg *commandmeta.Field, level int) string {
+func typeDoc(arg *commandmeta.Field, level int) string {
 	// Max level to expand struct fields.
 	const maxLevel = 3
 	if len(arg.Fields) > 0 {
 		if level < maxLevel {
-			return structDoc(arg.Fields, level)
+			return arg.FieldMod + structDoc(arg.Fields, level)
 		}
 		return "{ ... }"
 	}
@@ -432,7 +436,7 @@
 		if tag == "" {
 			tag = fld.Name
 		}
-		fmt.Fprintf(&b, "%s\t%q: %s,\n", indent, tag, argDoc(fld, level+1))
+		fmt.Fprintf(&b, "%s\t%q: %s,\n", indent, tag, typeDoc(fld, level+1))
 	}
 	fmt.Fprintf(&b, "%s}", indent)
 	return b.String()
@@ -739,6 +743,9 @@
 		if command.ArgDoc != "" {
 			fmt.Fprintf(section, "Args:\n\n```\n%s\n```\n\n", command.ArgDoc)
 		}
+		if command.ResultDoc != "" {
+			fmt.Fprintf(section, "Result:\n\n```\n%s\n```\n\n", command.ResultDoc)
+		}
 	}
 	return replaceSection(doc, "Commands", section.Bytes())
 }
diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md
index f2de5b1..d2aecc8 100644
--- a/gopls/doc/settings.md
+++ b/gopls/doc/settings.md
@@ -448,7 +448,7 @@
 Identifier: `tidy`
 
 Runs `go mod tidy` for a module.
-### **Upgrade dependency**
+### **Upgrade a dependency**
 
 Identifier: `upgrade_dependency`
 
diff --git a/internal/lsp/command/commandmeta/meta.go b/internal/lsp/command/commandmeta/meta.go
index c036d7a..1a6a2c7 100644
--- a/internal/lsp/command/commandmeta/meta.go
+++ b/internal/lsp/command/commandmeta/meta.go
@@ -28,7 +28,7 @@
 	Title  string
 	Doc    string
 	Args   []*Field
-	Result types.Type
+	Result *Field
 }
 
 func (c *Command) ID() string {
@@ -36,10 +36,11 @@
 }
 
 type Field struct {
-	Name    string
-	Doc     string
-	JSONTag string
-	Type    types.Type
+	Name     string
+	Doc      string
+	JSONTag  string
+	Type     types.Type
+	FieldMod string
 	// In some circumstances, we may want to recursively load additional field
 	// descriptors for fields of struct types, documenting their internals.
 	Fields []*Field
@@ -110,15 +111,15 @@
 		return nil, fmt.Errorf("final return must be error")
 	}
 	if rlen == 2 {
-		c.Result = sig.Results().At(0).Type()
+		obj := sig.Results().At(0)
+		c.Result, err = l.loadField(pkg, obj, "", "")
+		if err != nil {
+			return nil, err
+		}
 	}
-	ftype := node.Type.(*ast.FuncType)
-	if sig.Params().Len() != ftype.Params.NumFields() {
-		panic("bug: mismatching method params")
-	}
-	for i, p := range ftype.Params.List {
-		pt := sig.Params().At(i)
-		fld, err := l.loadField(pkg, p, pt, "")
+	for i := 0; i < sig.Params().Len(); i++ {
+		obj := sig.Params().At(i)
+		fld, err := l.loadField(pkg, obj, "", "")
 		if err != nil {
 			return nil, err
 		}
@@ -136,20 +137,29 @@
 	return c, nil
 }
 
-func (l *fieldLoader) loadField(pkg *packages.Package, node *ast.Field, obj *types.Var, tag string) (*Field, error) {
+func (l *fieldLoader) loadField(pkg *packages.Package, obj *types.Var, doc, tag string) (*Field, error) {
 	if existing, ok := l.loaded[obj]; ok {
 		return existing, nil
 	}
 	fld := &Field{
 		Name:    obj.Name(),
-		Doc:     strings.TrimSpace(node.Doc.Text()),
+		Doc:     strings.TrimSpace(doc),
 		Type:    obj.Type(),
 		JSONTag: reflect.StructTag(tag).Get("json"),
 	}
 	under := fld.Type.Underlying()
-	if p, ok := under.(*types.Pointer); ok {
-		under = p.Elem()
+	// Quick-and-dirty handling for various underyling types.
+	switch p := under.(type) {
+	case *types.Pointer:
+		under = p.Elem().Underlying()
+	case *types.Array:
+		under = p.Elem().Underlying()
+		fld.FieldMod = fmt.Sprintf("[%d]", p.Len())
+	case *types.Slice:
+		under = p.Elem().Underlying()
+		fld.FieldMod = "[]"
 	}
+
 	if s, ok := under.(*types.Struct); ok {
 		for i := 0; i < s.NumFields(); i++ {
 			obj2 := s.Field(i)
@@ -160,12 +170,12 @@
 					return nil, fmt.Errorf("missing import for %q: %q", pkg.ID, obj2.Pkg().Path())
 				}
 			}
-			node2, err := findField(pkg2, obj2.Pos())
+			node, err := findField(pkg2, obj2.Pos())
 			if err != nil {
 				return nil, err
 			}
 			tag := s.Tag(i)
-			structField, err := l.loadField(pkg2, node2, obj2, tag)
+			structField, err := l.loadField(pkg2, obj2, node.Doc.Text(), tag)
 			if err != nil {
 				return nil, err
 			}
diff --git a/internal/lsp/command/gen/gen.go b/internal/lsp/command/gen/gen.go
index 3934f1a..8f7a2d5 100644
--- a/internal/lsp/command/gen/gen.go
+++ b/internal/lsp/command/gen/gen.go
@@ -120,9 +120,11 @@
 				d.Imports[pth] = true
 			}
 		}
-		pth := pkgPath(c.Result)
-		if pth != "" && pth != thispkg {
-			d.Imports[pth] = true
+		if c.Result != nil {
+			pth := pkgPath(c.Result.Type)
+			if pth != "" && pth != thispkg {
+				d.Imports[pth] = true
+			}
 		}
 	}
 
diff --git a/internal/lsp/command/interface.go b/internal/lsp/command/interface.go
index 2347950..03e40a8 100644
--- a/internal/lsp/command/interface.go
+++ b/internal/lsp/command/interface.go
@@ -78,22 +78,22 @@
 	// Checks for module upgrades.
 	CheckUpgrades(context.Context, CheckUpgradesArgs) error
 
-	// AddDependency: Add dependency
+	// AddDependency: Add a dependency
 	//
 	// Adds a dependency to the go.mod file for a module.
 	AddDependency(context.Context, DependencyArgs) error
 
-	// UpgradeDependency: Upgrade dependency
+	// UpgradeDependency: Upgrade a dependency
 	//
 	// Upgrades a dependency in the go.mod file for a module.
 	UpgradeDependency(context.Context, DependencyArgs) error
 
-	// RemoveDependency: Remove dependency
+	// RemoveDependency: Remove a dependency
 	//
 	// Removes a dependency from the go.mod file of a module.
 	RemoveDependency(context.Context, RemoveDependencyArgs) error
 
-	// GoGetPackage: go get package
+	// GoGetPackage: go get a package
 	//
 	// Runs `go get` to fetch a package.
 	GoGetPackage(context.Context, GoGetPackageArgs) error
@@ -115,17 +115,25 @@
 	// (Re)generate the gopls.mod file for a workspace.
 	GenerateGoplsMod(context.Context, URIArg) error
 
-	// ListKnownPackages: retrieves a list of packages
-	// that are importable from the given URI.
+	// ListKnownPackages: List known packages
+	//
+	// Retrieve a list of packages that are importable from the given URI.
 	ListKnownPackages(context.Context, URIArg) (ListKnownPackagesResult, error)
 
-	// AddImport: asks the server to add an import path to a given Go file.
-	// The method will call applyEdit on the client so that clients don't have
-	// to apply the edit themselves.
+	// AddImport: Add an import
+	//
+	// Ask the server to add an import path to a given Go file.  The method will
+	// call applyEdit on the client so that clients don't have to apply the edit
+	// themselves.
 	AddImport(context.Context, AddImportArgs) error
 
+	// WorkspaceMetadata: Query workspace metadata
+	//
+	// Query the server for information about active workspaces.
 	WorkspaceMetadata(context.Context) (WorkspaceMetadataResult, error)
 
+	// StartDebugging: Start the gopls debug server if it isn't running, and
+	// return the debug address.
 	StartDebugging(context.Context, DebuggingArgs) (DebuggingResult, error)
 }
 
@@ -223,11 +231,14 @@
 }
 
 type WorkspaceMetadataResult struct {
+	// All workspaces for this session.
 	Workspaces []Workspace
 }
 
 type Workspace struct {
-	Name      string
+	// The workspace name.
+	Name string
+	// The workspace module directory.
 	ModuleDir string
 }
 
diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go
index 2d7db9a..f87aadc 100755
--- a/internal/lsp/source/api_json.go
+++ b/internal/lsp/source/api_json.go
@@ -731,124 +731,144 @@
 	},
 	Commands: []*CommandJSON{
 		{
-			Command: "gopls.add_dependency",
-			Title:   "Add dependency",
-			Doc:     "Adds a dependency to the go.mod file for a module.",
-			ArgDoc:  "{\n\t// The go.mod file URI.\n\t\"URI\": string,\n\t// Additional args to pass to the go command.\n\t\"GoCmdArgs\": []string,\n\t// Whether to add a require directive.\n\t\"AddRequire\": bool,\n}",
+			Command:   "gopls.add_dependency",
+			Title:     "Add a dependency",
+			Doc:       "Adds a dependency to the go.mod file for a module.",
+			ArgDoc:    "{\n\t// The go.mod file URI.\n\t\"URI\": string,\n\t// Additional args to pass to the go command.\n\t\"GoCmdArgs\": []string,\n\t// Whether to add a require directive.\n\t\"AddRequire\": bool,\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.add_import",
-			Title:   "asks the server to add an import path to a given Go file.",
-			Doc:     "The method will call applyEdit on the client so that clients don't have\nto apply the edit themselves.",
-			ArgDoc:  "{\n\t// ImportPath is the target import path that should\n\t// be added to the URI file\n\t\"ImportPath\": string,\n\t// URI is the file that the ImportPath should be\n\t// added to\n\t\"URI\": string,\n}",
+			Command:   "gopls.add_import",
+			Title:     "Add an import",
+			Doc:       "Ask the server to add an import path to a given Go file.  The method will\ncall applyEdit on the client so that clients don't have to apply the edit\nthemselves.",
+			ArgDoc:    "{\n\t// ImportPath is the target import path that should\n\t// be added to the URI file\n\t\"ImportPath\": string,\n\t// URI is the file that the ImportPath should be\n\t// added to\n\t\"URI\": string,\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.apply_fix",
-			Title:   "Apply a fix",
-			Doc:     "Applies a fix to a region of source code.",
-			ArgDoc:  "{\n\t// The fix to apply.\n\t\"Fix\": string,\n\t// The file URI for the document to fix.\n\t\"URI\": string,\n\t// The document range to scan for fixes.\n\t\"Range\": {\n\t\t\"start\": {\n\t\t\t\"line\": uint32,\n\t\t\t\"character\": uint32,\n\t\t},\n\t\t\"end\": {\n\t\t\t\"line\": uint32,\n\t\t\t\"character\": uint32,\n\t\t},\n\t},\n}",
+			Command:   "gopls.apply_fix",
+			Title:     "Apply a fix",
+			Doc:       "Applies a fix to a region of source code.",
+			ArgDoc:    "{\n\t// The fix to apply.\n\t\"Fix\": string,\n\t// The file URI for the document to fix.\n\t\"URI\": string,\n\t// The document range to scan for fixes.\n\t\"Range\": {\n\t\t\"start\": {\n\t\t\t\"line\": uint32,\n\t\t\t\"character\": uint32,\n\t\t},\n\t\t\"end\": {\n\t\t\t\"line\": uint32,\n\t\t\t\"character\": uint32,\n\t\t},\n\t},\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.check_upgrades",
-			Title:   "Check for upgrades",
-			Doc:     "Checks for module upgrades.",
-			ArgDoc:  "{\n\t// The go.mod file URI.\n\t\"URI\": string,\n\t// The modules to check.\n\t\"Modules\": []string,\n}",
+			Command:   "gopls.check_upgrades",
+			Title:     "Check for upgrades",
+			Doc:       "Checks for module upgrades.",
+			ArgDoc:    "{\n\t// The go.mod file URI.\n\t\"URI\": string,\n\t// The modules to check.\n\t\"Modules\": []string,\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.gc_details",
-			Title:   "Toggle gc_details",
-			Doc:     "Toggle the calculation of gc annotations.",
-			ArgDoc:  "string",
+			Command:   "gopls.gc_details",
+			Title:     "Toggle gc_details",
+			Doc:       "Toggle the calculation of gc annotations.",
+			ArgDoc:    "string",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.generate",
-			Title:   "Run go generate",
-			Doc:     "Runs `go generate` for a given directory.",
-			ArgDoc:  "{\n\t// URI for the directory to generate.\n\t\"Dir\": string,\n\t// Whether to generate recursively (go generate ./...)\n\t\"Recursive\": bool,\n}",
+			Command:   "gopls.generate",
+			Title:     "Run go generate",
+			Doc:       "Runs `go generate` for a given directory.",
+			ArgDoc:    "{\n\t// URI for the directory to generate.\n\t\"Dir\": string,\n\t// Whether to generate recursively (go generate ./...)\n\t\"Recursive\": bool,\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.generate_gopls_mod",
-			Title:   "Generate gopls.mod",
-			Doc:     "(Re)generate the gopls.mod file for a workspace.",
-			ArgDoc:  "{\n\t// The file URI.\n\t\"URI\": string,\n}",
+			Command:   "gopls.generate_gopls_mod",
+			Title:     "Generate gopls.mod",
+			Doc:       "(Re)generate the gopls.mod file for a workspace.",
+			ArgDoc:    "{\n\t// The file URI.\n\t\"URI\": string,\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.go_get_package",
-			Title:   "go get package",
-			Doc:     "Runs `go get` to fetch a package.",
-			ArgDoc:  "{\n\t// Any document URI within the relevant module.\n\t\"URI\": string,\n\t// The package to go get.\n\t\"Pkg\": string,\n\t\"AddRequire\": bool,\n}",
+			Command:   "gopls.go_get_package",
+			Title:     "go get a package",
+			Doc:       "Runs `go get` to fetch a package.",
+			ArgDoc:    "{\n\t// Any document URI within the relevant module.\n\t\"URI\": string,\n\t// The package to go get.\n\t\"Pkg\": string,\n\t\"AddRequire\": bool,\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.list_known_packages",
-			Title:   "retrieves a list of packages",
-			Doc:     "that are importable from the given URI.",
-			ArgDoc:  "{\n\t// The file URI.\n\t\"URI\": string,\n}",
+			Command:   "gopls.list_known_packages",
+			Title:     "List known packages",
+			Doc:       "Retrieve a list of packages that are importable from the given URI.",
+			ArgDoc:    "{\n\t// The file URI.\n\t\"URI\": string,\n}",
+			ResultDoc: "{\n\t// Packages is a list of packages relative\n\t// to the URIArg passed by the command request.\n\t// In other words, it omits paths that are already\n\t// imported or cannot be imported due to compiler\n\t// restrictions.\n\t\"Packages\": []string,\n}",
 		},
 		{
-			Command: "gopls.regenerate_cgo",
-			Title:   "Regenerate cgo",
-			Doc:     "Regenerates cgo definitions.",
-			ArgDoc:  "{\n\t// The file URI.\n\t\"URI\": string,\n}",
+			Command:   "gopls.regenerate_cgo",
+			Title:     "Regenerate cgo",
+			Doc:       "Regenerates cgo definitions.",
+			ArgDoc:    "{\n\t// The file URI.\n\t\"URI\": string,\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.remove_dependency",
-			Title:   "Remove dependency",
-			Doc:     "Removes a dependency from the go.mod file of a module.",
-			ArgDoc:  "{\n\t// The go.mod file URI.\n\t\"URI\": string,\n\t// The module path to remove.\n\t\"ModulePath\": string,\n\t\"OnlyDiagnostic\": bool,\n}",
+			Command:   "gopls.remove_dependency",
+			Title:     "Remove a dependency",
+			Doc:       "Removes a dependency from the go.mod file of a module.",
+			ArgDoc:    "{\n\t// The go.mod file URI.\n\t\"URI\": string,\n\t// The module path to remove.\n\t\"ModulePath\": string,\n\t\"OnlyDiagnostic\": bool,\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.run_tests",
-			Title:   "Run test(s)",
-			Doc:     "Runs `go test` for a specific set of test or benchmark functions.",
-			ArgDoc:  "{\n\t// The test file containing the tests to run.\n\t\"URI\": string,\n\t// Specific test names to run, e.g. TestFoo.\n\t\"Tests\": []string,\n\t// Specific benchmarks to run, e.g. BenchmarkFoo.\n\t\"Benchmarks\": []string,\n}",
+			Command:   "gopls.run_tests",
+			Title:     "Run test(s)",
+			Doc:       "Runs `go test` for a specific set of test or benchmark functions.",
+			ArgDoc:    "{\n\t// The test file containing the tests to run.\n\t\"URI\": string,\n\t// Specific test names to run, e.g. TestFoo.\n\t\"Tests\": []string,\n\t// Specific benchmarks to run, e.g. BenchmarkFoo.\n\t\"Benchmarks\": []string,\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.start_debugging",
-			Title:   "",
-			Doc:     "",
-			ArgDoc:  "{\n\t// Optional: the address (including port) for the debug server to listen on.\n\t// If not provided, the debug server will bind to \"localhost:0\", and the\n\t// full debug URL will be contained in the result.\n\t// \n\t// If there is more than one gopls instance along the serving path (i.e. you\n\t// are using a daemon), each gopls instance will attempt to start debugging.\n\t// If Addr specifies a port, only the daemon will be able to bind to that\n\t// port, and each intermediate gopls instance will fail to start debugging.\n\t// For this reason it is recommended not to specify a port (or equivalently,\n\t// to specify \":0\").\n\t// \n\t// If the server was already debugging this field has no effect, and the\n\t// result will contain the previously configured debug URL(s).\n\t\"Addr\": string,\n}",
+			Command:   "gopls.start_debugging",
+			Title:     "Start the gopls debug server if it isn't running, and",
+			Doc:       "return the debug address.",
+			ArgDoc:    "{\n\t// Optional: the address (including port) for the debug server to listen on.\n\t// If not provided, the debug server will bind to \"localhost:0\", and the\n\t// full debug URL will be contained in the result.\n\t// \n\t// If there is more than one gopls instance along the serving path (i.e. you\n\t// are using a daemon), each gopls instance will attempt to start debugging.\n\t// If Addr specifies a port, only the daemon will be able to bind to that\n\t// port, and each intermediate gopls instance will fail to start debugging.\n\t// For this reason it is recommended not to specify a port (or equivalently,\n\t// to specify \":0\").\n\t// \n\t// If the server was already debugging this field has no effect, and the\n\t// result will contain the previously configured debug URL(s).\n\t\"Addr\": string,\n}",
+			ResultDoc: "{\n\t// The URLs to use to access the debug servers, for all gopls instances in\n\t// the serving path. For the common case of a single gopls instance (i.e. no\n\t// daemon), this will be exactly one address.\n\t// \n\t// In the case of one or more gopls instances forwarding the LSP to a daemon,\n\t// URLs will contain debug addresses for each server in the serving path, in\n\t// serving order. The daemon debug address will be the last entry in the\n\t// slice. If any intermediate gopls instance fails to start debugging, no\n\t// error will be returned but the debug URL for that server in the URLs slice\n\t// will be empty.\n\t\"URLs\": []string,\n}",
 		},
 		{
-			Command: "gopls.test",
-			Title:   "Run test(s) (legacy)",
-			Doc:     "Runs `go test` for a specific set of test or benchmark functions.",
-			ArgDoc:  "string,\n[]string,\n[]string",
+			Command:   "gopls.test",
+			Title:     "Run test(s) (legacy)",
+			Doc:       "Runs `go test` for a specific set of test or benchmark functions.",
+			ArgDoc:    "string,\n[]string,\n[]string",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.tidy",
-			Title:   "Run go mod tidy",
-			Doc:     "Runs `go mod tidy` for a module.",
-			ArgDoc:  "{\n\t// The file URIs.\n\t\"URIs\": []string,\n}",
+			Command:   "gopls.tidy",
+			Title:     "Run go mod tidy",
+			Doc:       "Runs `go mod tidy` for a module.",
+			ArgDoc:    "{\n\t// The file URIs.\n\t\"URIs\": []string,\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.toggle_gc_details",
-			Title:   "Toggle gc_details",
-			Doc:     "Toggle the calculation of gc annotations.",
-			ArgDoc:  "{\n\t// The file URI.\n\t\"URI\": string,\n}",
+			Command:   "gopls.toggle_gc_details",
+			Title:     "Toggle gc_details",
+			Doc:       "Toggle the calculation of gc annotations.",
+			ArgDoc:    "{\n\t// The file URI.\n\t\"URI\": string,\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.update_go_sum",
-			Title:   "Update go.sum",
-			Doc:     "Updates the go.sum file for a module.",
-			ArgDoc:  "{\n\t// The file URIs.\n\t\"URIs\": []string,\n}",
+			Command:   "gopls.update_go_sum",
+			Title:     "Update go.sum",
+			Doc:       "Updates the go.sum file for a module.",
+			ArgDoc:    "{\n\t// The file URIs.\n\t\"URIs\": []string,\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.upgrade_dependency",
-			Title:   "Upgrade dependency",
-			Doc:     "Upgrades a dependency in the go.mod file for a module.",
-			ArgDoc:  "{\n\t// The go.mod file URI.\n\t\"URI\": string,\n\t// Additional args to pass to the go command.\n\t\"GoCmdArgs\": []string,\n\t// Whether to add a require directive.\n\t\"AddRequire\": bool,\n}",
+			Command:   "gopls.upgrade_dependency",
+			Title:     "Upgrade a dependency",
+			Doc:       "Upgrades a dependency in the go.mod file for a module.",
+			ArgDoc:    "{\n\t// The go.mod file URI.\n\t\"URI\": string,\n\t// Additional args to pass to the go command.\n\t\"GoCmdArgs\": []string,\n\t// Whether to add a require directive.\n\t\"AddRequire\": bool,\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.vendor",
-			Title:   "Run go mod vendor",
-			Doc:     "Runs `go mod vendor` for a module.",
-			ArgDoc:  "{\n\t// The file URI.\n\t\"URI\": string,\n}",
+			Command:   "gopls.vendor",
+			Title:     "Run go mod vendor",
+			Doc:       "Runs `go mod vendor` for a module.",
+			ArgDoc:    "{\n\t// The file URI.\n\t\"URI\": string,\n}",
+			ResultDoc: "",
 		},
 		{
-			Command: "gopls.workspace_metadata",
-			Title:   "",
-			Doc:     "",
-			ArgDoc:  "",
+			Command:   "gopls.workspace_metadata",
+			Title:     "Query workspace metadata",
+			Doc:       "Query the server for information about active workspaces.",
+			ArgDoc:    "",
+			ResultDoc: "{\n\t// All workspaces for this session.\n\t\"Workspaces\": []{\n\t\t\"Name\": string,\n\t\t\"ModuleDir\": string,\n\t},\n}",
 		},
 	},
 	Lenses: []*LensJSON{
@@ -879,7 +899,7 @@
 		},
 		{
 			Lens:  "upgrade_dependency",
-			Title: "Upgrade dependency",
+			Title: "Upgrade a dependency",
 			Doc:   "Upgrades a dependency in the go.mod file for a module.",
 		},
 		{
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 3dc3f17..c0e4c90 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -1267,10 +1267,11 @@
 }
 
 type CommandJSON struct {
-	Command string
-	Title   string
-	Doc     string
-	ArgDoc  string
+	Command   string
+	Title     string
+	Doc       string
+	ArgDoc    string
+	ResultDoc string
 }
 
 type LensJSON struct {