gopls/internal/golang: add "Browse gopls features" code action
This command opens the Index of Features doc page:
$ gopls codeaction -kind=gopls.doc.features -exec ./gopls/main.go
VS Code exposes this new code action through the Quick Fix
menu (Command-.) under the section "More actions...".
It should probably also be given a top-level command similar
to "Go: Add Import", etc.
Other editors seem to treat code actions
more uniformly, so special handling is unnecessary.
Change-Id: I633dd34cdb9005009a098bcd7bb50d0db06044c7
Reviewed-on: https://go-review.googlesource.com/c/tools/+/595557
Commit-Queue: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
diff --git a/gopls/doc/commands.md b/gopls/doc/commands.md
index 5eda89f..7a4e251 100644
--- a/gopls/doc/commands.md
+++ b/gopls/doc/commands.md
@@ -262,6 +262,17 @@
}
```
+<a id='gopls.client_open_url'></a>
+## `gopls.client_open_url`: **Request that the client open a URL in a browser.**
+
+
+
+Args:
+
+```
+string
+```
+
<a id='gopls.diagnose_files'></a>
## `gopls.diagnose_files`: **Cause server to publish diagnostics for the specified files.**
diff --git a/gopls/doc/features/README.md b/gopls/doc/features/README.md
index fc559fd..56648c2 100644
--- a/gopls/doc/features/README.md
+++ b/gopls/doc/features/README.md
@@ -38,7 +38,7 @@
- [Type Definition](navigation.md#type-definition): go to definition of type of selected symbol
- [References](navigation.md#references): list references to selected symbol
- [Implementation](navigation.md#implementation): show "implements" relationships of selected type
- - [Document Symbol](passive.md#document-symbol): outline of symbols defined in current file
+ - [Document Symbol](navigation.md#document-symbol): outline of symbols defined in current file
- [Symbol](navigation.md#symbol): fuzzy search for symbol by name
- [Selection Range](navigation.md#selection-range): select enclosing unit of syntax
- [Call Hierarchy](navigation.md#call-hierarchy): show outgoing/incoming calls to the current function
@@ -59,3 +59,8 @@
- [go.mod and go.work files](modfiles.md): Go module and workspace manifests
- [Command-line interface](../command-line.md): CLI for debugging and scripting (unstable)
- [Non-standard commands](../commands.md): gopls-specific RPC protocol extensions (unstable)
+
+You can find this page from within your editor by executing the
+`gopls.doc.features` [code action](transformation.md#code-actions),
+which opens it in a web browser.
+In VS Code, you can find it on the Quick fix menu.
diff --git a/gopls/internal/cmd/codeaction.go b/gopls/internal/cmd/codeaction.go
index 83fcccd..cb82e95 100644
--- a/gopls/internal/cmd/codeaction.go
+++ b/gopls/internal/cmd/codeaction.go
@@ -58,6 +58,7 @@
source.doc
source.freesymbols
goTest
+ gopls.doc.features
Kinds are hierarchical, so "refactor" includes "refactor.inline".
(Note: actions of kind "goTest" are not returned unless explicitly
diff --git a/gopls/internal/cmd/integration_test.go b/gopls/internal/cmd/integration_test.go
index e9bf1ab..f4d76b9 100644
--- a/gopls/internal/cmd/integration_test.go
+++ b/gopls/internal/cmd/integration_test.go
@@ -977,10 +977,13 @@
}
// list code actions in file, filtering by title
{
- res := gopls(t, tree, "codeaction", "-title=Br.wse", "a.go")
+ res := gopls(t, tree, "codeaction", "-title=Browse.*doc", "a.go")
res.checkExit(true)
got := res.stdout
- want := `command "Browse documentation for package a" [source.doc]` + "\n"
+ want := `command "Browse gopls feature documentation" [gopls.doc.features]` +
+ "\n" +
+ `command "Browse documentation for package a" [source.doc]` +
+ "\n"
if got != want {
t.Errorf("codeaction: got <<%s>>, want <<%s>>\nstderr:\n%s", got, want, res.stderr)
}
diff --git a/gopls/internal/cmd/usage/codeaction.hlp b/gopls/internal/cmd/usage/codeaction.hlp
index 977bcd4..edc6a3e 100644
--- a/gopls/internal/cmd/usage/codeaction.hlp
+++ b/gopls/internal/cmd/usage/codeaction.hlp
@@ -29,6 +29,7 @@
source.doc
source.freesymbols
goTest
+ gopls.doc.features
Kinds are hierarchical, so "refactor" includes "refactor.inline".
(Note: actions of kind "goTest" are not returned unless explicitly
diff --git a/gopls/internal/doc/api.json b/gopls/internal/doc/api.json
index 88a77cf..c0c4370 100644
--- a/gopls/internal/doc/api.json
+++ b/gopls/internal/doc/api.json
@@ -990,6 +990,13 @@
"ResultDoc": ""
},
{
+ "Command": "gopls.client_open_url",
+ "Title": "Request that the client open a URL in a browser.",
+ "Doc": "",
+ "ArgDoc": "string",
+ "ResultDoc": ""
+ },
+ {
"Command": "gopls.diagnose_files",
"Title": "Cause server to publish diagnostics for the specified files.",
"Doc": "This command is needed by the 'gopls {check,fix}' CLI subcommands.",
diff --git a/gopls/internal/golang/codeaction.go b/gopls/internal/golang/codeaction.go
index bf26458..31d036b 100644
--- a/gopls/internal/golang/codeaction.go
+++ b/gopls/internal/golang/codeaction.go
@@ -48,7 +48,8 @@
if wantQuickFixes ||
want[protocol.SourceOrganizeImports] ||
want[protocol.RefactorExtract] ||
- want[settings.GoFreeSymbols] {
+ want[settings.GoFreeSymbols] ||
+ want[settings.GoplsDocFeatures] {
pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
if err != nil {
@@ -115,6 +116,22 @@
Command: &cmd,
})
}
+
+ if want[settings.GoplsDocFeatures] {
+ // TODO(adonovan): after the docs are published in gopls/v0.17.0,
+ // use the gopls release tag instead of master.
+ cmd, err := command.NewClientOpenURLCommand(
+ "Browse gopls feature documentation",
+ "https://github.com/golang/tools/blob/master/gopls/doc/features/README.md")
+ if err != nil {
+ return nil, err
+ }
+ actions = append(actions, protocol.CodeAction{
+ Title: cmd.Title,
+ Kind: settings.GoplsDocFeatures,
+ Command: &cmd,
+ })
+ }
}
// Code actions requiring type information.
diff --git a/gopls/internal/protocol/command/command_gen.go b/gopls/internal/protocol/command/command_gen.go
index d89525b..7f9ba1b 100644
--- a/gopls/internal/protocol/command/command_gen.go
+++ b/gopls/internal/protocol/command/command_gen.go
@@ -31,6 +31,7 @@
Assembly Command = "gopls.assembly"
ChangeSignature Command = "gopls.change_signature"
CheckUpgrades Command = "gopls.check_upgrades"
+ ClientOpenURL Command = "gopls.client_open_url"
DiagnoseFiles Command = "gopls.diagnose_files"
Doc Command = "gopls.doc"
EditGoDirective Command = "gopls.edit_go_directive"
@@ -74,6 +75,7 @@
Assembly,
ChangeSignature,
CheckUpgrades,
+ ClientOpenURL,
DiagnoseFiles,
Doc,
EditGoDirective,
@@ -155,6 +157,12 @@
return nil, err
}
return nil, s.CheckUpgrades(ctx, a0)
+ case ClientOpenURL:
+ var a0 string
+ if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
+ return nil, err
+ }
+ return nil, s.ClientOpenURL(ctx, a0)
case DiagnoseFiles:
var a0 DiagnoseFilesArgs
if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
@@ -424,6 +432,18 @@
}, nil
}
+func NewClientOpenURLCommand(title string, a0 string) (protocol.Command, error) {
+ args, err := MarshalArgs(a0)
+ if err != nil {
+ return protocol.Command{}, err
+ }
+ return protocol.Command{
+ Title: title,
+ Command: ClientOpenURL.String(),
+ Arguments: args,
+ }, nil
+}
+
func NewDiagnoseFilesCommand(title string, a0 DiagnoseFilesArgs) (protocol.Command, error) {
args, err := MarshalArgs(a0)
if err != nil {
diff --git a/gopls/internal/protocol/command/interface.go b/gopls/internal/protocol/command/interface.go
index ba9f200..7bb1006 100644
--- a/gopls/internal/protocol/command/interface.go
+++ b/gopls/internal/protocol/command/interface.go
@@ -265,6 +265,9 @@
// The machine architecture is determined by the view.
Assembly(_ context.Context, viewID, packageID, symbol string) error
+ // ClientOpenURL: Request that the client open a URL in a browser.
+ ClientOpenURL(_ context.Context, url string) error
+
// ScanImports: force a sychronous scan of the imports cache.
//
// This command is intended for use by gopls tests only.
diff --git a/gopls/internal/server/code_action.go b/gopls/internal/server/code_action.go
index dd6abe0..fe1c885 100644
--- a/gopls/internal/server/code_action.go
+++ b/gopls/internal/server/code_action.go
@@ -143,7 +143,8 @@
case settings.GoTest,
settings.GoDoc,
settings.GoFreeSymbols,
- settings.GoAssembly:
+ settings.GoAssembly,
+ settings.GoplsDocFeatures:
return false // read-only query
}
return true // potential write operation
diff --git a/gopls/internal/server/command.go b/gopls/internal/server/command.go
index c5871be..25a7f33 100644
--- a/gopls/internal/server/command.go
+++ b/gopls/internal/server/command.go
@@ -1462,6 +1462,11 @@
return nil
}
+func (c *commandHandler) ClientOpenURL(ctx context.Context, url string) error {
+ openClientBrowser(ctx, c.s.client, url)
+ return nil
+}
+
func (c *commandHandler) ScanImports(ctx context.Context) error {
for _, v := range c.s.session.Views() {
v.ScanImports()
diff --git a/gopls/internal/settings/codeactionkind.go b/gopls/internal/settings/codeactionkind.go
index 0731115..dea2e69 100644
--- a/gopls/internal/settings/codeactionkind.go
+++ b/gopls/internal/settings/codeactionkind.go
@@ -31,7 +31,8 @@
// actions with kind="source.*". A lightbulb appears in both cases.
// A third menu, "Quick fix...", not found on the usual context
// menu but accessible through the command palette or "⌘.",
-// displays code actions of kind "quickfix.*" and "refactor.*".
+// displays code actions of kind "quickfix.*" and "refactor.*",
+// and ad hoc ones ("More actions...") such as "gopls.*".
// All of these CodeAction requests have triggerkind=Invoked.
//
// Cursor motion also performs a CodeAction request, but with
@@ -76,8 +77,9 @@
// instead of == for CodeActionKinds throughout gopls.
// See golang/go#40438 for related discussion.
const (
- GoAssembly protocol.CodeActionKind = "source.assembly"
- GoDoc protocol.CodeActionKind = "source.doc"
- GoFreeSymbols protocol.CodeActionKind = "source.freesymbols"
- GoTest protocol.CodeActionKind = "goTest" // TODO(adonovan): rename "source.test"
+ GoAssembly protocol.CodeActionKind = "source.assembly"
+ GoDoc protocol.CodeActionKind = "source.doc"
+ GoFreeSymbols protocol.CodeActionKind = "source.freesymbols"
+ GoTest protocol.CodeActionKind = "goTest" // TODO(adonovan): rename "source.test"
+ GoplsDocFeatures protocol.CodeActionKind = "gopls.doc.features"
)
diff --git a/gopls/internal/settings/default.go b/gopls/internal/settings/default.go
index 8d5eb6b..7b14d2a 100644
--- a/gopls/internal/settings/default.go
+++ b/gopls/internal/settings/default.go
@@ -53,6 +53,7 @@
GoDoc: true,
GoFreeSymbols: true,
// Not GoTest: it must be explicit in CodeActionParams.Context.Only
+ GoplsDocFeatures: true,
},
file.Mod: {
protocol.SourceOrganizeImports: true,
diff --git a/gopls/internal/test/integration/misc/codeactions_test.go b/gopls/internal/test/integration/misc/codeactions_test.go
index 376bbe3..b0325d0 100644
--- a/gopls/internal/test/integration/misc/codeactions_test.go
+++ b/gopls/internal/test/integration/misc/codeactions_test.go
@@ -67,12 +67,14 @@
settings.GoAssembly,
settings.GoDoc,
settings.GoFreeSymbols,
+ settings.GoplsDocFeatures,
protocol.RefactorExtract,
protocol.RefactorInline)
check("gen/a.go",
settings.GoAssembly,
settings.GoDoc,
- settings.GoFreeSymbols)
+ settings.GoFreeSymbols,
+ settings.GoplsDocFeatures)
})
}
diff --git a/gopls/internal/test/integration/misc/webserver_test.go b/gopls/internal/test/integration/misc/webserver_test.go
index 5bb709f..8105fd0 100644
--- a/gopls/internal/test/integration/misc/webserver_test.go
+++ b/gopls/internal/test/integration/misc/webserver_test.go
@@ -5,6 +5,7 @@
package misc
import (
+ "fmt"
"html"
"io"
"net/http"
@@ -15,6 +16,7 @@
"golang.org/x/tools/gopls/internal/protocol"
"golang.org/x/tools/gopls/internal/protocol/command"
+ "golang.org/x/tools/gopls/internal/settings"
. "golang.org/x/tools/gopls/internal/test/integration"
"golang.org/x/tools/internal/testenv"
)
@@ -271,18 +273,10 @@
func viewPkgDoc(t *testing.T, env *Env, loc protocol.Location) protocol.URI {
// Invoke the "Browse package documentation" code
// action to start the server.
- var docAction *protocol.CodeAction
actions := env.CodeAction(loc, nil, 0)
- for _, act := range actions {
- if strings.HasPrefix(act.Title, "Browse ") &&
- strings.Contains(act.Title, "documentation") {
- docAction = &act
- break
- }
- }
- if docAction == nil {
- t.Fatalf("can't find action with Title 'Browse...documentation', only %#v",
- actions)
+ docAction, err := codeActionByKind(actions, settings.GoDoc)
+ if err != nil {
+ t.Fatal(err)
}
// Execute the command.
@@ -335,16 +329,9 @@
if err != nil {
t.Fatalf("CodeAction: %v", err)
}
- var action *protocol.CodeAction
- for _, a := range actions {
- if a.Title == "Browse free symbols" {
- action = &a
- break
- }
- }
- if action == nil {
- t.Fatalf("can't find action with Title 'Browse free symbols', only %#v",
- actions)
+ action, err := codeActionByKind(actions, settings.GoFreeSymbols)
+ if err != nil {
+ t.Fatal(err)
}
// Execute the command.
@@ -401,17 +388,9 @@
if err != nil {
t.Fatalf("CodeAction: %v", err)
}
- const wantTitle = "Browse " + runtime.GOARCH + " assembly for f"
- var action *protocol.CodeAction
- for _, a := range actions {
- if a.Title == wantTitle {
- action = &a
- break
- }
- }
- if action == nil {
- t.Fatalf("can't find action with Title %s, only %#v",
- wantTitle, actions)
+ action, err := codeActionByKind(actions, settings.GoAssembly)
+ if err != nil {
+ t.Fatal(err)
}
// Execute the command.
@@ -504,3 +483,13 @@
}
}
}
+
+// codeActionByKind returns the first action of the specified kind, or an error.
+func codeActionByKind(actions []protocol.CodeAction, kind protocol.CodeActionKind) (*protocol.CodeAction, error) {
+ for _, act := range actions {
+ if act.Kind == kind {
+ return &act, nil
+ }
+ }
+ return nil, fmt.Errorf("can't find action with kind %s, only %#v", kind, actions)
+}