github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/command.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package lsp
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"os"
    15  	"path/filepath"
    16  	"sort"
    17  	"strings"
    18  
    19  	"golang.org/x/mod/modfile"
    20  	"github.com/jhump/golang-x-tools/go/ast/astutil"
    21  	"github.com/jhump/golang-x-tools/internal/event"
    22  	"github.com/jhump/golang-x-tools/internal/gocommand"
    23  	"github.com/jhump/golang-x-tools/internal/lsp/command"
    24  	"github.com/jhump/golang-x-tools/internal/lsp/debug"
    25  	"github.com/jhump/golang-x-tools/internal/lsp/progress"
    26  	"github.com/jhump/golang-x-tools/internal/lsp/protocol"
    27  	"github.com/jhump/golang-x-tools/internal/lsp/source"
    28  	"github.com/jhump/golang-x-tools/internal/span"
    29  	"github.com/jhump/golang-x-tools/internal/xcontext"
    30  	errors "golang.org/x/xerrors"
    31  )
    32  
    33  func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) {
    34  	var found bool
    35  	for _, name := range s.session.Options().SupportedCommands {
    36  		if name == params.Command {
    37  			found = true
    38  			break
    39  		}
    40  	}
    41  	if !found {
    42  		return nil, fmt.Errorf("%s is not a supported command", params.Command)
    43  	}
    44  
    45  	handler := &commandHandler{
    46  		s:      s,
    47  		params: params,
    48  	}
    49  	return command.Dispatch(ctx, params, handler)
    50  }
    51  
    52  type commandHandler struct {
    53  	s      *Server
    54  	params *protocol.ExecuteCommandParams
    55  }
    56  
    57  // commandConfig configures common command set-up and execution.
    58  type commandConfig struct {
    59  	async       bool                 // whether to run the command asynchronously. Async commands can only return errors.
    60  	requireSave bool                 // whether all files must be saved for the command to work
    61  	progress    string               // title to use for progress reporting. If empty, no progress will be reported.
    62  	forURI      protocol.DocumentURI // URI to resolve to a snapshot. If unset, snapshot will be nil.
    63  }
    64  
    65  // commandDeps is evaluated from a commandConfig. Note that not all fields may
    66  // be populated, depending on which configuration is set. See comments in-line
    67  // for details.
    68  type commandDeps struct {
    69  	snapshot source.Snapshot            // present if cfg.forURI was set
    70  	fh       source.VersionedFileHandle // present if cfg.forURI was set
    71  	work     *progress.WorkDone         // present cfg.progress was set
    72  }
    73  
    74  type commandFunc func(context.Context, commandDeps) error
    75  
    76  func (c *commandHandler) run(ctx context.Context, cfg commandConfig, run commandFunc) (err error) {
    77  	if cfg.requireSave {
    78  		var unsaved []string
    79  		for _, overlay := range c.s.session.Overlays() {
    80  			if !overlay.Saved() {
    81  				unsaved = append(unsaved, overlay.URI().Filename())
    82  			}
    83  		}
    84  		if len(unsaved) > 0 {
    85  			return errors.Errorf("All files must be saved first (unsaved: %v).", unsaved)
    86  		}
    87  	}
    88  	var deps commandDeps
    89  	if cfg.forURI != "" {
    90  		var ok bool
    91  		var release func()
    92  		deps.snapshot, deps.fh, ok, release, err = c.s.beginFileRequest(ctx, cfg.forURI, source.UnknownKind)
    93  		defer release()
    94  		if !ok {
    95  			return err
    96  		}
    97  	}
    98  	ctx, cancel := context.WithCancel(xcontext.Detach(ctx))
    99  	if cfg.progress != "" {
   100  		deps.work = c.s.progress.Start(ctx, cfg.progress, "Running...", c.params.WorkDoneToken, cancel)
   101  	}
   102  	runcmd := func() error {
   103  		defer cancel()
   104  		err := run(ctx, deps)
   105  		if deps.work != nil {
   106  			switch {
   107  			case errors.Is(err, context.Canceled):
   108  				deps.work.End("canceled")
   109  			case err != nil:
   110  				event.Error(ctx, "command error", err)
   111  				deps.work.End("failed")
   112  			default:
   113  				deps.work.End("completed")
   114  			}
   115  		}
   116  		return err
   117  	}
   118  	if cfg.async {
   119  		go func() {
   120  			if err := runcmd(); err != nil {
   121  				if showMessageErr := c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
   122  					Type:    protocol.Error,
   123  					Message: err.Error(),
   124  				}); showMessageErr != nil {
   125  					event.Error(ctx, fmt.Sprintf("failed to show message: %q", err.Error()), showMessageErr)
   126  				}
   127  			}
   128  		}()
   129  		return nil
   130  	}
   131  	return runcmd()
   132  }
   133  
   134  func (c *commandHandler) ApplyFix(ctx context.Context, args command.ApplyFixArgs) error {
   135  	return c.run(ctx, commandConfig{
   136  		// Note: no progress here. Applying fixes should be quick.
   137  		forURI: args.URI,
   138  	}, func(ctx context.Context, deps commandDeps) error {
   139  		edits, err := source.ApplyFix(ctx, args.Fix, deps.snapshot, deps.fh, args.Range)
   140  		if err != nil {
   141  			return err
   142  		}
   143  		r, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
   144  			Edit: protocol.WorkspaceEdit{
   145  				DocumentChanges: edits,
   146  			},
   147  		})
   148  		if err != nil {
   149  			return err
   150  		}
   151  		if !r.Applied {
   152  			return errors.New(r.FailureReason)
   153  		}
   154  		return nil
   155  	})
   156  }
   157  
   158  func (c *commandHandler) RegenerateCgo(ctx context.Context, args command.URIArg) error {
   159  	return c.run(ctx, commandConfig{
   160  		progress: "Regenerating Cgo",
   161  	}, func(ctx context.Context, deps commandDeps) error {
   162  		mod := source.FileModification{
   163  			URI:    args.URI.SpanURI(),
   164  			Action: source.InvalidateMetadata,
   165  		}
   166  		return c.s.didModifyFiles(ctx, []source.FileModification{mod}, FromRegenerateCgo)
   167  	})
   168  }
   169  
   170  func (c *commandHandler) CheckUpgrades(ctx context.Context, args command.CheckUpgradesArgs) error {
   171  	return c.run(ctx, commandConfig{
   172  		forURI:   args.URI,
   173  		progress: "Checking for upgrades",
   174  	}, func(ctx context.Context, deps commandDeps) error {
   175  		upgrades, err := c.s.getUpgrades(ctx, deps.snapshot, args.URI.SpanURI(), args.Modules)
   176  		if err != nil {
   177  			return err
   178  		}
   179  		deps.snapshot.View().RegisterModuleUpgrades(upgrades)
   180  		// Re-diagnose the snapshot to publish the new module diagnostics.
   181  		c.s.diagnoseSnapshot(deps.snapshot, nil, false)
   182  		return nil
   183  	})
   184  }
   185  
   186  func (c *commandHandler) AddDependency(ctx context.Context, args command.DependencyArgs) error {
   187  	return c.GoGetModule(ctx, args)
   188  }
   189  
   190  func (c *commandHandler) UpgradeDependency(ctx context.Context, args command.DependencyArgs) error {
   191  	return c.GoGetModule(ctx, args)
   192  }
   193  
   194  func (c *commandHandler) GoGetModule(ctx context.Context, args command.DependencyArgs) error {
   195  	return c.run(ctx, commandConfig{
   196  		progress: "Running go get",
   197  		forURI:   args.URI,
   198  	}, func(ctx context.Context, deps commandDeps) error {
   199  		return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI.SpanURI(), func(invoke func(...string) (*bytes.Buffer, error)) error {
   200  			return runGoGetModule(invoke, args.AddRequire, args.GoCmdArgs)
   201  		})
   202  	})
   203  }
   204  
   205  // TODO(rFindley): UpdateGoSum, Tidy, and Vendor could probably all be one command.
   206  func (c *commandHandler) UpdateGoSum(ctx context.Context, args command.URIArgs) error {
   207  	return c.run(ctx, commandConfig{
   208  		progress: "Updating go.sum",
   209  	}, func(ctx context.Context, deps commandDeps) error {
   210  		for _, uri := range args.URIs {
   211  			snapshot, fh, ok, release, err := c.s.beginFileRequest(ctx, uri, source.UnknownKind)
   212  			defer release()
   213  			if !ok {
   214  				return err
   215  			}
   216  			if err := c.s.runGoModUpdateCommands(ctx, snapshot, fh.URI(), func(invoke func(...string) (*bytes.Buffer, error)) error {
   217  				_, err := invoke("list", "all")
   218  				return err
   219  			}); err != nil {
   220  				return err
   221  			}
   222  		}
   223  		return nil
   224  	})
   225  }
   226  
   227  func (c *commandHandler) Tidy(ctx context.Context, args command.URIArgs) error {
   228  	return c.run(ctx, commandConfig{
   229  		requireSave: true,
   230  		progress:    "Running go mod tidy",
   231  	}, func(ctx context.Context, deps commandDeps) error {
   232  		for _, uri := range args.URIs {
   233  			snapshot, fh, ok, release, err := c.s.beginFileRequest(ctx, uri, source.UnknownKind)
   234  			defer release()
   235  			if !ok {
   236  				return err
   237  			}
   238  			if err := c.s.runGoModUpdateCommands(ctx, snapshot, fh.URI(), func(invoke func(...string) (*bytes.Buffer, error)) error {
   239  				_, err := invoke("mod", "tidy")
   240  				return err
   241  			}); err != nil {
   242  				return err
   243  			}
   244  		}
   245  		return nil
   246  	})
   247  }
   248  
   249  func (c *commandHandler) Vendor(ctx context.Context, args command.URIArg) error {
   250  	return c.run(ctx, commandConfig{
   251  		requireSave: true,
   252  		progress:    "Running go mod vendor",
   253  		forURI:      args.URI,
   254  	}, func(ctx context.Context, deps commandDeps) error {
   255  		_, err := deps.snapshot.RunGoCommandDirect(ctx, source.Normal|source.AllowNetwork, &gocommand.Invocation{
   256  			Verb:       "mod",
   257  			Args:       []string{"vendor"},
   258  			WorkingDir: filepath.Dir(args.URI.SpanURI().Filename()),
   259  		})
   260  		return err
   261  	})
   262  }
   263  
   264  func (c *commandHandler) RemoveDependency(ctx context.Context, args command.RemoveDependencyArgs) error {
   265  	return c.run(ctx, commandConfig{
   266  		progress: "Removing dependency",
   267  		forURI:   args.URI,
   268  	}, func(ctx context.Context, deps commandDeps) error {
   269  		// If the module is tidied apart from the one unused diagnostic, we can
   270  		// run `go get module@none`, and then run `go mod tidy`. Otherwise, we
   271  		// must make textual edits.
   272  		// TODO(rstambler): In Go 1.17+, we will be able to use the go command
   273  		// without checking if the module is tidy.
   274  		if args.OnlyDiagnostic {
   275  			return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI.SpanURI(), func(invoke func(...string) (*bytes.Buffer, error)) error {
   276  				if err := runGoGetModule(invoke, false, []string{args.ModulePath + "@none"}); err != nil {
   277  					return err
   278  				}
   279  				_, err := invoke("mod", "tidy")
   280  				return err
   281  			})
   282  		}
   283  		pm, err := deps.snapshot.ParseMod(ctx, deps.fh)
   284  		if err != nil {
   285  			return err
   286  		}
   287  		edits, err := dropDependency(deps.snapshot, pm, args.ModulePath)
   288  		if err != nil {
   289  			return err
   290  		}
   291  		response, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
   292  			Edit: protocol.WorkspaceEdit{
   293  				DocumentChanges: []protocol.TextDocumentEdit{{
   294  					TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
   295  						Version: deps.fh.Version(),
   296  						TextDocumentIdentifier: protocol.TextDocumentIdentifier{
   297  							URI: protocol.URIFromSpanURI(deps.fh.URI()),
   298  						},
   299  					},
   300  					Edits: edits,
   301  				}},
   302  			},
   303  		})
   304  		if err != nil {
   305  			return err
   306  		}
   307  		if !response.Applied {
   308  			return fmt.Errorf("edits not applied because of %s", response.FailureReason)
   309  		}
   310  		return nil
   311  	})
   312  }
   313  
   314  // dropDependency returns the edits to remove the given require from the go.mod
   315  // file.
   316  func dropDependency(snapshot source.Snapshot, pm *source.ParsedModule, modulePath string) ([]protocol.TextEdit, error) {
   317  	// We need a private copy of the parsed go.mod file, since we're going to
   318  	// modify it.
   319  	copied, err := modfile.Parse("", pm.Mapper.Content, nil)
   320  	if err != nil {
   321  		return nil, err
   322  	}
   323  	if err := copied.DropRequire(modulePath); err != nil {
   324  		return nil, err
   325  	}
   326  	copied.Cleanup()
   327  	newContent, err := copied.Format()
   328  	if err != nil {
   329  		return nil, err
   330  	}
   331  	// Calculate the edits to be made due to the change.
   332  	diff, err := snapshot.View().Options().ComputeEdits(pm.URI, string(pm.Mapper.Content), string(newContent))
   333  	if err != nil {
   334  		return nil, err
   335  	}
   336  	return source.ToProtocolEdits(pm.Mapper, diff)
   337  }
   338  
   339  func (c *commandHandler) Test(ctx context.Context, uri protocol.DocumentURI, tests, benchmarks []string) error {
   340  	return c.RunTests(ctx, command.RunTestsArgs{
   341  		URI:        uri,
   342  		Tests:      tests,
   343  		Benchmarks: benchmarks,
   344  	})
   345  }
   346  
   347  func (c *commandHandler) RunTests(ctx context.Context, args command.RunTestsArgs) error {
   348  	return c.run(ctx, commandConfig{
   349  		async:       true,
   350  		progress:    "Running go test",
   351  		requireSave: true,
   352  		forURI:      args.URI,
   353  	}, func(ctx context.Context, deps commandDeps) error {
   354  		if err := c.runTests(ctx, deps.snapshot, deps.work, args.URI, args.Tests, args.Benchmarks); err != nil {
   355  			return errors.Errorf("running tests failed: %w", err)
   356  		}
   357  		return nil
   358  	})
   359  }
   360  
   361  func (c *commandHandler) runTests(ctx context.Context, snapshot source.Snapshot, work *progress.WorkDone, uri protocol.DocumentURI, tests, benchmarks []string) error {
   362  	// TODO: fix the error reporting when this runs async.
   363  	pkgs, err := snapshot.PackagesForFile(ctx, uri.SpanURI(), source.TypecheckWorkspace, false)
   364  	if err != nil {
   365  		return err
   366  	}
   367  	if len(pkgs) == 0 {
   368  		return fmt.Errorf("package could not be found for file: %s", uri.SpanURI().Filename())
   369  	}
   370  	pkgPath := pkgs[0].ForTest()
   371  
   372  	// create output
   373  	buf := &bytes.Buffer{}
   374  	ew := progress.NewEventWriter(ctx, "test")
   375  	out := io.MultiWriter(ew, progress.NewWorkDoneWriter(work), buf)
   376  
   377  	// Run `go test -run Func` on each test.
   378  	var failedTests int
   379  	for _, funcName := range tests {
   380  		inv := &gocommand.Invocation{
   381  			Verb:       "test",
   382  			Args:       []string{pkgPath, "-v", "-count=1", "-run", fmt.Sprintf("^%s$", funcName)},
   383  			WorkingDir: filepath.Dir(uri.SpanURI().Filename()),
   384  		}
   385  		if err := snapshot.RunGoCommandPiped(ctx, source.Normal, inv, out, out); err != nil {
   386  			if errors.Is(err, context.Canceled) {
   387  				return err
   388  			}
   389  			failedTests++
   390  		}
   391  	}
   392  
   393  	// Run `go test -run=^$ -bench Func` on each test.
   394  	var failedBenchmarks int
   395  	for _, funcName := range benchmarks {
   396  		inv := &gocommand.Invocation{
   397  			Verb:       "test",
   398  			Args:       []string{pkgPath, "-v", "-run=^$", "-bench", fmt.Sprintf("^%s$", funcName)},
   399  			WorkingDir: filepath.Dir(uri.SpanURI().Filename()),
   400  		}
   401  		if err := snapshot.RunGoCommandPiped(ctx, source.Normal, inv, out, out); err != nil {
   402  			if errors.Is(err, context.Canceled) {
   403  				return err
   404  			}
   405  			failedBenchmarks++
   406  		}
   407  	}
   408  
   409  	var title string
   410  	if len(tests) > 0 && len(benchmarks) > 0 {
   411  		title = "tests and benchmarks"
   412  	} else if len(tests) > 0 {
   413  		title = "tests"
   414  	} else if len(benchmarks) > 0 {
   415  		title = "benchmarks"
   416  	} else {
   417  		return errors.New("No functions were provided")
   418  	}
   419  	message := fmt.Sprintf("all %s passed", title)
   420  	if failedTests > 0 && failedBenchmarks > 0 {
   421  		message = fmt.Sprintf("%d / %d tests failed and %d / %d benchmarks failed", failedTests, len(tests), failedBenchmarks, len(benchmarks))
   422  	} else if failedTests > 0 {
   423  		message = fmt.Sprintf("%d / %d tests failed", failedTests, len(tests))
   424  	} else if failedBenchmarks > 0 {
   425  		message = fmt.Sprintf("%d / %d benchmarks failed", failedBenchmarks, len(benchmarks))
   426  	}
   427  	if failedTests > 0 || failedBenchmarks > 0 {
   428  		message += "\n" + buf.String()
   429  	}
   430  
   431  	return c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
   432  		Type:    protocol.Info,
   433  		Message: message,
   434  	})
   435  }
   436  
   437  func (c *commandHandler) Generate(ctx context.Context, args command.GenerateArgs) error {
   438  	title := "Running go generate ."
   439  	if args.Recursive {
   440  		title = "Running go generate ./..."
   441  	}
   442  	return c.run(ctx, commandConfig{
   443  		requireSave: true,
   444  		progress:    title,
   445  		forURI:      args.Dir,
   446  	}, func(ctx context.Context, deps commandDeps) error {
   447  		er := progress.NewEventWriter(ctx, "generate")
   448  
   449  		pattern := "."
   450  		if args.Recursive {
   451  			pattern = "./..."
   452  		}
   453  		inv := &gocommand.Invocation{
   454  			Verb:       "generate",
   455  			Args:       []string{"-x", pattern},
   456  			WorkingDir: args.Dir.SpanURI().Filename(),
   457  		}
   458  		stderr := io.MultiWriter(er, progress.NewWorkDoneWriter(deps.work))
   459  		if err := deps.snapshot.RunGoCommandPiped(ctx, source.Normal, inv, er, stderr); err != nil {
   460  			return err
   461  		}
   462  		return nil
   463  	})
   464  }
   465  
   466  func (c *commandHandler) GoGetPackage(ctx context.Context, args command.GoGetPackageArgs) error {
   467  	return c.run(ctx, commandConfig{
   468  		forURI:   args.URI,
   469  		progress: "Running go get",
   470  	}, func(ctx context.Context, deps commandDeps) error {
   471  		// Run on a throwaway go.mod, otherwise it'll write to the real one.
   472  		stdout, err := deps.snapshot.RunGoCommandDirect(ctx, source.WriteTemporaryModFile|source.AllowNetwork, &gocommand.Invocation{
   473  			Verb:       "list",
   474  			Args:       []string{"-f", "{{.Module.Path}}@{{.Module.Version}}", args.Pkg},
   475  			WorkingDir: filepath.Dir(args.URI.SpanURI().Filename()),
   476  		})
   477  		if err != nil {
   478  			return err
   479  		}
   480  		ver := strings.TrimSpace(stdout.String())
   481  		return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI.SpanURI(), func(invoke func(...string) (*bytes.Buffer, error)) error {
   482  			if args.AddRequire {
   483  				if err := addModuleRequire(invoke, []string{ver}); err != nil {
   484  					return err
   485  				}
   486  			}
   487  			_, err := invoke(append([]string{"get", "-d"}, args.Pkg)...)
   488  			return err
   489  		})
   490  	})
   491  }
   492  
   493  func (s *Server) runGoModUpdateCommands(ctx context.Context, snapshot source.Snapshot, uri span.URI, run func(invoke func(...string) (*bytes.Buffer, error)) error) error {
   494  	tmpModfile, newModBytes, newSumBytes, err := snapshot.RunGoCommands(ctx, true, filepath.Dir(uri.Filename()), run)
   495  	if err != nil {
   496  		return err
   497  	}
   498  	if !tmpModfile {
   499  		return nil
   500  	}
   501  	modURI := snapshot.GoModForFile(uri)
   502  	sumURI := span.URIFromPath(strings.TrimSuffix(modURI.Filename(), ".mod") + ".sum")
   503  	modEdits, err := applyFileEdits(ctx, snapshot, modURI, newModBytes)
   504  	if err != nil {
   505  		return err
   506  	}
   507  	sumEdits, err := applyFileEdits(ctx, snapshot, sumURI, newSumBytes)
   508  	if err != nil {
   509  		return err
   510  	}
   511  	changes := append(sumEdits, modEdits...)
   512  	if len(changes) == 0 {
   513  		return nil
   514  	}
   515  	response, err := s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
   516  		Edit: protocol.WorkspaceEdit{
   517  			DocumentChanges: changes,
   518  		},
   519  	})
   520  	if err != nil {
   521  		return err
   522  	}
   523  	if !response.Applied {
   524  		return fmt.Errorf("edits not applied because of %s", response.FailureReason)
   525  	}
   526  	return nil
   527  }
   528  
   529  func applyFileEdits(ctx context.Context, snapshot source.Snapshot, uri span.URI, newContent []byte) ([]protocol.TextDocumentEdit, error) {
   530  	fh, err := snapshot.GetVersionedFile(ctx, uri)
   531  	if err != nil {
   532  		return nil, err
   533  	}
   534  	oldContent, err := fh.Read()
   535  	if err != nil && !os.IsNotExist(err) {
   536  		return nil, err
   537  	}
   538  	if bytes.Equal(oldContent, newContent) {
   539  		return nil, nil
   540  	}
   541  
   542  	// Sending a workspace edit to a closed file causes VS Code to open the
   543  	// file and leave it unsaved. We would rather apply the changes directly,
   544  	// especially to go.sum, which should be mostly invisible to the user.
   545  	if !snapshot.IsOpen(uri) {
   546  		err := ioutil.WriteFile(uri.Filename(), newContent, 0666)
   547  		return nil, err
   548  	}
   549  
   550  	m := &protocol.ColumnMapper{
   551  		URI:       fh.URI(),
   552  		Converter: span.NewContentConverter(fh.URI().Filename(), oldContent),
   553  		Content:   oldContent,
   554  	}
   555  	diff, err := snapshot.View().Options().ComputeEdits(uri, string(oldContent), string(newContent))
   556  	if err != nil {
   557  		return nil, err
   558  	}
   559  	edits, err := source.ToProtocolEdits(m, diff)
   560  	if err != nil {
   561  		return nil, err
   562  	}
   563  	return []protocol.TextDocumentEdit{{
   564  		TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
   565  			Version: fh.Version(),
   566  			TextDocumentIdentifier: protocol.TextDocumentIdentifier{
   567  				URI: protocol.URIFromSpanURI(uri),
   568  			},
   569  		},
   570  		Edits: edits,
   571  	}}, nil
   572  }
   573  
   574  func runGoGetModule(invoke func(...string) (*bytes.Buffer, error), addRequire bool, args []string) error {
   575  	if addRequire {
   576  		if err := addModuleRequire(invoke, args); err != nil {
   577  			return err
   578  		}
   579  	}
   580  	_, err := invoke(append([]string{"get", "-d"}, args...)...)
   581  	return err
   582  }
   583  
   584  func addModuleRequire(invoke func(...string) (*bytes.Buffer, error), args []string) error {
   585  	// Using go get to create a new dependency results in an
   586  	// `// indirect` comment we may not want. The only way to avoid it
   587  	// is to add the require as direct first. Then we can use go get to
   588  	// update go.sum and tidy up.
   589  	_, err := invoke(append([]string{"mod", "edit", "-require"}, args...)...)
   590  	return err
   591  }
   592  
   593  func (s *Server) getUpgrades(ctx context.Context, snapshot source.Snapshot, uri span.URI, modules []string) (map[string]string, error) {
   594  	stdout, err := snapshot.RunGoCommandDirect(ctx, source.Normal|source.AllowNetwork, &gocommand.Invocation{
   595  		Verb:       "list",
   596  		Args:       append([]string{"-m", "-u", "-json"}, modules...),
   597  		WorkingDir: filepath.Dir(uri.Filename()),
   598  		ModFlag:    "readonly",
   599  	})
   600  	if err != nil {
   601  		return nil, err
   602  	}
   603  
   604  	upgrades := map[string]string{}
   605  	for dec := json.NewDecoder(stdout); dec.More(); {
   606  		mod := &gocommand.ModuleJSON{}
   607  		if err := dec.Decode(mod); err != nil {
   608  			return nil, err
   609  		}
   610  		if mod.Update == nil {
   611  			continue
   612  		}
   613  		upgrades[mod.Path] = mod.Update.Version
   614  	}
   615  	return upgrades, nil
   616  }
   617  
   618  func (c *commandHandler) GCDetails(ctx context.Context, uri protocol.DocumentURI) error {
   619  	return c.ToggleGCDetails(ctx, command.URIArg{URI: uri})
   620  }
   621  
   622  func (c *commandHandler) ToggleGCDetails(ctx context.Context, args command.URIArg) error {
   623  	return c.run(ctx, commandConfig{
   624  		requireSave: true,
   625  		progress:    "Toggling GC Details",
   626  		forURI:      args.URI,
   627  	}, func(ctx context.Context, deps commandDeps) error {
   628  		pkg, err := deps.snapshot.PackageForFile(ctx, deps.fh.URI(), source.TypecheckWorkspace, source.NarrowestPackage)
   629  		if err != nil {
   630  			return err
   631  		}
   632  		c.s.gcOptimizationDetailsMu.Lock()
   633  		if _, ok := c.s.gcOptimizationDetails[pkg.ID()]; ok {
   634  			delete(c.s.gcOptimizationDetails, pkg.ID())
   635  			c.s.clearDiagnosticSource(gcDetailsSource)
   636  		} else {
   637  			c.s.gcOptimizationDetails[pkg.ID()] = struct{}{}
   638  		}
   639  		c.s.gcOptimizationDetailsMu.Unlock()
   640  		c.s.diagnoseSnapshot(deps.snapshot, nil, false)
   641  		return nil
   642  	})
   643  }
   644  
   645  func (c *commandHandler) GenerateGoplsMod(ctx context.Context, args command.URIArg) error {
   646  	// TODO: go back to using URI
   647  	return c.run(ctx, commandConfig{
   648  		requireSave: true,
   649  		progress:    "Generating gopls.mod",
   650  	}, func(ctx context.Context, deps commandDeps) error {
   651  		views := c.s.session.Views()
   652  		if len(views) != 1 {
   653  			return fmt.Errorf("cannot resolve view: have %d views", len(views))
   654  		}
   655  		v := views[0]
   656  		snapshot, release := v.Snapshot(ctx)
   657  		defer release()
   658  		modFile, err := snapshot.BuildGoplsMod(ctx)
   659  		if err != nil {
   660  			return errors.Errorf("getting workspace mod file: %w", err)
   661  		}
   662  		content, err := modFile.Format()
   663  		if err != nil {
   664  			return errors.Errorf("formatting mod file: %w", err)
   665  		}
   666  		filename := filepath.Join(snapshot.View().Folder().Filename(), "gopls.mod")
   667  		if err := ioutil.WriteFile(filename, content, 0644); err != nil {
   668  			return errors.Errorf("writing mod file: %w", err)
   669  		}
   670  		return nil
   671  	})
   672  }
   673  
   674  func (c *commandHandler) ListKnownPackages(ctx context.Context, args command.URIArg) (command.ListKnownPackagesResult, error) {
   675  	var result command.ListKnownPackagesResult
   676  	err := c.run(ctx, commandConfig{
   677  		progress: "Listing packages",
   678  		forURI:   args.URI,
   679  	}, func(ctx context.Context, deps commandDeps) error {
   680  		var err error
   681  		result.Packages, err = source.KnownPackages(ctx, deps.snapshot, deps.fh)
   682  		return err
   683  	})
   684  	return result, err
   685  }
   686  
   687  func (c *commandHandler) ListImports(ctx context.Context, args command.URIArg) (command.ListImportsResult, error) {
   688  	var result command.ListImportsResult
   689  	err := c.run(ctx, commandConfig{
   690  		forURI: args.URI,
   691  	}, func(ctx context.Context, deps commandDeps) error {
   692  		pkg, err := deps.snapshot.PackageForFile(ctx, args.URI.SpanURI(), source.TypecheckWorkspace, source.NarrowestPackage)
   693  		if err != nil {
   694  			return err
   695  		}
   696  		pgf, err := pkg.File(args.URI.SpanURI())
   697  		if err != nil {
   698  			return err
   699  		}
   700  		for _, group := range astutil.Imports(deps.snapshot.FileSet(), pgf.File) {
   701  			for _, imp := range group {
   702  				if imp.Path == nil {
   703  					continue
   704  				}
   705  				var name string
   706  				if imp.Name != nil {
   707  					name = imp.Name.Name
   708  				}
   709  				result.Imports = append(result.Imports, command.FileImport{
   710  					Path: source.ImportPath(imp),
   711  					Name: name,
   712  				})
   713  			}
   714  		}
   715  		for _, imp := range pkg.Imports() {
   716  			result.PackageImports = append(result.PackageImports, command.PackageImport{
   717  				Path: imp.PkgPath(), // This might be the vendored path under GOPATH vendoring, in which case it's a bug.
   718  			})
   719  		}
   720  		sort.Slice(result.PackageImports, func(i, j int) bool {
   721  			return result.PackageImports[i].Path < result.PackageImports[j].Path
   722  		})
   723  		return nil
   724  	})
   725  	return result, err
   726  }
   727  
   728  func (c *commandHandler) AddImport(ctx context.Context, args command.AddImportArgs) error {
   729  	return c.run(ctx, commandConfig{
   730  		progress: "Adding import",
   731  		forURI:   args.URI,
   732  	}, func(ctx context.Context, deps commandDeps) error {
   733  		edits, err := source.AddImport(ctx, deps.snapshot, deps.fh, args.ImportPath)
   734  		if err != nil {
   735  			return fmt.Errorf("could not add import: %v", err)
   736  		}
   737  		if _, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
   738  			Edit: protocol.WorkspaceEdit{
   739  				DocumentChanges: documentChanges(deps.fh, edits),
   740  			},
   741  		}); err != nil {
   742  			return fmt.Errorf("could not apply import edits: %v", err)
   743  		}
   744  		return nil
   745  	})
   746  }
   747  
   748  func (c *commandHandler) StartDebugging(ctx context.Context, args command.DebuggingArgs) (result command.DebuggingResult, _ error) {
   749  	addr := args.Addr
   750  	if addr == "" {
   751  		addr = "localhost:0"
   752  	}
   753  	di := debug.GetInstance(ctx)
   754  	if di == nil {
   755  		return result, errors.New("internal error: server has no debugging instance")
   756  	}
   757  	listenedAddr, err := di.Serve(ctx, addr)
   758  	if err != nil {
   759  		return result, errors.Errorf("starting debug server: %w", err)
   760  	}
   761  	result.URLs = []string{"http://" + listenedAddr}
   762  	return result, nil
   763  }