diff --git a/internal/lsp/cmd/test/cmdtest.go b/internal/lsp/cmd/test/cmdtest.go index 7754610d67..12c2fd5638 100644 --- a/internal/lsp/cmd/test/cmdtest.go +++ b/internal/lsp/cmd/test/cmdtest.go @@ -71,7 +71,7 @@ func NewRunner(exporter packagestest.Exporter, data *tests.Data, ctx context.Con return r } -func (r *runner) CodeLens(t *testing.T, spn span.Span, want []protocol.CodeLens) { +func (r *runner) CodeLens(t *testing.T, uri span.URI, want []protocol.CodeLens) { //TODO: add command line completions tests when it works } diff --git a/internal/lsp/command.go b/internal/lsp/command.go index 4c0ad878d2..2c00b7ec12 100644 --- a/internal/lsp/command.go +++ b/internal/lsp/command.go @@ -2,6 +2,7 @@ package lsp import ( "context" + "strings" "golang.org/x/tools/internal/gocommand" "golang.org/x/tools/internal/lsp/protocol" @@ -35,15 +36,15 @@ func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCom return nil, errors.Errorf("expected one file URI and one dependency for call to `go get`, got %v", params.Arguments) } uri := protocol.DocumentURI(params.Arguments[0].(string)) + deps := params.Arguments[1].(string) snapshot, _, ok, err := s.beginFileRequest(uri, source.UnknownKind) if !ok { return nil, err } - dep := params.Arguments[1].(string) // Run "go get" on the dependency to upgrade it to the latest version. inv := gocommand.Invocation{ Verb: "get", - Args: []string{dep}, + Args: strings.Split(deps, " "), Env: snapshot.Config(ctx).Env, WorkingDir: snapshot.View().Folder().Filename(), } diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index b10b07389b..2c3ae63781 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -96,19 +96,19 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) { } } -func (r *runner) CodeLens(t *testing.T, spn span.Span, want []protocol.CodeLens) { - if source.DetectLanguage("", spn.URI().Filename()) != source.Mod { +func (r *runner) CodeLens(t *testing.T, uri span.URI, want []protocol.CodeLens) { + if source.DetectLanguage("", uri.Filename()) != source.Mod { return } - v, err := r.server.session.ViewOf(spn.URI()) + v, err := r.server.session.ViewOf(uri) if err != nil { t.Fatal(err) } - got, err := mod.CodeLens(r.ctx, v.Snapshot(), spn.URI()) + got, err := mod.CodeLens(r.ctx, v.Snapshot(), uri) if err != nil { t.Fatal(err) } - if diff := tests.DiffCodeLens(spn.URI(), want, got); diff != "" { + if diff := tests.DiffCodeLens(uri, want, got); diff != "" { t.Error(diff) } } diff --git a/internal/lsp/mod/code_lens.go b/internal/lsp/mod/code_lens.go index bb087882ac..7a69a27e03 100644 --- a/internal/lsp/mod/code_lens.go +++ b/internal/lsp/mod/code_lens.go @@ -3,7 +3,9 @@ package mod import ( "context" "fmt" + "strings" + "golang.org/x/mod/modfile" "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/lsp/telemetry" @@ -32,6 +34,7 @@ func CodeLens(ctx context.Context, snapshot source.Snapshot, uri span.URI) ([]pr return nil, err } var codelens []protocol.CodeLens + var allUpgrades []string for _, req := range f.Require { dep := req.Mod.Path latest, ok := upgrades[dep] @@ -39,18 +42,7 @@ func CodeLens(ctx context.Context, snapshot source.Snapshot, uri span.URI) ([]pr continue } // Get the range of the require directive. - s, e := req.Syntax.Start, req.Syntax.End - line, col, err := m.Converter.ToPosition(s.Byte) - if err != nil { - return nil, err - } - start := span.NewPoint(line, col, s.Byte) - line, col, err = m.Converter.ToPosition(e.Byte) - if err != nil { - return nil, err - } - end := span.NewPoint(line, col, e.Byte) - rng, err := m.Range(span.New(uri, start, end)) + rng, err := positionsToRange(uri, m, req.Syntax.Start, req.Syntax.End) if err != nil { return nil, err } @@ -62,6 +54,41 @@ func CodeLens(ctx context.Context, snapshot source.Snapshot, uri span.URI) ([]pr Arguments: []interface{}{uri, dep}, }, }) + allUpgrades = append(allUpgrades, dep) + } + // If there is at least 1 upgrade, add an "Upgrade all dependencies" to the module statement. + if module := f.Module; len(allUpgrades) > 0 && module != nil && module.Syntax != nil { + // Get the range of the module directive. + rng, err := positionsToRange(uri, m, module.Syntax.Start, module.Syntax.End) + if err != nil { + return nil, err + } + codelens = append(codelens, protocol.CodeLens{ + Range: rng, + Command: protocol.Command{ + Title: "Upgrade all dependencies", + Command: "upgrade.dependency", + Arguments: []interface{}{uri, strings.Join(append([]string{"-u"}, allUpgrades...), " ")}, + }, + }) } return codelens, err } + +func positionsToRange(uri span.URI, m *protocol.ColumnMapper, s, e modfile.Position) (protocol.Range, error) { + line, col, err := m.Converter.ToPosition(s.Byte) + if err != nil { + return protocol.Range{}, err + } + start := span.NewPoint(line, col, s.Byte) + line, col, err = m.Converter.ToPosition(e.Byte) + if err != nil { + return protocol.Range{}, err + } + end := span.NewPoint(line, col, e.Byte) + rng, err := m.Range(span.New(uri, start, end)) + if err != nil { + return protocol.Range{}, err + } + return rng, err +} diff --git a/internal/lsp/server.go b/internal/lsp/server.go index b5bdb6fe80..65c27e0ccb 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -83,9 +83,6 @@ func (s *Server) codeLens(ctx context.Context, params *protocol.CodeLensParams) if !ok { return nil, err } - if !snapshot.IsSaved(fh.Identity().URI) { - return nil, nil - } return mod.CodeLens(ctx, snapshot, fh.Identity().URI) } diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index d2721ec570..b3ae9f4bbd 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -894,7 +894,7 @@ func (r *runner) Link(t *testing.T, uri span.URI, wantLinks []tests.Link) { // This is a pure LSP feature, no source level functionality to be tested. } -func (r *runner) CodeLens(t *testing.T, spn span.Span, want []protocol.CodeLens) { +func (r *runner) CodeLens(t *testing.T, uri span.URI, want []protocol.CodeLens) { // This is a pure LSP feature, no source level functionality to be tested. } diff --git a/internal/lsp/testdata/upgradedep/primarymod/go.mod b/internal/lsp/testdata/upgradedep/primarymod/go.mod index ac1ed82303..7aacfb2ca9 100644 --- a/internal/lsp/testdata/upgradedep/primarymod/go.mod +++ b/internal/lsp/testdata/upgradedep/primarymod/go.mod @@ -1,4 +1,4 @@ -module upgradedep +module upgradedep //@codelens("module upgradedep", "Upgrade all dependencies", "upgrade.dependency") // TODO(microsoft/vscode-go#12): Another issue. //@link(`microsoft/vscode-go#12`, `https://github.com/microsoft/vscode-go/issues/12`) diff --git a/internal/lsp/testdata/upgradedep/summary.txt.golden b/internal/lsp/testdata/upgradedep/summary.txt.golden index 7ae33ebc61..79042cc6c8 100644 --- a/internal/lsp/testdata/upgradedep/summary.txt.golden +++ b/internal/lsp/testdata/upgradedep/summary.txt.golden @@ -1,5 +1,5 @@ -- summary -- -CodeLensCount = 1 +CodeLensCount = 2 CompletionsCount = 0 CompletionSnippetCount = 0 UnimportedCompletionsCount = 0 diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index 6e863a7f34..eb1a852b5d 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -43,7 +43,7 @@ const ( var UpdateGolden = flag.Bool("golden", false, "Update golden files") -type CodeLens map[span.Span][]protocol.CodeLens +type CodeLens map[span.URI][]protocol.CodeLens type Diagnostics map[span.URI][]source.Diagnostic type CompletionItems map[token.Pos]*source.CompletionItem type Completions map[span.Span][]Completion @@ -115,7 +115,7 @@ type Data struct { } type Tests interface { - CodeLens(*testing.T, span.Span, []protocol.CodeLens) + CodeLens(*testing.T, span.URI, []protocol.CodeLens) Diagnostics(*testing.T, span.URI, []source.Diagnostic) Completion(*testing.T, span.Span, Completion, CompletionItems) CompletionSnippet(*testing.T, span.Span, CompletionSnippet, bool, CompletionItems) @@ -530,14 +530,14 @@ func Run(t *testing.T, tests Tests, data *Data) { t.Run("CodeLens", func(t *testing.T) { t.Helper() - for spn, want := range data.CodeLens { + for uri, want := range data.CodeLens { // Check if we should skip this URI if the -modfile flag is not available. - if shouldSkip(data, spn.URI()) { + if shouldSkip(data, uri) { continue } - t.Run(SpanName(spn), func(t *testing.T) { + t.Run(uriName(uri), func(t *testing.T) { t.Helper() - tests.CodeLens(t, spn, want) + tests.CodeLens(t, uri, want) }) } }) @@ -767,7 +767,7 @@ func checkData(t *testing.T, data *Data) { return count } - countCodeLens := func(c map[span.Span][]protocol.CodeLens) (count int) { + countCodeLens := func(c map[span.URI][]protocol.CodeLens) (count int) { for _, want := range c { count += len(want) } @@ -883,8 +883,8 @@ func (data *Data) Golden(tag string, target string, update func() ([]byte, error } func (data *Data) collectCodeLens(spn span.Span, title, cmd string) { - if _, ok := data.CodeLens[spn]; !ok { - data.CodeLens[spn] = []protocol.CodeLens{} + if _, ok := data.CodeLens[spn.URI()]; !ok { + data.CodeLens[spn.URI()] = []protocol.CodeLens{} } m, err := data.Mapper(spn.URI()) if err != nil { @@ -894,7 +894,7 @@ func (data *Data) collectCodeLens(spn span.Span, title, cmd string) { if err != nil { return } - data.CodeLens[spn] = append(data.CodeLens[spn], protocol.CodeLens{ + data.CodeLens[spn.URI()] = append(data.CodeLens[spn.URI()], protocol.CodeLens{ Range: rng, Command: protocol.Command{ Title: title, diff --git a/internal/lsp/tests/util.go b/internal/lsp/tests/util.go index b81cfd01e6..32cbf14c12 100644 --- a/internal/lsp/tests/util.go +++ b/internal/lsp/tests/util.go @@ -197,7 +197,7 @@ func DiffDiagnostics(uri span.URI, want, got []source.Diagnostic) string { return "" } -func summarizeDiagnostics(i int, uri span.URI, want []source.Diagnostic, got []source.Diagnostic, reason string, args ...interface{}) string { +func summarizeDiagnostics(i int, uri span.URI, want, got []source.Diagnostic, reason string, args ...interface{}) string { msg := &bytes.Buffer{} fmt.Fprint(msg, "diagnostics failed") if i >= 0 {