internal/lsp: switch to the new command API

Fully switch to the new generated command API, and remove the old
dynamic command configuration.

This involved several steps:
 + Switch the command dispatch in internal/lsp/command.go to go through
   the command package. This means that all commands must now use the new
   signature.
 + Update commandHandler to use the new command signatures.
 + Fix some errors discovered in the command interface now that we're
   actually using it.
 + Regenerate bindings.
 + Update all code lens and suggested fixes to new the new command
   constructors.
 + Generate values in the command package to hold command names and the
   full set of commands, so that they may be referenced by name.
 + Update any references to command names to use the command package.
 + Delete command metadata from the source package. Rename command.go to
   fix.go.
 + Update lsp tests to execute commands directly rather than use an
   internal API. This involved a bit of hackery to collect the edits.
 + Update document generation to use command metadata. Documenting the
   arguments is left to a later CL.
 + Various small fixes related to the above.

This change is intended to be invisible to users. We have changed the
command signatures, but have not (previously) committed to backwards
compatibility for commands. Notably, the gopls.test and gopls.gc_details
signatures are preserved, as these are the two cases where we are aware
of LSP clients calling them directly, not from a code lens or
diagnostic.

For golang/go#40438

Change-Id: Ie1b92c95d6ce7e2fc25fc029d1f85b942f40e851
Reviewed-on: https://go-review.googlesource.com/c/tools/+/290111
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: Heschi Kreinick <heschi@google.com>
This commit is contained in:
Rob Findley 2021-02-05 17:55:31 -05:00 committed by Robert Findley
parent a30116df7a
commit 8aef11fa67
30 changed files with 685 additions and 953 deletions

View File

@ -6,105 +6,81 @@ This document describes the LSP-level commands supported by `gopls`. They cannot
### **Add dependency**
Identifier: `gopls.add_dependency`
add_dependency adds a dependency.
Adds a dependency to the go.mod file for a module.
### **Apply a fix**
Identifier: `gopls.apply_fix`
Applies a fix to a region of source code.
### **Check for upgrades**
Identifier: `gopls.check_upgrades`
check_upgrades checks for module upgrades.
### **Extract to function**
Identifier: `gopls.extract_function`
extract_function extracts statements to a function.
### **Extract to variable**
Identifier: `gopls.extract_variable`
extract_variable extracts an expression to a variable.
### **Fill struct**
Identifier: `gopls.fill_struct`
fill_struct is a gopls command to fill a struct with default
values.
Checks for module upgrades.
### **Toggle gc_details**
Identifier: `gopls.gc_details`
gc_details controls calculation of gc annotations.
Toggle the calculation of gc annotations.
### **Run go generate**
Identifier: `gopls.generate`
generate runs `go generate` for a given directory.
Runs `go generate` for a given directory.
### **Generate gopls.mod**
Identifier: `gopls.generate_gopls_mod`
generate_gopls_mod (re)generates the gopls.mod file.
(Re)generate the gopls.mod file for a workspace.
### **go get package**
Identifier: `gopls.go_get_package`
go_get_package runs `go get` to fetch a package.
Runs `go get` to fetch a package.
### **Regenerate cgo**
Identifier: `gopls.regenerate_cgo`
regenerate_cgo regenerates cgo definitions.
Regenerates cgo definitions.
### **Remove dependency**
Identifier: `gopls.remove_dependency`
remove_dependency removes a dependency.
Removes a dependency from the go.mod file of a module.
### **Run test(s)**
Identifier: `gopls.run_tests`
Runs `go test` for a specific set of test or benchmark functions.
### **Run test(s) (legacy)**
Identifier: `gopls.test`
test runs `go test` for a specific test function.
Runs `go test` for a specific set of test or benchmark functions.
### **Run go mod tidy**
Identifier: `gopls.tidy`
tidy runs `go mod tidy` for a module.
Runs `go mod tidy` for a module.
### **Toggle gc_details**
Identifier: `gopls.toggle_gc_details`
### **Undeclared name**
Identifier: `gopls.undeclared_name`
undeclared_name adds a variable declaration for an undeclared
name.
Toggle the calculation of gc annotations.
### **Update go.sum**
Identifier: `gopls.update_go_sum`
update_go_sum updates the go.sum file for a module.
Updates the go.sum file for a module.
### **Upgrade dependency**
Identifier: `gopls.upgrade_dependency`
upgrade_dependency upgrades a dependency.
Upgrades a dependency in the go.mod file for a module.
### **Run go mod vendor**
Identifier: `gopls.vendor`
vendor runs `go mod vendor` for a module.
Runs `go mod vendor` for a module.
<!-- END Commands: DO NOT MANUALLY EDIT THIS SECTION -->

View File

