internal/lsp: use -json for module upgrades

This is better than parsing the default output.
Also, change the start progress message, since it ends up duplicating
the title in the message that the user sees.

Change-Id: I3540d30c7976c6be0722531b2e258341081e0b72
Reviewed-on: https://go-review.googlesource.com/c/tools/+/251920
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go
index dfa1f4f..c2e80ff 100644
--- a/internal/lsp/cache/mod.go
+++ b/internal/lsp/cache/mod.go
@@ -6,7 +6,9 @@
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
+	"io"
 	"os"
 	"path/filepath"
 	"regexp"
@@ -273,6 +275,15 @@
 	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 @@
 		}
 		// 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 @@
 		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 191af73..c53d4dc 100644
--- a/internal/lsp/command.go
+++ b/internal/lsp/command.go
@@ -105,7 +105,7 @@
 	// 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 @@
 			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")
 		}