diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go index dfa1f4f8c6..c2e80ff5b9 100644 --- a/internal/lsp/cache/mod.go +++ b/internal/lsp/cache/mod.go @@ -6,7 +6,9 @@ package cache import ( "context" + "encoding/json" "fmt" + "io" "os" "path/filepath" "regexp" @@ -273,6 +275,15 @@ func (muh *modUpgradeHandle) upgrades(ctx context.Context, snapshot *snapshot) ( return data.upgrades, data.err } +// moduleUpgrade describes a module that can be upgraded to a particular +// version. +type moduleUpgrade struct { + Path string + Update struct { + Version string + } +} + func (s *snapshot) ModUpgrade(ctx context.Context, fh source.FileHandle) (map[string]string, error) { if err := s.awaitLoaded(ctx); err != nil { return nil, err @@ -306,7 +317,7 @@ func (s *snapshot) ModUpgrade(ctx context.Context, fh source.FileHandle) (map[st } // Run "go list -mod readonly -u -m all" to be able to see which deps can be // upgraded without modifying mod file. - args := []string{"-u", "-m", "all"} + args := []string{"-u", "-m", "-json", "all"} if !snapshot.view.tmpMod || containsVendor(fh.URI()) { // Use -mod=readonly if the module contains a vendor directory // (see golang/go#38711). @@ -316,28 +327,26 @@ func (s *snapshot) ModUpgrade(ctx context.Context, fh source.FileHandle) (map[st if err != nil { return &modUpgradeData{err: err} } - upgradesList := strings.Split(stdout.String(), "\n") - if len(upgradesList) <= 1 { - return nil + var upgradeList []moduleUpgrade + dec := json.NewDecoder(stdout) + for { + var m moduleUpgrade + if err := dec.Decode(&m); err == io.EOF { + break + } else if err != nil { + return &modUpgradeData{err: err} + } + upgradeList = append(upgradeList, m) + } + if len(upgradeList) <= 1 { + return &modUpgradeData{} } upgrades := make(map[string]string) - for _, upgrade := range upgradesList[1:] { - // Example: "github.com/x/tools v1.1.0 [v1.2.0]" - info := strings.Split(upgrade, " ") - if len(info) != 3 { + for _, upgrade := range upgradeList[1:] { + if upgrade.Update.Version == "" { continue } - dep, version := info[0], info[2] - - // Make sure that the format matches our expectation. - if len(version) < 2 { - continue - } - if version[0] != '[' || version[len(version)-1] != ']' { - continue - } - latest := version[1 : len(version)-1] // remove the "[" and "]" - upgrades[dep] = latest + upgrades[upgrade.Path] = upgrade.Update.Version } return &modUpgradeData{ upgrades: upgrades, diff --git a/internal/lsp/command.go b/internal/lsp/command.go index 191af7321a..c53d4dc59a 100644 --- a/internal/lsp/command.go +++ b/internal/lsp/command.go @@ -105,7 +105,7 @@ func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCom // clients are aware of the work item before the command completes. This // matters for regtests, where having a continuous thread of work is // convenient for assertions. - work := s.progress.start(ctx, title, title+": running...", params.WorkDoneToken, cancel) + work := s.progress.start(ctx, title, "Running...", params.WorkDoneToken, cancel) go func() { defer cancel() err := s.runCommand(ctx, work, command, params.Arguments) @@ -117,10 +117,12 @@ func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCom work.end(title + ": failed") // Show a message when work completes with error, because the progress end // message is typically dismissed immediately by LSP clients. - s.client.ShowMessage(ctx, &protocol.ShowMessageParams{ + if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{ Type: protocol.Error, Message: fmt.Sprintf("%s: An error occurred: %v", title, err), - }) + }); err != nil { + event.Error(ctx, title+": failed to show message", err) + } default: work.end(command.Name + ": completed") }