@ -29,6 +29,8 @@ import (
"github.com/sanity-io/litter"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/command/commandmeta"
"golang.org/x/tools/internal/lsp/mod"
"golang.org/x/tools/internal/lsp/source"
)
@ -87,7 +89,7 @@ func loadAPI() (*source.APIJSON, error) {
// Transform the internal command name to the external command name.
for _, c := range api.Commands {
c.Command = source.CommandPrefix + c.Command
c.Command = command.ID(c.Command)
}
for _, m := range []map[string]source.Analyzer{
defaults.DefaultAnalyzers,
@ -368,90 +370,40 @@ func valueDoc(name, value, doc string) string {
}
func loadCommands(pkg *packages.Package) ([]*source.CommandJSON, error) {
// The code that defines commands is much more complicated than the
// code that defines options, so reading comments for the Doc is very
// fragile. If this causes problems, we should switch to a dynamic
// approach and put the doc in the Commands struct rather than reading
// from the source code.
// Find the Commands slice.
typesSlice := pkg.Types.Scope().Lookup("Commands")
f, err := fileForPos(pkg, typesSlice.Pos())
if err != nil {
return nil, err
}
path, _ := astutil.PathEnclosingInterval(f, typesSlice.Pos(), typesSlice.Pos())
vspec := path[1].(*ast.ValueSpec)
var astSlice *ast.CompositeLit
for i, name := range vspec.Names {
if name.Name == "Commands" {
astSlice = vspec.Values[i].(*ast.CompositeLit)
}
}
var commands []*source.CommandJSON
_, cmds, err := commandmeta.Load()
if err != nil {
return nil, err
}
// Parse the objects it contains.
for _, elt := range astSlice.Elts {
// Find the composite literal of the Command.
typesCommand := pkg.TypesInfo.ObjectOf(elt.(*ast.Ident))
path, _ := astutil.PathEnclosingInterval(f, typesCommand.Pos(), typesCommand.Pos())
vspec := path[1].(*ast.ValueSpec)
var astCommand ast.Expr
for i, name := range vspec.Names {
if name.Name == typesCommand.Name() {
astCommand = vspec.Values[i]
}
}
// Read the Name and Title fields of the literal.
var name, title string
ast.Inspect(astCommand, func(n ast.Node) bool {
kv, ok := n.(*ast.KeyValueExpr)
if ok {
k := kv.Key.(*ast.Ident).Name
switch k {
case "Name":
name = strings.Trim(kv.Value.(*ast.BasicLit).Value, `"`)
case "Title":
title = strings.Trim(kv.Value.(*ast.BasicLit).Value, `"`)
}
}
return true
})
if title == "" {
title = name
}
// Conventionally, the doc starts with the name of the variable.
// Replace it with the name of the command.
doc := vspec.Doc.Text()
doc = strings.Replace(doc, typesCommand.Name(), name, 1)
for _, cmd := range cmds {
commands = append(commands, &source.CommandJSON{
Command: name,
Title: title,
Doc: doc,
Command: cmd.Name,
Title: cmd.Title,
Doc: cmd.Doc,
})
}
return commands, nil
}
func loadLenses(commands []*source.CommandJSON) []*source.LensJSON {
lensNames := map[string]struct{}{}
all := map[command.Command]struct{}{}
for k := range source.LensFuncs() {
lensNames[k] = struct{}{}
all[k] = struct{}{}
}
for k := range mod.LensFuncs() {
lensNames[k] = struct{}{}
if _, ok := all[k]; ok {
panic(fmt.Sprintf("duplicate lens %q", string(k)))
}
all[k] = struct{}{}
}
var lenses []*source.LensJSON
for _, cmd := range commands {
if _, ok := lensNames[cmd.Command]; ok {
if _, ok := all[command.Command(cmd.Command)]; ok {
lenses = append(lenses, &source.LensJSON{
Lens: cmd.Command,
Title: cmd.Title,

View File

@ -388,42 +388,35 @@ features are subject to change.
Identifier: `gc_details`
gc_details controls calculation of gc annotations.
Toggle the calculation of gc annotations.
### **Run go generate**
Identifier: `generate`
generate runs `go generate` for a given directory.
Runs `go generate` for a given directory.
### **Regenerate cgo**
Identifier: `regenerate_cgo`
regenerate_cgo regenerates cgo definitions.
### **Run test(s)**
Regenerates cgo definitions.
### **Run test(s) (legacy)**
Identifier: `test`
test runs `go test` for a specific test function.
Runs `go test` for a specific set of test or benchmark functions.
### **Run go mod tidy**
Identifier: `tidy`
tidy runs `go mod tidy` for a module.
Runs `go mod tidy` for a module.
### **Upgrade dependency**
Identifier: `upgrade_dependency`
upgrade_dependency upgrades a dependency.
Upgrades a dependency in the go.mod file for a module.
### **Run go mod vendor**
Identifier: `vendor`
vendor runs `go mod vendor` for a module.
Runs `go mod vendor` for a module.
<!-- END Lenses: DO NOT MANUALLY EDIT THIS SECTION -->

View File

@ -12,9 +12,9 @@ import (
. "golang.org/x/tools/gopls/internal/regtest"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/fake"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/tests"
"golang.org/x/tools/internal/testenv"
)
@ -53,7 +53,7 @@ const (
},
{
label: "generate disabled",
enabled: map[string]bool{source.CommandGenerate.Name: false},
enabled: map[string]bool{string(command.Generate): false},
wantCodeLens: false,
},
}
@ -161,7 +161,7 @@ require golang.org/x/hello v1.3.3
t.Run("Upgrade individual dependency", func(t *testing.T) {
WithOptions(ProxyFiles(proxyWithLatest)).Run(t, shouldUpdateDep, func(t *testing.T, env *Env) {
env.OpenFile("go.mod")
env.ExecuteCodeLensCommand("go.mod", source.CommandCheckUpgrades)
env.ExecuteCodeLensCommand("go.mod", command.CheckUpgrades)
d := &protocol.PublishDiagnosticsParams{}
env.Await(OnceMet(env.DiagnosticAtRegexpWithMessage("go.mod", `require`, "can be upgraded"),
ReadDiagnostics("go.mod", d)))
@ -219,7 +219,7 @@ func main() {
`
WithOptions(ProxyFiles(proxy)).Run(t, shouldRemoveDep, func(t *testing.T, env *Env) {
env.OpenFile("go.mod")
env.ExecuteCodeLensCommand("go.mod", source.CommandTidy)
env.ExecuteCodeLensCommand("go.mod", command.Tidy)
env.Await(env.DoneWithChangeWatchedFiles())
got := env.Editor.BufferText("go.mod")
const wantGoMod = `module mod.com
@ -266,7 +266,7 @@ func Foo() {
env.Await(env.DiagnosticAtRegexp("cgo.go", `C\.(fortytwo)`))
// Regenerate cgo, fixing the diagnostic.
env.ExecuteCodeLensCommand("cgo.go", source.CommandRegenerateCgo)
env.ExecuteCodeLensCommand("cgo.go", command.RegenerateCgo)
env.Await(EmptyDiagnostics("cgo.go"))
})
}
@ -301,7 +301,7 @@ func main() {
Timeout(60*time.Second),
).Run(t, mod, func(t *testing.T, env *Env) {
env.OpenFile("main.go")
env.ExecuteCodeLensCommand("main.go", source.CommandToggleDetails)
env.ExecuteCodeLensCommand("main.go", command.GCDetails)
d := &protocol.PublishDiagnosticsParams{}
env.Await(
OnceMet(
@ -334,7 +334,7 @@ func main() {
env.Await(DiagnosticAt("main.go", 6, 12))
// Toggle the GC details code lens again so now it should be off.
env.ExecuteCodeLensCommand("main.go", source.CommandToggleDetails)
env.ExecuteCodeLensCommand("main.go", command.GCDetails)
env.Await(
EmptyDiagnostics("main.go"),
)

View File

@ -10,7 +10,6 @@ import (
. "golang.org/x/tools/gopls/internal/regtest"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/testenv"
)
@ -71,10 +70,6 @@ func _() {
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(
DiagnosticAt("a/a1.go", 6, 5),

View File

@ -9,9 +9,9 @@ import (
"path"
"testing"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/fake"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
errors "golang.org/x/xerrors"
)
@ -299,7 +299,7 @@ func (e *Env) CodeLens(path string) []protocol.CodeLens {
// ExecuteCodeLensCommand executes the command for the code lens matching the
// given command name.
func (e *Env) ExecuteCodeLensCommand(path string, cmd *source.Command) {
func (e *Env) ExecuteCodeLensCommand(path string, cmd command.Command) {
lenses := e.CodeLens(path)
var lens protocol.CodeLens
var found bool

View File

@ -21,6 +21,7 @@ import (
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
@ -485,18 +486,16 @@ func addGoGetFixes(ctx context.Context, snapshot source.Snapshot, pkg *pkg) erro
continue
}
direct := !strings.Contains(diag.Message, "error while importing")
args, err := source.MarshalArgs(pkg.compiledGoFiles[0].URI, direct, matches[1])
title := fmt.Sprintf("go get package %v", matches[1])
cmd, err := command.NewGoGetPackageCommand(title, command.GoGetPackageArgs{
URI: protocol.URIFromSpanURI(pkg.compiledGoFiles[0].URI),
AddRequire: direct,
Pkg: matches[1],
})
if err != nil {
return err
}
diag.SuggestedFixes = append(diag.SuggestedFixes, source.SuggestedFix{
Title: fmt.Sprintf("go get package %v", matches[1]),
Command: &protocol.Command{
Title: fmt.Sprintf("go get package %v", matches[1]),
Command: source.CommandGoGetPackage.ID(),
Arguments: args,
},
})
diag.SuggestedFixes = append(diag.SuggestedFixes, source.SuggestedFixFromCommand(cmd))
}
return nil
}

View File

@ -15,6 +15,7 @@ import (
"golang.org/x/mod/module"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/gocommand"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
@ -285,7 +286,12 @@ func (s *snapshot) matchErrorToModule(ctx context.Context, fh source.FileHandle,
disabledByGOPROXY := strings.Contains(goCmdError, "disabled by GOPROXY=off")
shouldAddDep := strings.Contains(goCmdError, "to add it")
if innermost != nil && (disabledByGOPROXY || shouldAddDep) {
args, err := source.MarshalArgs(fh.URI(), false, []string{fmt.Sprintf("%v@%v", innermost.Path, innermost.Version)})
title := fmt.Sprintf("Download %v@%v", innermost.Path, innermost.Version)
cmd, err := command.NewAddDependencyCommand(title, command.DependencyArgs{
URI: protocol.URIFromSpanURI(fh.URI()),
AddRequire: false,
GoCmdArgs: []string{fmt.Sprintf("%v@%v", innermost.Path, innermost.Version)},
})
if err != nil {
return nil
}
@ -294,19 +300,12 @@ func (s *snapshot) matchErrorToModule(ctx context.Context, fh source.FileHandle,
msg = fmt.Sprintf("%v@%v has not been downloaded", innermost.Path, innermost.Version)
}
return &source.Diagnostic{
URI: fh.URI(),
Range: rng,
Severity: protocol.SeverityError,
Message: msg,
Source: source.ListError,
SuggestedFixes: []source.SuggestedFix{{
Title: fmt.Sprintf("Download %v@%v", innermost.Path, innermost.Version),
Command: &protocol.Command{
Title: source.CommandAddDependency.Title,
Command: source.CommandAddDependency.ID(),
Arguments: args,
},
}},
URI: fh.URI(),
Range: rng,
Severity: protocol.SeverityError,
Message: msg,
Source: source.ListError,
SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
}
}
diagSource := source.ListError

View File

@ -18,6 +18,7 @@ import (
"golang.org/x/mod/modfile"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/gocommand"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/diff"
"golang.org/x/tools/internal/lsp/protocol"
@ -172,13 +173,15 @@ func (s *snapshot) parseModError(ctx context.Context, fh source.FileHandle, errT
if err != nil {
return nil
}
args, err := source.MarshalArgs(protocol.URIFromSpanURI(fh.URI()))
if err != nil {
return nil
}
// TODO(rFindley): we shouldn't really need to call three constructors here.
// Reconsider this.
args := command.URIArg{protocol.URIFromSpanURI(fh.URI())}
switch {
case isInconsistentVendor:
cmd, err := command.NewVendorCommand("Run go mod vendor", args)
if err != nil {
return nil
}
return &source.Diagnostic{
URI: fh.URI(),
Range: rng,
@ -186,17 +189,18 @@ func (s *snapshot) parseModError(ctx context.Context, fh source.FileHandle, errT
Source: 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{{
Title: source.CommandVendor.Title,
Command: &protocol.Command{
Command: source.CommandVendor.ID(),
Title: source.CommandVendor.Title,
Arguments: args,
},
}},
SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
}
case isGoSumUpdates:
tidyCmd, err := command.NewTidyCommand("Run go mod tidy", args)
if err != nil {
return nil
}
updateCmd, err := command.NewUpdateGoSumCommand("Update go.sum", args)
if err != nil {
return nil
}
return &source.Diagnostic{
URI: fh.URI(),
Range: rng,
@ -204,22 +208,8 @@ See https://github.com/golang/go/issues/39164 for more detail on this issue.`,
Source: source.ListError,
Message: `go.sum is out of sync with go.mod. Please update it or run "go mod tidy".`,
SuggestedFixes: []source.SuggestedFix{
{
Title: source.CommandTidy.Title,
Command: &protocol.Command{
Command: source.CommandTidy.ID(),
Title: source.CommandTidy.Title,
Arguments: args,
},
},
{
Title: source.CommandUpdateGoSum.Title,
Command: &protocol.Command{
Command: source.CommandUpdateGoSum.ID(),
Title: source.CommandUpdateGoSum.Title,
Arguments: args,
},
},
source.SuggestedFixFromCommand(tidyCmd),
source.SuggestedFixFromCommand(updateCmd),
},
}
}
@ -385,24 +375,22 @@ func unusedDiagnostic(m *protocol.ColumnMapper, req *modfile.Require, onlyDiagno
if err != nil {
return nil, err
}
args, err := source.MarshalArgs(m.URI, onlyDiagnostic, req.Mod.Path)
title := fmt.Sprintf("Remove dependency: %s", req.Mod.Path)
cmd, err := command.NewRemoveDependencyCommand(title, command.RemoveDependencyArgs{
URI: protocol.URIFromSpanURI(m.URI),
OnlyDiagnostic: onlyDiagnostic,
ModulePath: req.Mod.Path,
})
if err != nil {
return nil, err
}
return &source.Diagnostic{
URI: m.URI,
Range: rng,
Severity: protocol.SeverityWarning,
Source: source.ModTidyError,
Message: fmt.Sprintf("%s is not used in this module", req.Mod.Path),
SuggestedFixes: []source.SuggestedFix{{
Title: fmt.Sprintf("Remove dependency: %s", req.Mod.Path),
Command: &protocol.Command{
Title: source.CommandRemoveDependency.Title,
Command: source.CommandRemoveDependency.ID(),
Arguments: args,
},
}},
URI: m.URI,
Range: rng,
Severity: protocol.SeverityWarning,
Source: source.ModTidyError,
Message: fmt.Sprintf("%s is not used in this module", req.Mod.Path),
SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
}, nil
}
@ -459,24 +447,22 @@ func missingModuleDiagnostic(snapshot source.Snapshot, pm *source.ParsedModule,
return nil, err
}
}
args, err := source.MarshalArgs(pm.Mapper.URI, !req.Indirect, []string{req.Mod.Path + "@" + req.Mod.Version})
title := fmt.Sprintf("Add %s to your go.mod file", req.Mod.Path)
cmd, err := command.NewAddDependencyCommand(title, command.DependencyArgs{
URI: protocol.URIFromSpanURI(pm.Mapper.URI),
AddRequire: !req.Indirect,
GoCmdArgs: []string{req.Mod.Path + "@" + req.Mod.Version},
})
if err != nil {
return nil, err
}
return &source.Diagnostic{
URI: pm.Mapper.URI,
Range: rng,
Severity: protocol.SeverityError,
Source: source.ModTidyError,
Message: fmt.Sprintf("%s is not in your go.mod file", req.Mod.Path),
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,
},
}},
URI: pm.Mapper.URI,
Range: rng,
Severity: protocol.SeverityError,
Source: source.ModTidyError,
Message: fmt.Sprintf("%s is not in your go.mod file", req.Mod.Path),
SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
}, nil
}

View File

@ -9,6 +9,7 @@ import (
"flag"
"fmt"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/tool"
@ -82,7 +83,11 @@ func (c *generateWorkspaceMod) Run(ctx context.Context, args ...string) error {
return err
}
defer conn.terminate(ctx)
params := &protocol.ExecuteCommandParams{Command: source.CommandGenerateGoplsMod.ID()}
cmd, err := command.NewGenerateGoplsModCommand("", command.URIArg{})
if err != nil {
return err
}
params := &protocol.ExecuteCommandParams{Command: cmd.Command, Arguments: cmd.Arguments}
if _, err := conn.ExecuteCommand(ctx, params); err != nil {
return fmt.Errorf("executing server command: %v", err)
}

View File

@ -13,6 +13,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/imports"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/mod"
"golang.org/x/tools/internal/lsp/protocol"
@ -264,7 +265,7 @@ func analysisFixes(ctx context.Context, snapshot source.Snapshot, pkg source.Pac
}
// If the suggested fix for the diagnostic is expected to be separate,
// see if there are any supported commands available.
if analyzer.Command != nil {
if analyzer.Fix != "" {
action, err := diagnosticToCommandCodeAction(ctx, snapshot, srcErr, &diag, protocol.QuickFix)
if err != nil {
return nil, nil, err
@ -362,7 +363,7 @@ func convenienceFixes(ctx context.Context, snapshot source.Snapshot, pkg source.
if !a.IsEnabled(snapshot.View()) {
continue
}
if a.Command == nil {
if a.Fix == "" {
event.Error(ctx, "convenienceFixes", fmt.Errorf("no suggested fixes for convenience analyzer %s", a.Analyzer.Name))
continue
}
@ -399,10 +400,14 @@ func diagnosticToCommandCodeAction(ctx context.Context, snapshot source.Snapshot
if analyzer == nil {
return nil, fmt.Errorf("no convenience analyzer for source %s", sd.Source)
}
if analyzer.Command == nil {
return nil, fmt.Errorf("no command for convenience analyzer %s", analyzer.Analyzer.Name)
if analyzer.Fix == "" {
return nil, fmt.Errorf("no fix for convenience analyzer %s", analyzer.Analyzer.Name)
}
jsonArgs, err := source.MarshalArgs(sd.URI, sd.Range)
cmd, err := command.NewApplyFixCommand(sd.Message, command.ApplyFixArgs{
URI: protocol.URIFromSpanURI(sd.URI),
Range: sd.Range,
Fix: analyzer.Fix,
})
if err != nil {
return nil, err
}
@ -414,11 +419,7 @@ func diagnosticToCommandCodeAction(ctx context.Context, snapshot source.Snapshot
Title: sd.Message,
Kind: kind,
Diagnostics: diagnostics,
Command: &protocol.Command{
Command: analyzer.Command.ID(),
Title: sd.Message,
Arguments: jsonArgs,
},
Command: &cmd,
}, nil
}
@ -430,10 +431,6 @@ func extractionFixes(ctx context.Context, snapshot source.Snapshot, pkg source.P
if err != nil {
return nil, err
}
jsonArgs, err := source.MarshalArgs(uri, rng)
if err != nil {
return nil, err
}
_, pgf, err := source.GetParsedFile(ctx, snapshot, fh, source.NarrowestPackage)
if err != nil {
return nil, errors.Errorf("getting file for Identifier: %w", err)
@ -442,22 +439,36 @@ func extractionFixes(ctx context.Context, snapshot source.Snapshot, pkg source.P
if err != nil {
return nil, err
}
var commands []*source.Command
puri := protocol.URIFromSpanURI(uri)
var commands []protocol.Command
if _, ok, _ := source.CanExtractFunction(snapshot.FileSet(), srng, pgf.Src, pgf.File); ok {
commands = append(commands, source.CommandExtractFunction)
cmd, err := command.NewApplyFixCommand("Extract to function", command.ApplyFixArgs{
URI: puri,
Fix: source.ExtractFunction,
Range: rng,
})
if err != nil {
return nil, err
}
commands = append(commands, cmd)
}
if _, _, ok, _ := source.CanExtractVariable(srng, pgf.File); ok {
commands = append(commands, source.CommandExtractVariable)
cmd, err := command.NewApplyFixCommand("Extract variable", command.ApplyFixArgs{
URI: puri,
Fix: source.ExtractVariable,
Range: rng,
})
if err != nil {
return nil, err
}
commands = append(commands, cmd)
}
var actions []protocol.CodeAction
for _, command := range commands {
for _, cmd := range commands {
actions = append(actions, protocol.CodeAction{
Title: command.Title,
Kind: protocol.RefactorExtract,
Command: &protocol.Command{
Command: command.ID(),
Arguments: jsonArgs,
},
Title: cmd.Title,
Kind: protocol.RefactorExtract,
Command: &cmd,
})
}
return actions, nil
@ -574,17 +585,13 @@ func goTest(ctx context.Context, snapshot source.Snapshot, uri span.URI, rng pro
return nil, nil
}
jsonArgs, err := source.MarshalArgs(uri, tests, benchmarks)
cmd, err := command.NewTestCommand("Run tests and benchmarks", protocol.URIFromSpanURI(uri), tests, benchmarks)
if err != nil {
return nil, err
}
return []protocol.CodeAction{{
Title: source.CommandTest.Name,
Kind: protocol.GoTest,
Command: &protocol.Command{
Title: source.CommandTest.Title,
Command: source.CommandTest.ID(),
Arguments: jsonArgs,
},
Title: cmd.Title,
Kind: protocol.GoTest,
Command: &cmd,
}}, nil
}

View File

@ -10,6 +10,7 @@ import (
"sort"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/mod"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
@ -21,26 +22,26 @@ func (s *Server) codeLens(ctx context.Context, params *protocol.CodeLensParams)
if !ok {
return nil, err
}
var lensFuncs map[string]source.LensFunc
var lenses map[command.Command]source.LensFunc
switch fh.Kind() {
case source.Mod:
lensFuncs = mod.LensFuncs()
lenses = mod.LensFuncs()
case source.Go:
lensFuncs = source.LensFuncs()
lenses = source.LensFuncs()
default:
// Unsupported file kind for a code lens.
return nil, nil
}
var result []protocol.CodeLens
for lens, lf := range lensFuncs {
if !snapshot.View().Options().Codelenses[lens] {
for cmd, lf := range lenses {
if !snapshot.View().Options().Codelenses[string(cmd)] {
continue
}
added, err := lf(ctx, snapshot, fh)
// Code lens is called on every keystroke, so we should just operate in
// a best-effort mode, ignoring errors.
if err != nil {
event.Error(ctx, fmt.Sprintf("code lens %s failed", lens), err)
event.Error(ctx, fmt.Sprintf("code lens %s failed", cmd), err)
continue
}
result = append(result, added...)

View File

@ -18,6 +18,7 @@ import (
"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/command"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
@ -37,12 +38,12 @@ func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCom
return nil, fmt.Errorf("%s is not a supported command", params.Command)
}
cmd := &commandHandler{
handler := &commandHandler{
ctx: ctx,
s: s,
params: params,
}
return cmd.dispatch()
return command.Dispatch(params, handler)
}
type commandHandler struct {
@ -114,106 +115,12 @@ func (c *commandHandler) run(cfg commandConfig, run commandFunc) (err error) {
return runcmd()
}
func (c *commandHandler) dispatch() (interface{}, error) {
switch c.params.Command {
case source.CommandFillStruct.ID(), source.CommandUndeclaredName.ID(),
source.CommandExtractVariable.ID(), source.CommandExtractFunction.ID():
var uri protocol.DocumentURI
var rng protocol.Range
if err := source.UnmarshalArgs(c.params.Arguments, &uri, &rng); err != nil {
return nil, err
}
err := c.ApplyFix(uri, rng)
return nil, err
case source.CommandTest.ID():
var uri protocol.DocumentURI
var tests, benchmarks []string
if err := source.UnmarshalArgs(c.params.Arguments, &uri, &tests, &benchmarks); err != nil {
return nil, err
}
err := c.RunTests(uri, tests, benchmarks)
return nil, err
case source.CommandGenerate.ID():
var uri protocol.DocumentURI
var recursive bool
if err := source.UnmarshalArgs(c.params.Arguments, &uri, &recursive); err != nil {
return nil, err
}
err := c.Generate(uri, recursive)
return nil, err
case source.CommandRegenerateCgo.ID():
var uri protocol.DocumentURI
if err := source.UnmarshalArgs(c.params.Arguments, &uri); err != nil {
return nil, err
}
return nil, c.RegenerateCgo(uri)
case source.CommandTidy.ID():
var uri protocol.DocumentURI
if err := source.UnmarshalArgs(c.params.Arguments, &uri); err != nil {
return nil, err
}
return nil, c.Tidy(uri)
case source.CommandVendor.ID():
var uri protocol.DocumentURI
if err := source.UnmarshalArgs(c.params.Arguments, &uri); err != nil {
return nil, err
}
return nil, c.Vendor(uri)
case source.CommandUpdateGoSum.ID():
var uri protocol.DocumentURI
if err := source.UnmarshalArgs(c.params.Arguments, &uri); err != nil {
return nil, err
}
return nil, c.UpdateGoSum(uri)
case source.CommandCheckUpgrades.ID():
var uri protocol.DocumentURI
var modules []string
if err := source.UnmarshalArgs(c.params.Arguments, &uri, &modules); err != nil {
return nil, err
}
return nil, c.CheckUpgrades(uri, modules)
case source.CommandAddDependency.ID(), source.CommandUpgradeDependency.ID():
var uri protocol.DocumentURI
var goCmdArgs []string
var addRequire bool
if err := source.UnmarshalArgs(c.params.Arguments, &uri, &addRequire, &goCmdArgs); err != nil {
return nil, err
}
return nil, c.GoGetModule(uri, addRequire, goCmdArgs)
case source.CommandRemoveDependency.ID():
var uri protocol.DocumentURI
var modulePath string
var onlyDiagnostic bool
if err := source.UnmarshalArgs(c.params.Arguments, &uri, &onlyDiagnostic, &modulePath); err != nil {
return nil, err
}
return nil, c.RemoveDependency(modulePath, uri, onlyDiagnostic)
case source.CommandGoGetPackage.ID():
var uri protocol.DocumentURI
var pkg string
var addRequire bool
if err := source.UnmarshalArgs(c.params.Arguments, &uri, &addRequire, &pkg); err != nil {
return nil, err
}
return nil, c.GoGetPackage(uri, addRequire, pkg)
case source.CommandToggleDetails.ID():
var uri protocol.DocumentURI
if err := source.UnmarshalArgs(c.params.Arguments, &uri); err != nil {
return nil, err
}
return nil, c.GCDetails(uri)
case source.CommandGenerateGoplsMod.ID():
return nil, c.GenerateGoplsMod()
}
return nil, fmt.Errorf("unsupported command: %s", c.params.Command)
}
func (c *commandHandler) ApplyFix(uri protocol.DocumentURI, rng protocol.Range) error {
func (c *commandHandler) ApplyFix(args command.ApplyFixArgs) error {
return c.run(commandConfig{
// Note: no progress here. Applying fixes should be quick.
forURI: uri,
forURI: args.URI,
}, func(ctx context.Context, deps commandDeps) error {
edits, err := source.ApplyFix(ctx, c.params.Command, deps.snapshot, deps.fh, rng)
edits, err := source.ApplyFix(ctx, args.Fix, deps.snapshot, deps.fh, args.Range)
if err != nil {
return err
}
@ -232,24 +139,24 @@ func (c *commandHandler) ApplyFix(uri protocol.DocumentURI, rng protocol.Range)
})
}
func (c *commandHandler) RegenerateCgo(uri protocol.DocumentURI) error {
func (c *commandHandler) RegenerateCgo(args command.URIArg) error {
return c.run(commandConfig{
progress: source.CommandRegenerateCgo.Title,
progress: "Regenerating Cgo",
}, func(ctx context.Context, deps commandDeps) error {
mod := source.FileModification{
URI: uri.SpanURI(),
URI: args.URI.SpanURI(),
Action: source.InvalidateMetadata,
}
return c.s.didModifyFiles(c.ctx, []source.FileModification{mod}, FromRegenerateCgo)
})
}
func (c *commandHandler) CheckUpgrades(uri protocol.DocumentURI, modules []string) error {
func (c *commandHandler) CheckUpgrades(args command.CheckUpgradesArgs) error {
return c.run(commandConfig{
forURI: uri,
progress: source.CommandCheckUpgrades.Title,
forURI: args.URI,
progress: "Checking for upgrades",
}, func(ctx context.Context, deps commandDeps) error {
upgrades, err := c.s.getUpgrades(ctx, deps.snapshot, uri.SpanURI(), modules)
upgrades, err := c.s.getUpgrades(ctx, deps.snapshot, args.URI.SpanURI(), args.Modules)
if err != nil {
return err
}
@ -260,70 +167,78 @@ func (c *commandHandler) CheckUpgrades(uri protocol.DocumentURI, modules []strin
})
}
func (c *commandHandler) GoGetModule(uri protocol.DocumentURI, addRequire bool, goCmdArgs []string) error {
func (c *commandHandler) AddDependency(args command.DependencyArgs) error {
return c.GoGetModule(args)
}
func (c *commandHandler) UpgradeDependency(args command.DependencyArgs) error {
return c.GoGetModule(args)
}
func (c *commandHandler) GoGetModule(args command.DependencyArgs) error {
return c.run(commandConfig{
requireSave: true,
progress: "Running go get",
forURI: uri,
forURI: args.URI,
}, func(ctx context.Context, deps commandDeps) error {
return runGoGetModule(ctx, deps.snapshot, uri.SpanURI(), addRequire, goCmdArgs)
return runGoGetModule(ctx, deps.snapshot, args.URI.SpanURI(), args.AddRequire, args.GoCmdArgs)
})
}
// TODO(rFindley): UpdateGoSum, Tidy, and Vendor could probably all be one command.
func (c *commandHandler) UpdateGoSum(uri protocol.DocumentURI) error {
func (c *commandHandler) UpdateGoSum(args command.URIArg) error {
return c.run(commandConfig{
requireSave: true,
progress: source.CommandUpdateGoSum.Title,
forURI: uri,
progress: "Updating go.sum",
forURI: args.URI,
}, func(ctx context.Context, deps commandDeps) error {
return runSimpleGoCommand(ctx, deps.snapshot, source.UpdateUserModFile|source.AllowNetwork, uri.SpanURI(), "list", []string{"all"})
return runSimpleGoCommand(ctx, deps.snapshot, source.UpdateUserModFile|source.AllowNetwork, args.URI.SpanURI(), "list", []string{"all"})
})
}
func (c *commandHandler) Tidy(uri protocol.DocumentURI) error {
func (c *commandHandler) Tidy(args command.URIArg) error {
return c.run(commandConfig{
requireSave: true,
progress: source.CommandTidy.Title,
forURI: uri,
progress: "Running go mod tidy",
forURI: args.URI,
}, func(ctx context.Context, deps commandDeps) error {
return runSimpleGoCommand(ctx, deps.snapshot, source.UpdateUserModFile|source.AllowNetwork, uri.SpanURI(), "mod", []string{"tidy"})
return runSimpleGoCommand(ctx, deps.snapshot, source.UpdateUserModFile|source.AllowNetwork, args.URI.SpanURI(), "mod", []string{"tidy"})
})
}
func (c *commandHandler) Vendor(uri protocol.DocumentURI) error {
func (c *commandHandler) Vendor(args command.URIArg) error {
return c.run(commandConfig{
requireSave: true,
progress: source.CommandVendor.Title,
forURI: uri,
progress: "Running go mod vendor",
forURI: args.URI,
}, func(ctx context.Context, deps commandDeps) error {
return runSimpleGoCommand(ctx, deps.snapshot, source.UpdateUserModFile|source.AllowNetwork, uri.SpanURI(), "mod", []string{"vendor"})
return runSimpleGoCommand(ctx, deps.snapshot, source.UpdateUserModFile|source.AllowNetwork, args.URI.SpanURI(), "mod", []string{"vendor"})
})
}
func (c *commandHandler) RemoveDependency(modulePath string, uri protocol.DocumentURI, onlyDiagnostic bool) error {
func (c *commandHandler) RemoveDependency(args command.RemoveDependencyArgs) error {
return c.run(commandConfig{
requireSave: true,
progress: source.CommandRemoveDependency.Title,
forURI: uri,
progress: "Removing dependency",
forURI: args.URI,
}, func(ctx context.Context, deps commandDeps) error {
// If the module is tidied apart from the one unused diagnostic, we can
// run `go get module@none`, and then run `go mod tidy`. Otherwise, we
// must make textual edits.
// TODO(rstambler): In Go 1.17+, we will be able to use the go command
// without checking if the module is tidy.
if onlyDiagnostic {
if err := runGoGetModule(ctx, deps.snapshot, uri.SpanURI(), false, []string{modulePath + "@none"}); err != nil {
if args.OnlyDiagnostic {
if err := runGoGetModule(ctx, deps.snapshot, args.URI.SpanURI(), false, []string{args.ModulePath + "@none"}); err != nil {
return err
}
return runSimpleGoCommand(ctx, deps.snapshot, source.UpdateUserModFile|source.AllowNetwork, uri.SpanURI(), "mod", []string{"tidy"})
return runSimpleGoCommand(ctx, deps.snapshot, source.UpdateUserModFile|source.AllowNetwork, args.URI.SpanURI(), "mod", []string{"tidy"})
}
pm, err := deps.snapshot.ParseMod(ctx, deps.fh)
if err != nil {
return err
}
edits, err := dropDependency(deps.snapshot, pm, modulePath)
edits, err := dropDependency(deps.snapshot, pm, args.ModulePath)
if err != nil {
return err
}
@ -375,14 +290,22 @@ func dropDependency(snapshot source.Snapshot, pm *source.ParsedModule, modulePat
return source.ToProtocolEdits(pm.Mapper, diff)
}
func (c *commandHandler) RunTests(uri protocol.DocumentURI, tests, benchmarks []string) error {
func (c *commandHandler) Test(uri protocol.DocumentURI, tests, benchmarks []string) error {
return c.RunTests(command.RunTestsArgs{
URI: uri,
Tests: tests,
Benchmarks: benchmarks,
})
}
func (c *commandHandler) RunTests(args command.RunTestsArgs) error {
return c.run(commandConfig{
async: true,
progress: source.CommandTest.Title,
progress: "Running go test",
requireSave: true,
forURI: uri,
forURI: args.URI,
}, func(ctx context.Context, deps commandDeps) error {
if err := c.runTests(ctx, deps.snapshot, deps.work, uri, tests, benchmarks); err != nil {
if err := c.runTests(ctx, deps.snapshot, deps.work, args.URI, args.Tests, args.Benchmarks); err != nil {
if err := c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
Type: protocol.Error,
Message: fmt.Sprintf("Running tests failed: %v", err),
@ -472,22 +395,26 @@ func (c *commandHandler) runTests(ctx context.Context, snapshot source.Snapshot,
})
}
func (c *commandHandler) Generate(uri protocol.DocumentURI, recursive bool) error {
func (c *commandHandler) Generate(args command.GenerateArgs) error {
title := "Running go generate ."
if args.Recursive {
title = "Running go generate ./..."
}
return c.run(commandConfig{
requireSave: true,
progress: source.CommandGenerate.Title,
forURI: uri,
progress: title,
forURI: args.Dir,
}, func(ctx context.Context, deps commandDeps) error {
er := &eventWriter{ctx: ctx, operation: "generate"}
pattern := "."
if recursive {
if args.Recursive {
pattern = "./..."
}
inv := &gocommand.Invocation{
Verb: "generate",
Args: []string{"-x", pattern},
WorkingDir: uri.SpanURI().Filename(),
WorkingDir: args.Dir.SpanURI().Filename(),
}
stderr := io.MultiWriter(er, workDoneWriter{deps.work})
if err := deps.snapshot.RunGoCommandPiped(ctx, source.Normal, inv, er, stderr); err != nil {
@ -497,22 +424,22 @@ func (c *commandHandler) Generate(uri protocol.DocumentURI, recursive bool) erro
})
}
func (c *commandHandler) GoGetPackage(puri protocol.DocumentURI, addRequire bool, pkg string) error {
func (c *commandHandler) GoGetPackage(args command.GoGetPackageArgs) error {
return c.run(commandConfig{
forURI: puri,
progress: source.CommandGoGetPackage.Title,
forURI: args.URI,
progress: "Running go get",
}, func(ctx context.Context, deps commandDeps) error {
uri := puri.SpanURI()
uri := args.URI.SpanURI()
stdout, err := deps.snapshot.RunGoCommandDirect(ctx, source.WriteTemporaryModFile|source.AllowNetwork, &gocommand.Invocation{
Verb: "list",
Args: []string{"-f", "{{.Module.Path}}@{{.Module.Version}}", pkg},
Args: []string{"-f", "{{.Module.Path}}@{{.Module.Version}}", args.Pkg},
WorkingDir: filepath.Dir(uri.Filename()),
})
if err != nil {
return err
}
ver := strings.TrimSpace(stdout.String())
return runGoGetModule(ctx, deps.snapshot, uri, addRequire, []string{ver})
return runGoGetModule(ctx, deps.snapshot, uri, args.AddRequire, []string{ver})
})
}
@ -563,12 +490,16 @@ func (s *Server) getUpgrades(ctx context.Context, snapshot source.Snapshot, uri
}
func (c *commandHandler) GCDetails(uri protocol.DocumentURI) error {
return c.ToggleGCDetails(command.URIArg{URI: uri})
}
func (c *commandHandler) ToggleGCDetails(args command.URIArg) error {
return c.run(commandConfig{
requireSave: true,
progress: source.CommandToggleDetails.Title,
forURI: uri,
progress: "Toggling GC Details",
forURI: args.URI,
}, func(ctx context.Context, deps commandDeps) error {
pkgDir := span.URIFromPath(filepath.Dir(uri.SpanURI().Filename()))
pkgDir := span.URIFromPath(filepath.Dir(args.URI.SpanURI().Filename()))
c.s.gcOptimizationDetailsMu.Lock()
if _, ok := c.s.gcOptimizationDetails[pkgDir]; ok {
delete(c.s.gcOptimizationDetails, pkgDir)
@ -582,10 +513,11 @@ func (c *commandHandler) GCDetails(uri protocol.DocumentURI) error {
})
}
func (c *commandHandler) GenerateGoplsMod() error {
func (c *commandHandler) GenerateGoplsMod(args command.URIArg) error {
// TODO: go back to using URI
return c.run(commandConfig{
requireSave: true,
progress: source.CommandGenerateGoplsMod.Title,
progress: "Generating gopls.mod",
}, func(ctx context.Context, deps commandDeps) error {
views := c.s.session.Views()
if len(views) != 1 {

View File

@ -16,6 +16,44 @@ import (
"golang.org/x/tools/internal/lsp/protocol"
)
const (
AddDependency Command = "add_dependency"
ApplyFix Command = "apply_fix"
CheckUpgrades Command = "check_upgrades"
GCDetails Command = "gc_details"
Generate Command = "generate"
GenerateGoplsMod Command = "generate_gopls_mod"
GoGetPackage Command = "go_get_package"
RegenerateCgo Command = "regenerate_cgo"
RemoveDependency Command = "remove_dependency"
RunTests Command = "run_tests"
Test Command = "test"
Tidy Command = "tidy"
ToggleGCDetails Command = "toggle_gc_details"
UpdateGoSum Command = "update_go_sum"
UpgradeDependency Command = "upgrade_dependency"
Vendor Command = "vendor"
)
var Commands = []Command{
AddDependency,
ApplyFix,
CheckUpgrades,
GCDetails,
Generate,
GenerateGoplsMod,
GoGetPackage,
RegenerateCgo,
RemoveDependency,
RunTests,
Test,
Tidy,
ToggleGCDetails,
UpdateGoSum,
UpgradeDependency,
Vendor,
}
func Dispatch(params *protocol.ExecuteCommandParams, s Interface) (interface{}, error) {
switch params.Command {
case "gopls.add_dependency":
@ -25,6 +63,13 @@ func Dispatch(params *protocol.ExecuteCommandParams, s Interface) (interface{},
}
err := s.AddDependency(a0)
return nil, err
case "gopls.apply_fix":
var a0 ApplyFixArgs
if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
return nil, err
}
err := s.ApplyFix(a0)
return nil, err
case "gopls.check_upgrades":
var a0 CheckUpgradesArgs
if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
@ -33,7 +78,7 @@ func Dispatch(params *protocol.ExecuteCommandParams, s Interface) (interface{},
err := s.CheckUpgrades(a0)
return nil, err
case "gopls.gc_details":
var a0 URIArg
var a0 protocol.DocumentURI
if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
return nil, err
}
@ -97,6 +142,13 @@ func Dispatch(params *protocol.ExecuteCommandParams, s Interface) (interface{},
}
err := s.Tidy(a0)
return nil, err
case "gopls.toggle_gc_details":
var a0 URIArg
if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
return nil, err
}
err := s.ToggleGCDetails(a0)
return nil, err
case "gopls.update_go_sum":
var a0 URIArg
if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
@ -134,6 +186,18 @@ func NewAddDependencyCommand(title string, a0 DependencyArgs) (protocol.Command,
}, nil
}
func NewApplyFixCommand(title string, a0 ApplyFixArgs) (protocol.Command, error) {
args, err := MarshalArgs(a0)
if err != nil {
return protocol.Command{}, err
}
return protocol.Command{
Title: title,
Command: "gopls.apply_fix",
Arguments: args,
}, nil
}
func NewCheckUpgradesCommand(title string, a0 CheckUpgradesArgs) (protocol.Command, error) {
args, err := MarshalArgs(a0)
if err != nil {
@ -146,7 +210,7 @@ func NewCheckUpgradesCommand(title string, a0 CheckUpgradesArgs) (protocol.Comma
}, nil
}
func NewGCDetailsCommand(title string, a0 URIArg) (protocol.Command, error) {
func NewGCDetailsCommand(title string, a0 protocol.DocumentURI) (protocol.Command, error) {
args, err := MarshalArgs(a0)
if err != nil {
return protocol.Command{}, err
@ -254,6 +318,18 @@ func NewTidyCommand(title string, a0 URIArg) (protocol.Command, error) {
}, nil
}
func NewToggleGCDetailsCommand(title string, a0 URIArg) (protocol.Command, error) {
args, err := MarshalArgs(a0)
if err != nil {
return protocol.Command{}, err
}
return protocol.Command{
Title: title,
Command: "gopls.toggle_gc_details",
Arguments: args,
}, nil
}
func NewUpdateGoSumCommand(title string, a0 URIArg) (protocol.Command, error) {
args, err := MarshalArgs(a0)
if err != nil {

View File

@ -31,6 +31,10 @@ type Command struct {
Result types.Type
}
func (c *Command) ID() string {
return command.ID(c.Name)
}
type Field struct {
Name string
Doc string
@ -44,14 +48,18 @@ type Field struct {
func Load() (*packages.Package, []*Command, error) {
pkgs, err := packages.Load(
&packages.Config{
Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedDeps,
Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedImports | packages.NeedDeps,
BuildFlags: []string{"-tags=generate"},
},
"golang.org/x/tools/internal/lsp/command",
)
if err != nil {
return nil, nil, err
return nil, nil, fmt.Errorf("packages.Load: %v", err)
}
pkg := pkgs[0]
if len(pkg.Errors) > 0 {
return pkg, nil, pkg.Errors[0]
}
// For a bit of type safety, use reflection to get the interface name within
// the package scope.
@ -138,7 +146,10 @@ func (l *fieldLoader) loadField(pkg *packages.Package, node *ast.Field, obj *typ
obj2 := s.Field(i)
pkg2 := pkg
if obj2.Pkg() != pkg2.Types {
pkg2 = pkg.Imports[obj2.Pkg().Path()]
pkg2, ok = pkg.Imports[obj2.Pkg().Path()]
if !ok {
return nil, fmt.Errorf("missing import for %q: %q", pkg.ID, obj2.Pkg().Path())
}
}
node2, err := findField(pkg2, obj2.Pos())
if err != nil {
@ -160,7 +171,7 @@ func (l *fieldLoader) loadField(pkg *packages.Package, node *ast.Field, obj *typ
//
// The doc comment should be of the form: "MethodName: Title\nDocumentation"
func splitDoc(text string) (title, doc string) {
docParts := strings.SplitN(doc, "\n", 2)
docParts := strings.SplitN(text, "\n", 2)
titleParts := strings.SplitN(docParts[0], ":", 2)
if len(titleParts) > 1 {
title = strings.TrimSpace(titleParts[1])
@ -177,7 +188,7 @@ func lspName(methodName string) string {
for i := range words {
words[i] = strings.ToLower(words[i])
}
return "gopls." + strings.Join(words, "_")
return strings.Join(words, "_")
}
// splitCamel splits s into words, according to camel-case word boundaries.

View File

@ -34,10 +34,22 @@ import (
{{end}}
)
const (
{{- range .Commands}}
{{.MethodName}} Command = "{{.Name}}"
{{- end}}
)
var Commands = []Command {
{{- range .Commands}}
{{.MethodName}},
{{- end}}
}
func Dispatch(params *protocol.ExecuteCommandParams, s Interface) (interface{}, error) {
switch params.Command {
{{- range .Commands}}
case "{{.Name}}":
case "{{.ID}}":
{{- if .Args -}}
{{- range $i, $v := .Args}}
var a{{$i}} {{typeString $v.Type}}
@ -61,7 +73,7 @@ func New{{.MethodName}}Command(title string, {{range $i, $v := .Args}}{{if $i}},
}
return protocol.Command{
Title: title,
Command: "{{.Name}}",
Command: "{{.ID}}",
Arguments: args,
}, nil
}
@ -76,7 +88,7 @@ type data struct {
func Generate() ([]byte, error) {
pkg, cmds, err := commandmeta.Load()
if err != nil {
return nil, err
return nil, fmt.Errorf("loading command data: %v", err)
}
qf := func(p *types.Package) string {
if p == pkg.Types {

View File

@ -28,11 +28,17 @@ import "golang.org/x/tools/internal/lsp/protocol"
// is considered the command 'Title'.
// TODO(rFindley): reconsider this -- Title may be unnecessary.
type Interface interface {
// ApplyFix: Apply a fix
//
// Applies a fix to a region of source code.
ApplyFix(ApplyFixArgs) error
// Test: Run test(s) (legacy)
//
// Deprecated: use gopls.run_tests.
// Runs `go test` for a specific set of test or benchmark functions.
Test(protocol.DocumentURI, []string, []string) error
// TODO: deprecate Test in favor of RunTests below.
// Test: Run test(s)
//
// Runs `go test` for a specific set of test or benchmark functions.
@ -91,7 +97,14 @@ type Interface interface {
// GCDetails: Toggle gc_details
//
// Toggle the calculation of gc annotations.
GCDetails(URIArg) error
GCDetails(protocol.DocumentURI) error
// TODO: deprecate GCDetails in favor of ToggleGCDetails below.
// ToggleGCDetails: Toggle gc_details
//
// Toggle the calculation of gc annotations.
ToggleGCDetails(URIArg) error
// GenerateGoplsMod: Generate gopls.mod
//
@ -111,9 +124,8 @@ type RunTestsArgs struct {
}
type GenerateArgs struct {
// URI is any file within the directory to generate. Usually this is the file
// containing the '//go:generate' directive.
URI protocol.DocumentURI
// Dir is the directory to generate.
Dir protocol.DocumentURI
// Recursive controls whether to generate recursively (go generate ./...)
Recursive bool
@ -121,6 +133,12 @@ type GenerateArgs struct {
// TODO(rFindley): document the rest of these once the docgen is fleshed out.
type ApplyFixArgs struct {
Fix string
URI protocol.DocumentURI
Range protocol.Range
}
type URIArg struct {
URI protocol.DocumentURI
}
@ -143,6 +161,7 @@ type RemoveDependencyArgs struct {
}
type GoGetPackageArgs struct {
URI protocol.DocumentURI
Pkg string
URI protocol.DocumentURI
Pkg string
AddRequire bool
}

View File

@ -9,6 +9,17 @@ import (
"fmt"
)
// ID returns the command name for use in the LSP.
func ID(name string) string {
return "gopls." + name
}
type Command string
func (c Command) ID() string {
return ID(string(c))
}
// MarshalArgs encodes the given arguments to json.RawMessages. This function
// is used to construct arguments to a protocol.Command.
//

View File

@ -15,8 +15,8 @@ import (
"sync"
"golang.org/x/tools/internal/jsonrpc2"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
errors "golang.org/x/xerrors"
)
@ -892,18 +892,22 @@ func (e *Editor) checkBufferPosition(path string, pos Pos) error {
// path. It does not report any resulting file changes as a watched file
// change, so must be followed by a call to Workdir.CheckForFileChanges once
// the generate command has completed.
// TODO(rFindley): this shouldn't be necessary anymore. Delete it.
func (e *Editor) RunGenerate(ctx context.Context, dir string) error {
if e.Server == nil {
return nil
}
absDir := e.sandbox.Workdir.AbsPath(dir)
jsonArgs, err := source.MarshalArgs(span.URIFromPath(absDir), false)
cmd, err := command.NewGenerateCommand("", command.GenerateArgs{
Dir: protocol.URIFromSpanURI(span.URIFromPath(absDir)),
Recursive: false,
})
if err != nil {
return err
}
params := &protocol.ExecuteCommandParams{
Command: source.CommandGenerate.ID(),
Arguments: jsonArgs,
Command: cmd.Command,
Arguments: cmd.Arguments,
}
if _, err := e.ExecuteCommand(ctx, params); err != nil {
return fmt.Errorf("running generate: %v", err)

View File

@ -40,6 +40,7 @@ type runner struct {
diagnostics map[span.URI][]*source.Diagnostic
ctx context.Context
normalizers []tests.Normalizer
editRecv chan map[span.URI]string
}
func testLSP(t *testing.T, datum *tests.Data) {
@ -85,17 +86,19 @@ func testLSP(t *testing.T, datum *tests.Data) {
t.Fatal(err)
}
r := &runner{
server: NewServer(session, testClient{}),
data: datum,
ctx: ctx,
normalizers: tests.CollectNormalizers(datum.Exported),
editRecv: make(chan map[span.URI]string, 1),
}
r.server = NewServer(session, testClient{runner: r})
tests.Run(t, r, datum)
}
// testClient stubs any client functions that may be called by LSP functions.
type testClient struct {
protocol.Client
runner *runner
}
// Trivially implement PublishDiagnostics so that we can call
@ -104,6 +107,15 @@ func (c testClient) PublishDiagnostics(context.Context, *protocol.PublishDiagnos
return nil
}
func (c testClient) ApplyEdit(ctx context.Context, params *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResponse, error) {
res, err := applyTextDocumentEdits(c.runner, params.Edit.DocumentChanges)
if err != nil {
return nil, err
}
c.runner.editRecv <- res
return &protocol.ApplyWorkspaceEditResponse{Applied: true}, nil
}
func (r *runner) CallHierarchy(t *testing.T, spn span.Span, expectedCalls *tests.CallHierarchyResult) {
mapper, err := r.data.Mapper(spn.URI())
if err != nil {
@ -468,13 +480,6 @@ func (r *runner) SuggestedFix(t *testing.T, spn span.Span, actionKinds []string,
t.Fatal(err)
}
snapshot, release := view.Snapshot(r.ctx)
defer release()
fh, err := snapshot.GetVersionedFile(r.ctx, uri)
if err != nil {
t.Fatal(err)
}
m, err := r.data.Mapper(uri)
if err != nil {
t.Fatal(err)
@ -533,14 +538,14 @@ func (r *runner) SuggestedFix(t *testing.T, spn span.Span, actionKinds []string,
}
var res map[span.URI]string
if cmd := action.Command; cmd != nil {
edits, err := source.ApplyFix(r.ctx, cmd.Command, snapshot, fh, rng)
_, err := r.server.ExecuteCommand(r.ctx, &protocol.ExecuteCommandParams{
Command: action.Command.Command,
Arguments: action.Command.Arguments,
})
if err != nil {
t.Fatalf("error converting command %q to edits: %v", action.Command.Command, err)
}
res, err = applyTextDocumentEdits(r, edits)
if err != nil {
t.Fatal(err)
}
res = <-r.editRecv
} else {
res, err = applyTextDocumentEdits(r, action.Edit.DocumentChanges)
if err != nil {
@ -559,18 +564,6 @@ func (r *runner) SuggestedFix(t *testing.T, spn span.Span, actionKinds []string,
func (r *runner) FunctionExtraction(t *testing.T, start span.Span, end span.Span) {
uri := start.URI()
view, err := r.server.session.ViewOf(uri)
if err != nil {
t.Fatal(err)
}
snapshot, release := view.Snapshot(r.ctx)
defer release()
fh, err := snapshot.GetVersionedFile(r.ctx, uri)
if err != nil {
t.Fatal(err)
}
m, err := r.data.Mapper(uri)
if err != nil {
t.Fatal(err)
@ -597,14 +590,14 @@ func (r *runner) FunctionExtraction(t *testing.T, start span.Span, end span.Span
if len(actions) == 0 || len(actions) > 1 {
t.Fatalf("unexpected number of code actions, want 1, got %v", len(actions))
}
edits, err := source.ApplyFix(r.ctx, actions[0].Command.Command, snapshot, fh, rng)
if err != nil {
t.Fatal(err)
}
res, err := applyTextDocumentEdits(r, edits)
_, err = r.server.ExecuteCommand(r.ctx, &protocol.ExecuteCommandParams{
Command: actions[0].Command.Command,
Arguments: actions[0].Command.Arguments,
})
if err != nil {
t.Fatal(err)
}
res := <-r.editRecv
for u, got := range res {
want := string(r.data.Golden("functionextraction_"+tests.SpanName(spn), u.Filename(), func() ([]byte, error) {
return []byte(got), nil

View File

@ -11,17 +11,18 @@ import (
"path/filepath"
"golang.org/x/mod/modfile"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
)
// LensFuncs returns the supported lensFuncs for go.mod files.
func LensFuncs() map[string]source.LensFunc {
return map[string]source.LensFunc{
source.CommandUpgradeDependency.Name: upgradeLenses,
source.CommandTidy.Name: tidyLens,
source.CommandVendor.Name: vendorLens,
func LensFuncs() map[command.Command]source.LensFunc {
return map[command.Command]source.LensFunc{
command.UpgradeDependency: upgradeLenses,
command.Tidy: tidyLens,
command.Vendor: vendorLens,
}
}
@ -34,19 +35,31 @@ func upgradeLenses(ctx context.Context, snapshot source.Snapshot, fh source.File
// Nothing to upgrade.
return nil, nil
}
upgradeTransitiveArgs, err := source.MarshalArgs(fh.URI(), false, []string{"-u", "all"})
if err != nil {
return nil, err
}
var requires []string
for _, req := range pm.File.Require {
requires = append(requires, req.Mod.Path)
}
checkUpgradeArgs, err := source.MarshalArgs(fh.URI(), requires)
uri := protocol.URIFromSpanURI(fh.URI())
checkUpgrade, err := command.NewCheckUpgradesCommand("Check for upgrades", command.CheckUpgradesArgs{
URI: uri,
Modules: requires,
})
if err != nil {
return nil, err
}
upgradeDirectArgs, err := source.MarshalArgs(fh.URI(), false, requires)
upgradeTransitive, err := command.NewUpgradeDependencyCommand("Upgrade transitive dependencies", command.DependencyArgs{
URI: uri,
AddRequire: false,
GoCmdArgs: []string{"-u", "all"},
})
if err != nil {
return nil, err
}
upgradeDirect, err := command.NewUpgradeDependencyCommand("Upgrade direct dependencies", command.DependencyArgs{
URI: uri,
AddRequire: false,
GoCmdArgs: requires,
})
if err != nil {
return nil, err
}
@ -57,30 +70,9 @@ func upgradeLenses(ctx context.Context, snapshot source.Snapshot, fh source.File
}
return []protocol.CodeLens{
{
Range: rng,
Command: protocol.Command{
Title: "Check for upgrades",
Command: source.CommandCheckUpgrades.ID(),
Arguments: checkUpgradeArgs,
},
},
{
Range: rng,
Command: protocol.Command{
Title: "Upgrade transitive dependencies",
Command: source.CommandUpgradeDependency.ID(),
Arguments: upgradeTransitiveArgs,
},
},
{
Range: rng,
Command: protocol.Command{
Title: "Upgrade direct dependencies",
Command: source.CommandUpgradeDependency.ID(),
Arguments: upgradeDirectArgs,
},
},
{Range: rng, Command: checkUpgrade},
{Range: rng, Command: upgradeTransitive},
{Range: rng, Command: upgradeDirect},
}, nil
}
@ -93,7 +85,8 @@ func tidyLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandl
// Nothing to vendor.
return nil, nil
}
goModArgs, err := source.MarshalArgs(fh.URI())
uri := protocol.URIFromSpanURI(fh.URI())
cmd, err := command.NewTidyCommand("Run go mod tidy", command.URIArg{URI: uri})
if err != nil {
return nil, err
}
@ -102,12 +95,8 @@ func tidyLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandl
return nil, err
}
return []protocol.CodeLens{{
Range: rng,
Command: protocol.Command{
Title: source.CommandTidy.Title,
Command: source.CommandTidy.ID(),
Arguments: goModArgs,
},
Range: rng,
Command: cmd,
}}, nil
}
@ -120,25 +109,19 @@ func vendorLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHan
if err != nil {
return nil, err
}
goModArgs, err := source.MarshalArgs(fh.URI())
title := "Create vendor directory"
uri := protocol.URIFromSpanURI(fh.URI())
cmd, err := command.NewVendorCommand(title, command.URIArg{URI: uri})
if err != nil {
return nil, err
}
// Change the message depending on whether or not the module already has a
// vendor directory.
title := "Create vendor directory"
vendorDir := filepath.Join(filepath.Dir(fh.URI().Filename()), "vendor")
if info, _ := os.Stat(vendorDir); info != nil && info.IsDir() {
title = "Sync vendor directory"
}
return []protocol.CodeLens{{
Range: rng,
Command: protocol.Command{
Title: title,
Command: source.CommandVendor.ID(),
Arguments: goModArgs,
},
}}, nil
return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil
}
func moduleStmtRange(fh source.FileHandle, pm *source.ParsedModule) (protocol.Range, error) {

View File

@ -11,6 +11,7 @@ import (
"fmt"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
@ -65,24 +66,22 @@ func DiagnosticsForMod(ctx context.Context, snapshot source.Snapshot, fh source.
return nil, err
}
// Upgrade to the exact version we offer the user, not the most recent.
args, err := source.MarshalArgs(fh.URI(), false, []string{req.Mod.Path + "@" + ver})
title := fmt.Sprintf("Upgrade to %v", ver)
cmd, err := command.NewUpgradeDependencyCommand(title, command.DependencyArgs{
URI: protocol.URIFromSpanURI(fh.URI()),
AddRequire: false,
GoCmdArgs: []string{req.Mod.Path + "@" + ver},
})
if err != nil {
return nil, err
}
diagnostics = append(diagnostics, &source.Diagnostic{
URI: fh.URI(),
Range: rng,
Severity: protocol.SeverityInformation,
Source: source.UpgradeNotification,
Message: fmt.Sprintf("%v can be upgraded", req.Mod.Path),
SuggestedFixes: []source.SuggestedFix{{
Title: fmt.Sprintf("Upgrade to %v", ver),
Command: &protocol.Command{
Title: fmt.Sprintf("Upgrade to %v", ver),
Command: source.CommandUpgradeDependency.ID(),
Arguments: args,
},
}},
URI: fh.URI(),
Range: rng,
Severity: protocol.SeverityInformation,
Source: source.UpgradeNotification,
Message: fmt.Sprintf("%v can be upgraded", req.Mod.Path),
SuggestedFixes: []source.SuggestedFix{source.SuggestedFixFromCommand(cmd)},
})
}

View File

@ -577,37 +577,37 @@ var GeneratedAPIJSON = &APIJSON{
Keys: []EnumKey{
{
Name: "\"gc_details\"",
Doc: "gc_details controls calculation of gc annotations.\n",
Doc: "Toggle the calculation of gc annotations.",
Default: "false",
},
{
Name: "\"generate\"",
Doc: "generate runs `go generate` for a given directory.\n",
Doc: "Runs `go generate` for a given directory.",
Default: "true",
},
{
Name: "\"regenerate_cgo\"",
Doc: "regenerate_cgo regenerates cgo definitions.\n",
Doc: "Regenerates cgo definitions.",
Default: "true",
},
{
Name: "\"test\"",
Doc: "test runs `go test` for a specific test function.\n",
Doc: "Runs `go test` for a specific set of test or benchmark functions.",
Default: "false",
},
{
Name: "\"tidy\"",
Doc: "tidy runs `go mod tidy` for a module.\n",
Doc: "Runs `go mod tidy` for a module.",
Default: "true",
},
{
Name: "\"upgrade_dependency\"",
Doc: "upgrade_dependency upgrades a dependency.\n",
Doc: "Upgrades a dependency in the go.mod file for a module.",
Default: "true",
},
{
Name: "\"vendor\"",
Doc: "vendor runs `go mod vendor` for a module.\n",
Doc: "Runs `go mod vendor` for a module.",
Default: "true",
},
},
@ -675,124 +675,119 @@ var GeneratedAPIJSON = &APIJSON{
{
Command: "gopls.add_dependency",
Title: "Add dependency",
Doc: "add_dependency adds a dependency.\n",
Doc: "Adds a dependency to the go.mod file for a module.",
},
{
Command: "gopls.apply_fix",
Title: "Apply a fix",
Doc: "Applies a fix to a region of source code.",
},
{
Command: "gopls.check_upgrades",
Title: "Check for upgrades",
Doc: "check_upgrades checks for module upgrades.\n",
},
{
Command: "gopls.extract_function",
Title: "Extract to function",
Doc: "extract_function extracts statements to a function.\n",
},
{
Command: "gopls.extract_variable",
Title: "Extract to variable",
Doc: "extract_variable extracts an expression to a variable.\n",
},
{
Command: "gopls.fill_struct",
Title: "Fill struct",
Doc: "fill_struct is a gopls command to fill a struct with default\nvalues.\n",
Doc: "Checks for module upgrades.",
},
{
Command: "gopls.gc_details",
Title: "Toggle gc_details",
Doc: "gc_details controls calculation of gc annotations.\n",
Doc: "Toggle the calculation of gc annotations.",
},
{
Command: "gopls.generate",
Title: "Run go generate",
Doc: "generate runs `go generate` for a given directory.\n",
Doc: "Runs `go generate` for a given directory.",
},
{
Command: "gopls.generate_gopls_mod",
Title: "Generate gopls.mod",
Doc: "generate_gopls_mod (re)generates the gopls.mod file.\n",
Doc: "(Re)generate the gopls.mod file for a workspace.",
},
{
Command: "gopls.go_get_package",
Title: "go get package",
Doc: "go_get_package runs `go get` to fetch a package.\n",
Doc: "Runs `go get` to fetch a package.",
},
{
Command: "gopls.regenerate_cgo",
Title: "Regenerate cgo",
Doc: "regenerate_cgo regenerates cgo definitions.\n",
Doc: "Regenerates cgo definitions.",
},
{
Command: "gopls.remove_dependency",
Title: "Remove dependency",
Doc: "remove_dependency removes a dependency.\n",
Doc: "Removes a dependency from the go.mod file of a module.",
},
{
Command: "gopls.run_tests",
Title: "Run test(s)",
Doc: "Runs `go test` for a specific set of test or benchmark functions.",
},
{
Command: "gopls.test",
Title: "Run test(s)",
Doc: "test runs `go test` for a specific test function.\n",
Title: "Run test(s) (legacy)",
Doc: "Runs `go test` for a specific set of test or benchmark functions.",
},
{
Command: "gopls.tidy",
Title: "Run go mod tidy",
Doc: "tidy runs `go mod tidy` for a module.\n",
Doc: "Runs `go mod tidy` for a module.",
},
{
Command: "gopls.undeclared_name",
Title: "Undeclared name",
Doc: "undeclared_name adds a variable declaration for an undeclared\nname.\n",
Command: "gopls.toggle_gc_details",
Title: "Toggle gc_details",
Doc: "Toggle the calculation of gc annotations.",
},
{
Command: "gopls.update_go_sum",
Title: "Update go.sum",
Doc: "update_go_sum updates the go.sum file for a module.\n",
Doc: "Updates the go.sum file for a module.",
},
{
Command: "gopls.upgrade_dependency",
Title: "Upgrade dependency",
Doc: "upgrade_dependency upgrades a dependency.\n",
Doc: "Upgrades a dependency in the go.mod file for a module.",
},
{
Command: "gopls.vendor",
Title: "Run go mod vendor",
Doc: "vendor runs `go mod vendor` for a module.\n",
Doc: "Runs `go mod vendor` for a module.",
},
},
Lenses: []*LensJSON{
{
Lens: "gc_details",
Title: "Toggle gc_details",
Doc: "gc_details controls calculation of gc annotations.\n",
Doc: "Toggle the calculation of gc annotations.",
},
{
Lens: "generate",
Title: "Run go generate",
Doc: "generate runs `go generate` for a given directory.\n",
Doc: "Runs `go generate` for a given directory.",
},
{
Lens: "regenerate_cgo",
Title: "Regenerate cgo",
Doc: "regenerate_cgo regenerates cgo definitions.\n",
Doc: "Regenerates cgo definitions.",
},
{
Lens: "test",
Title: "Run test(s)",
Doc: "test runs `go test` for a specific test function.\n",
Title: "Run test(s) (legacy)",
Doc: "Runs `go test` for a specific set of test or benchmark functions.",
},
{
Lens: "tidy",
Title: "Run go mod tidy",
Doc: "tidy runs `go mod tidy` for a module.\n",
Doc: "Runs `go mod tidy` for a module.",
},
{
Lens: "upgrade_dependency",
Title: "Upgrade dependency",
Doc: "upgrade_dependency upgrades a dependency.\n",
Doc: "Upgrades a dependency in the go.mod file for a module.",
},
{
Lens: "vendor",
Title: "Run go mod vendor",
Doc: "vendor runs `go mod vendor` for a module.\n",
Doc: "Runs `go mod vendor` for a module.",
},
},
Analyzers: []*AnalyzerJSON{

View File

@ -13,6 +13,7 @@ import (
"regexp"
"strings"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/span"
)
@ -20,12 +21,12 @@ import (
type LensFunc func(context.Context, Snapshot, FileHandle) ([]protocol.CodeLens, error)
// LensFuncs returns the supported lensFuncs for Go files.
func LensFuncs() map[string]LensFunc {
return map[string]LensFunc{
CommandGenerate.Name: goGenerateCodeLens,
CommandTest.Name: runTestCodeLens,
CommandRegenerateCgo.Name: regenerateCgoLens,
CommandToggleDetails.Name: toggleDetailsCodeLens,
func LensFuncs() map[command.Command]LensFunc {
return map[command.Command]LensFunc{
command.Generate: goGenerateCodeLens,
command.Test: runTestCodeLens,
command.RegenerateCgo: regenerateCgoLens,
command.GCDetails: toggleDetailsCodeLens,
}
}
@ -41,34 +42,23 @@ func runTestCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]p
if err != nil {
return nil, err
}
puri := protocol.URIFromSpanURI(fh.URI())
for _, fn := range fns.Tests {
jsonArgs, err := MarshalArgs(fh.URI(), []string{fn.Name}, nil)
cmd, err := command.NewTestCommand("run test", puri, []string{fn.Name}, nil)
if err != nil {
return nil, err
}
codeLens = append(codeLens, protocol.CodeLens{
Range: protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start},
Command: protocol.Command{
Title: "run test",
Command: CommandTest.ID(),
Arguments: jsonArgs,
},
})
rng := protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start}
codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
}
for _, fn := range fns.Benchmarks {
jsonArgs, err := MarshalArgs(fh.URI(), nil, []string{fn.Name})
cmd, err := command.NewTestCommand("run benchmark", puri, nil, []string{fn.Name})
if err != nil {
return nil, err
}
codeLens = append(codeLens, protocol.CodeLens{
Range: protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start},
Command: protocol.Command{
Title: "run benchmark",
Command: CommandTest.ID(),
Arguments: jsonArgs,
},
})
rng := protocol.Range{Start: fn.Rng.Start, End: fn.Rng.Start}
codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
}
if len(fns.Benchmarks) > 0 {
@ -81,18 +71,15 @@ func runTestCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]p
if err != nil {
return nil, err
}
args, err := MarshalArgs(fh.URI(), []string{}, fns.Benchmarks)
var benches []string
for _, fn := range fns.Benchmarks {
benches = append(benches, fn.Name)
}
cmd, err := command.NewTestCommand("run file benchmarks", puri, nil, benches)
if err != nil {
return nil, err
}
codeLens = append(codeLens, protocol.CodeLens{
Range: rng,
Command: protocol.Command{
Title: "run file benchmarks",
Command: CommandTest.ID(),
Arguments: args,
},
})
codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
}
return codeLens, nil
}
@ -194,32 +181,18 @@ func goGenerateCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) (
if err != nil {
return nil, err
}
dir := span.URIFromPath(filepath.Dir(fh.URI().Filename()))
nonRecursiveArgs, err := MarshalArgs(dir, false)
dir := protocol.URIFromSpanURI(span.URIFromPath(filepath.Dir(fh.URI().Filename())))
nonRecursiveCmd, err := command.NewGenerateCommand("run go generate", command.GenerateArgs{Dir: dir, Recursive: false})
if err != nil {
return nil, err
}
recursiveArgs, err := MarshalArgs(dir, true)
recursiveCmd, err := command.NewGenerateCommand("run go generate ./...", command.GenerateArgs{Dir: dir, Recursive: true})
if err != nil {
return nil, err
}
return []protocol.CodeLens{
{
Range: rng,
Command: protocol.Command{
Title: "run go generate",
Command: CommandGenerate.ID(),
Arguments: nonRecursiveArgs,
},
},
{
Range: rng,
Command: protocol.Command{
Title: "run go generate ./...",
Command: CommandGenerate.ID(),
Arguments: recursiveArgs,
},
},
{Range: rng, Command: recursiveCmd},
{Range: rng, Command: nonRecursiveCmd},
}, nil
}
@ -245,20 +218,12 @@ func regenerateCgoLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([
if err != nil {
return nil, err
}
jsonArgs, err := MarshalArgs(fh.URI())
puri := protocol.URIFromSpanURI(fh.URI())
cmd, err := command.NewRegenerateCgoCommand("regenerate cgo definitions", command.URIArg{URI: puri})
if err != nil {
return nil, err
}
return []protocol.CodeLens{
{
Range: rng,
Command: protocol.Command{
Title: "regenerate cgo definitions",
Command: CommandRegenerateCgo.ID(),
Arguments: jsonArgs,
},
},
}, nil
return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil
}
func toggleDetailsCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle) ([]protocol.CodeLens, error) {
@ -270,16 +235,10 @@ func toggleDetailsCodeLens(ctx context.Context, snapshot Snapshot, fh FileHandle
if err != nil {
return nil, err
}
jsonArgs, err := MarshalArgs(fh.URI())
puri := protocol.URIFromSpanURI(fh.URI())
cmd, err := command.NewGCDetailsCommand("Toggle gc annotation details", puri)
if err != nil {
return nil, err
}
return []protocol.CodeLens{{
Range: rng,
Command: protocol.Command{
Title: "Toggle gc annotation details",
Command: CommandToggleDetails.ID(),
Arguments: jsonArgs,
},
}}, nil
return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil
}

View File

@ -1,243 +0,0 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"context"
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/lsp/analysis/fillstruct"
"golang.org/x/tools/internal/lsp/analysis/undeclaredname"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/span"
errors "golang.org/x/xerrors"
)
type Command struct {
Title string
Name string
// Async controls whether the command executes asynchronously.
Async bool
}
// CommandPrefix is the prefix of all command names gopls uses externally.
const CommandPrefix = "gopls."
// ID adds the CommandPrefix to the command name, in order to avoid
// collisions with other language servers.
func (c Command) ID() string {
return CommandPrefix + c.Name
}
// SuggestedFixFunc is a function used to get the suggested fixes for a given
// gopls command, some of which are provided by go/analysis.Analyzers. Some of
// the analyzers in internal/lsp/analysis are not efficient enough to include
// suggested fixes with their diagnostics, so we have to compute them
// separately. Such analyzers should provide a function with a signature of
// SuggestedFixFunc.
type SuggestedFixFunc func(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error)
// Commands are the commands currently supported by gopls.
var Commands = []*Command{
CommandAddDependency,
CommandCheckUpgrades,
CommandExtractFunction,
CommandExtractVariable,
CommandFillStruct,
CommandToggleDetails, // gc_details
CommandGenerate,
CommandGenerateGoplsMod,
CommandGoGetPackage,
CommandRegenerateCgo,
CommandRemoveDependency,
CommandTest,
CommandTidy,
CommandUndeclaredName,
CommandUpdateGoSum,
CommandUpgradeDependency,
CommandVendor,
}
var (
// CommandTest runs `go test` for a specific test function.
CommandTest = &Command{
Name: "test",
Title: "Run test(s)",
Async: true,
}
// CommandGenerate runs `go generate` for a given directory.
CommandGenerate = &Command{
Name: "generate",
Title: "Run go generate",
}
// CommandTidy runs `go mod tidy` for a module.
CommandTidy = &Command{
Name: "tidy",
Title: "Run go mod tidy",
}
// CommandVendor runs `go mod vendor` for a module.
CommandVendor = &Command{
Name: "vendor",
Title: "Run go mod vendor",
}
// CommandGoGetPackage runs `go get` to fetch a package.
CommandGoGetPackage = &Command{
Name: "go_get_package",
Title: "go get package",
}
// CommandUpdateGoSum updates the go.sum file for a module.
CommandUpdateGoSum = &Command{
Name: "update_go_sum",
Title: "Update go.sum",
}
// CommandCheckUpgrades checks for module upgrades.
CommandCheckUpgrades = &Command{
Name: "check_upgrades",
Title: "Check for upgrades",
}
// 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",
Title: "Regenerate cgo",
}
// CommandToggleDetails controls calculation of gc annotations.
CommandToggleDetails = &Command{
Name: "gc_details",
Title: "Toggle gc_details",
}
// CommandFillStruct is a gopls command to fill a struct with default
// values.
CommandFillStruct = &Command{
Name: "fill_struct",
Title: "Fill struct",
}
// CommandUndeclaredName adds a variable declaration for an undeclared
// name.
CommandUndeclaredName = &Command{
Name: "undeclared_name",
Title: "Undeclared name",
}
// CommandExtractVariable extracts an expression to a variable.
CommandExtractVariable = &Command{
Name: "extract_variable",
Title: "Extract to variable",
}
// CommandExtractFunction extracts statements to a function.
CommandExtractFunction = &Command{
Name: "extract_function",
Title: "Extract to function",
}
// CommandGenerateGoplsMod (re)generates the gopls.mod file.
CommandGenerateGoplsMod = &Command{
Name: "generate_gopls_mod",
Title: "Generate gopls.mod",
}
)
// suggestedFixes maps a suggested fix command id to its handler.
var suggestedFixes = map[string]SuggestedFixFunc{
CommandFillStruct.ID(): fillstruct.SuggestedFix,
CommandUndeclaredName.ID(): undeclaredname.SuggestedFix,
CommandExtractVariable.ID(): extractVariable,
CommandExtractFunction.ID(): extractFunction,
}
// ApplyFix applies the command's suggested fix to the given file and
// range, returning the resulting edits.
func ApplyFix(ctx context.Context, cmdid string, snapshot Snapshot, fh VersionedFileHandle, pRng protocol.Range) ([]protocol.TextDocumentEdit, error) {
handler, ok := suggestedFixes[cmdid]
if !ok {
return nil, fmt.Errorf("no suggested fix function for %s", cmdid)
}
fset, rng, src, file, m, pkg, info, err := getAllSuggestedFixInputs(ctx, snapshot, fh, pRng)
if err != nil {
return nil, err
}
fix, err := handler(fset, rng, src, file, pkg, info)
if err != nil {
return nil, err
}
if fix == nil {
return nil, nil
}
var edits []protocol.TextDocumentEdit
for _, edit := range fix.TextEdits {
rng := span.NewRange(fset, edit.Pos, edit.End)
spn, err := rng.Span()
if err != nil {
return nil, err
}
clRng, err := m.Range(spn)
if err != nil {
return nil, err
}
edits = append(edits, protocol.TextDocumentEdit{
TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
Version: fh.Version(),
TextDocumentIdentifier: protocol.TextDocumentIdentifier{
URI: protocol.URIFromSpanURI(fh.URI()),
},
},
Edits: []protocol.TextEdit{
{
Range: clRng,
NewText: string(edit.NewText),
},
},
})
}
return edits, nil
}
// getAllSuggestedFixInputs is a helper function to collect all possible needed
// inputs for an AppliesFunc or SuggestedFixFunc.
func getAllSuggestedFixInputs(ctx context.Context, snapshot Snapshot, fh FileHandle, pRng protocol.Range) (*token.FileSet, span.Range, []byte, *ast.File, *protocol.ColumnMapper, *types.Package, *types.Info, error) {
pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
if err != nil {
return nil, span.Range{}, nil, nil, nil, nil, nil, errors.Errorf("getting file for Identifier: %w", err)
}
rng, err := pgf.Mapper.RangeToSpanRange(pRng)
if err != nil {
return nil, span.Range{}, nil, nil, nil, nil, nil, err
}
return snapshot.FileSet(), rng, pgf.Src, pgf.File, pgf.Mapper, pkg.GetTypes(), pkg.GetTypesInfo(), nil
}

112
internal/lsp/source/fix.go Normal file
View File

@ -0,0 +1,112 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"context"
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/lsp/analysis/fillstruct"
"golang.org/x/tools/internal/lsp/analysis/undeclaredname"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/span"
errors "golang.org/x/xerrors"
)
// SuggestedFixFunc is a function used to get the suggested fixes for a given
// gopls command, some of which are provided by go/analysis.Analyzers. Some of
// the analyzers in internal/lsp/analysis are not efficient enough to include
// suggested fixes with their diagnostics, so we have to compute them
// separately. Such analyzers should provide a function with a signature of
// SuggestedFixFunc.
type SuggestedFixFunc func(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error)
const (
FillStruct = "fill_struct"
UndeclaredName = "undeclared_name"
ExtractVariable = "extract_variable"
ExtractFunction = "extract_function"
)
// suggestedFixes maps a suggested fix command id to its handler.
var suggestedFixes = map[string]SuggestedFixFunc{
FillStruct: fillstruct.SuggestedFix,
UndeclaredName: undeclaredname.SuggestedFix,
ExtractVariable: extractVariable,
ExtractFunction: extractFunction,
}
func SuggestedFixFromCommand(cmd protocol.Command) SuggestedFix {
return SuggestedFix{
Title: cmd.Title,
Command: &cmd,
}
}
// ApplyFix applies the command's suggested fix to the given file and
// range, returning the resulting edits.
func ApplyFix(ctx context.Context, fix string, snapshot Snapshot, fh VersionedFileHandle, pRng protocol.Range) ([]protocol.TextDocumentEdit, error) {
handler, ok := suggestedFixes[fix]
if !ok {
return nil, fmt.Errorf("no suggested fix function for %s", fix)
}
fset, rng, src, file, m, pkg, info, err := getAllSuggestedFixInputs(ctx, snapshot, fh, pRng)
if err != nil {
return nil, err
}
suggestion, err := handler(fset, rng, src, file, pkg, info)
if err != nil {
return nil, err
}
if suggestion == nil {
return nil, nil
}
var edits []protocol.TextDocumentEdit
for _, edit := range suggestion.TextEdits {
rng := span.NewRange(fset, edit.Pos, edit.End)
spn, err := rng.Span()
if err != nil {
return nil, err
}
clRng, err := m.Range(spn)
if err != nil {
return nil, err
}
edits = append(edits, protocol.TextDocumentEdit{
TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
Version: fh.Version(),
TextDocumentIdentifier: protocol.TextDocumentIdentifier{
URI: protocol.URIFromSpanURI(fh.URI()),
},
},
Edits: []protocol.TextEdit{
{
Range: clRng,
NewText: string(edit.NewText),
},
},
})
}
return edits, nil
}
// getAllSuggestedFixInputs is a helper function to collect all possible needed
// inputs for an AppliesFunc or SuggestedFixFunc.
func getAllSuggestedFixInputs(ctx context.Context, snapshot Snapshot, fh FileHandle, pRng protocol.Range) (*token.FileSet, span.Range, []byte, *ast.File, *protocol.ColumnMapper, *types.Package, *types.Info, error) {
pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
if err != nil {
return nil, span.Range{}, nil, nil, nil, nil, nil, errors.Errorf("getting file for Identifier: %w", err)
}
rng, err := pgf.Mapper.RangeToSpanRange(pRng)
if err != nil {
return nil, span.Range{}, nil, nil, nil, nil, nil, err
}
return snapshot.FileSet(), rng, pgf.Src, pgf.File, pgf.Mapper, pkg.GetTypes(), pkg.GetTypesInfo(), nil
}

View File

@ -53,6 +53,7 @@ import (
"golang.org/x/tools/internal/lsp/analysis/simplifyslice"
"golang.org/x/tools/internal/lsp/analysis/undeclaredname"
"golang.org/x/tools/internal/lsp/analysis/unusedparams"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/diff"
"golang.org/x/tools/internal/lsp/diff/myers"
"golang.org/x/tools/internal/lsp/protocol"
@ -70,7 +71,7 @@ var (
func DefaultOptions() *Options {
optionsOnce.Do(func() {
var commands []string
for _, c := range Commands {
for _, c := range command.Commands {
commands = append(commands, c.ID())
}
defaultOptions = &Options{
@ -129,12 +130,12 @@ func DefaultOptions() *Options {
CompletionBudget: 100 * time.Millisecond,
},
Codelenses: map[string]bool{
CommandGenerate.Name: true,
CommandRegenerateCgo.Name: true,
CommandTidy.Name: true,
CommandToggleDetails.Name: false,
CommandUpgradeDependency.Name: true,
CommandVendor.Name: true,
string(command.Generate): true,
string(command.RegenerateCgo): true,
string(command.Tidy): true,
string(command.GCDetails): false,
string(command.UpgradeDependency): true,
string(command.Vendor): true,
},
},
},
@ -676,8 +677,8 @@ func (o *Options) enableAllExperiments() {
}
func (o *Options) enableAllExperimentMaps() {
if _, ok := o.Codelenses[CommandToggleDetails.Name]; !ok {
o.Codelenses[CommandToggleDetails.Name] = true
if _, ok := o.Codelenses[string(command.GCDetails)]; !ok {
o.Codelenses[string(command.GCDetails)] = true
}
if _, ok := o.Analyses[unusedparams.Analyzer.Name]; !ok {
o.Analyses[unusedparams.Analyzer.Name] = true
@ -1089,7 +1090,7 @@ func typeErrorAnalyzers() map[string]Analyzer {
undeclaredname.Analyzer.Name: {
Analyzer: undeclaredname.Analyzer,
FixesError: undeclaredname.FixesError,
Command: CommandUndeclaredName,
Fix: UndeclaredName,
Enabled: true,
},
}
@ -1099,7 +1100,7 @@ func convenienceAnalyzers() map[string]Analyzer {
return map[string]Analyzer{
fillstruct.Analyzer.Name: {
Analyzer: fillstruct.Analyzer,
Command: CommandFillStruct,
Fix: FillStruct,
Enabled: true,
},
}

View File

@ -6,8 +6,6 @@ package source
import (
"context"
"encoding/json"
"fmt"
"go/ast"
"go/printer"
"go/token"
@ -293,50 +291,6 @@ func findFileInDeps(pkg Package, uri span.URI) (*ParsedGoFile, Package, error) {
return nil, nil, errors.Errorf("no file for %s in package %s", uri, pkg.ID())
}
// MarshalArgs encodes the given arguments to json.RawMessages. This function
// is used to construct arguments to a protocol.Command.
//
// Example usage:
//
// jsonArgs, err := EncodeArgs(1, "hello", true, StructuredArg{42, 12.6})
//
func MarshalArgs(args ...interface{}) ([]json.RawMessage, error) {
var out []json.RawMessage
for _, arg := range args {
argJSON, err := json.Marshal(arg)
if err != nil {
return nil, err
}
out = append(out, argJSON)
}
return out, nil
}
// UnmarshalArgs decodes the given json.RawMessages to the variables provided
// by args. Each element of args should be a pointer.
//
// Example usage:
//
// var (
// num int
// str string
// bul bool
// structured StructuredArg
// )
// err := UnmarshalArgs(args, &num, &str, &bul, &structured)
//
func UnmarshalArgs(jsonArgs []json.RawMessage, args ...interface{}) error {
if len(args) != len(jsonArgs) {
return fmt.Errorf("DecodeArgs: expected %d input arguments, got %d JSON arguments", len(args), len(jsonArgs))
}
for i, arg := range args {
if err := json.Unmarshal(jsonArgs[i], arg); err != nil {
return err
}
}
return nil
}
// ImportPath returns the unquoted import path of s,
// or "" if the path is not properly quoted.
func ImportPath(s *ast.ImportSpec) string {

View File

@ -505,11 +505,11 @@ type Analyzer struct {
// the value of the Staticcheck setting overrides this field.
Enabled bool
// Command is the name of the command used to invoke the suggested fixes
// for the analyzer. It is non-nil if we expect this analyzer to provide
// its fix separately from its diagnostics. That is, we should apply the
// analyzer's suggested fixes through a Command, not a TextEdit.
Command *Command
// Fix is the name of the suggested fix name used to invoke the suggested
// fixes for the analyzer. It is non-empty if we expect this analyzer to
// provide its fix separately from its diagnostics. That is, we should apply
// the analyzer's suggested fixes through a Command, not a TextEdit.
Fix string
// If this is true, then we can apply the suggested fixes
// as part of a source.FixAll codeaction.

View File

@ -26,6 +26,7 @@ import (
"golang.org/x/tools/go/expect"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/packages/packagestest"
"golang.org/x/tools/internal/lsp/command"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/source/completion"
@ -234,7 +235,7 @@ func DefaultOptions(o *source.Options) {
},
source.Sum: {},
}
o.UserOptions.Codelenses[source.CommandTest.Name] = true
o.UserOptions.Codelenses[string(command.Test)] = true
o.HoverKind = source.SynopsisDocumentation
o.InsertTextFormat = protocol.SnippetTextFormat
o.CompletionBudget = time.Minute