golang.org/x/tools/gopls@v0.15.3/internal/server/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 server
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"os"
    15  	"path/filepath"
    16  	"regexp"
    17  	"runtime"
    18  	"runtime/pprof"
    19  	"sort"
    20  	"strings"
    21  	"sync"
    22  
    23  	"golang.org/x/mod/modfile"
    24  	"golang.org/x/tools/go/ast/astutil"
    25  	"golang.org/x/tools/gopls/internal/cache"
    26  	"golang.org/x/tools/gopls/internal/cache/metadata"
    27  	"golang.org/x/tools/gopls/internal/cache/parsego"
    28  	"golang.org/x/tools/gopls/internal/debug"
    29  	"golang.org/x/tools/gopls/internal/file"
    30  	"golang.org/x/tools/gopls/internal/golang"
    31  	"golang.org/x/tools/gopls/internal/progress"
    32  	"golang.org/x/tools/gopls/internal/protocol"
    33  	"golang.org/x/tools/gopls/internal/protocol/command"
    34  	"golang.org/x/tools/gopls/internal/settings"
    35  	"golang.org/x/tools/gopls/internal/telemetry"
    36  	"golang.org/x/tools/gopls/internal/util/bug"
    37  	"golang.org/x/tools/gopls/internal/vulncheck"
    38  	"golang.org/x/tools/gopls/internal/vulncheck/scan"
    39  	"golang.org/x/tools/internal/diff"
    40  	"golang.org/x/tools/internal/event"
    41  	"golang.org/x/tools/internal/gocommand"
    42  	"golang.org/x/tools/internal/tokeninternal"
    43  	"golang.org/x/tools/internal/xcontext"
    44  )
    45  
    46  func (s *server) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) {
    47  	ctx, done := event.Start(ctx, "lsp.Server.executeCommand")
    48  	defer done()
    49  
    50  	var found bool
    51  	for _, name := range s.Options().SupportedCommands {
    52  		if name == params.Command {
    53  			found = true
    54  			break
    55  		}
    56  	}
    57  	if !found {
    58  		return nil, fmt.Errorf("%s is not a supported command", params.Command)
    59  	}
    60  
    61  	handler := &commandHandler{
    62  		s:      s,
    63  		params: params,
    64  	}
    65  	return command.Dispatch(ctx, params, handler)
    66  }
    67  
    68  type commandHandler struct {
    69  	s      *server
    70  	params *protocol.ExecuteCommandParams
    71  }
    72  
    73  func (h *commandHandler) MaybePromptForTelemetry(ctx context.Context) error {
    74  	go h.s.maybePromptForTelemetry(ctx, true)
    75  	return nil
    76  }
    77  
    78  func (*commandHandler) AddTelemetryCounters(_ context.Context, args command.AddTelemetryCountersArgs) error {
    79  	if len(args.Names) != len(args.Values) {
    80  		return fmt.Errorf("Names and Values must have the same length")
    81  	}
    82  	// invalid counter update requests will be silently dropped. (no audience)
    83  	telemetry.AddForwardedCounters(args.Names, args.Values)
    84  	return nil
    85  }
    86  
    87  // commandConfig configures common command set-up and execution.
    88  type commandConfig struct {
    89  	// TODO(adonovan): whether a command is synchronous or
    90  	// asynchronous is part of the server interface contract, not
    91  	// a mere implementation detail of the handler.
    92  	// Export a (command.Command).IsAsync() property so that
    93  	// clients can tell. (The tricky part is ensuring the handler
    94  	// remains consistent with the command.Command metadata, as at
    95  	// the point were we read the 'async' field below, we no
    96  	// longer know that command.Command.)
    97  
    98  	async       bool                 // whether to run the command asynchronously. Async commands can only return errors.
    99  	requireSave bool                 // whether all files must be saved for the command to work
   100  	progress    string               // title to use for progress reporting. If empty, no progress will be reported.
   101  	forView     string               // view to resolve to a snapshot; incompatible with forURI
   102  	forURI      protocol.DocumentURI // URI to resolve to a snapshot. If unset, snapshot will be nil.
   103  }
   104  
   105  // commandDeps is evaluated from a commandConfig. Note that not all fields may
   106  // be populated, depending on which configuration is set. See comments in-line
   107  // for details.
   108  type commandDeps struct {
   109  	snapshot *cache.Snapshot    // present if cfg.forURI was set
   110  	fh       file.Handle        // present if cfg.forURI was set
   111  	work     *progress.WorkDone // present cfg.progress was set
   112  }
   113  
   114  type commandFunc func(context.Context, commandDeps) error
   115  
   116  // These strings are reported as the final WorkDoneProgressEnd message
   117  // for each workspace/executeCommand request.
   118  const (
   119  	CommandCanceled  = "canceled"
   120  	CommandFailed    = "failed"
   121  	CommandCompleted = "completed"
   122  )
   123  
   124  // run performs command setup for command execution, and invokes the given run
   125  // function. If cfg.async is set, run executes the given func in a separate
   126  // goroutine, and returns as soon as setup is complete and the goroutine is
   127  // scheduled.
   128  //
   129  // Invariant: if the resulting error is non-nil, the given run func will
   130  // (eventually) be executed exactly once.
   131  func (c *commandHandler) run(ctx context.Context, cfg commandConfig, run commandFunc) (err error) {
   132  	if cfg.requireSave {
   133  		var unsaved []string
   134  		for _, overlay := range c.s.session.Overlays() {
   135  			if !overlay.SameContentsOnDisk() {
   136  				unsaved = append(unsaved, overlay.URI().Path())
   137  			}
   138  		}
   139  		if len(unsaved) > 0 {
   140  			return fmt.Errorf("All files must be saved first (unsaved: %v).", unsaved)
   141  		}
   142  	}
   143  	var deps commandDeps
   144  	var release func()
   145  	if cfg.forURI != "" && cfg.forView != "" {
   146  		return bug.Errorf("internal error: forURI=%q, forView=%q", cfg.forURI, cfg.forView)
   147  	}
   148  	if cfg.forURI != "" {
   149  		deps.fh, deps.snapshot, release, err = c.s.fileOf(ctx, cfg.forURI)
   150  		if err != nil {
   151  			return err
   152  		}
   153  
   154  	} else if cfg.forView != "" {
   155  		view, err := c.s.session.View(cfg.forView)
   156  		if err != nil {
   157  			return err
   158  		}
   159  		deps.snapshot, release, err = view.Snapshot()
   160  		if err != nil {
   161  			return err
   162  		}
   163  
   164  	} else {
   165  		release = func() {}
   166  	}
   167  	// Inv: release() must be called exactly once after this point.
   168  	// In the async case, runcmd may outlive run().
   169  
   170  	ctx, cancel := context.WithCancel(xcontext.Detach(ctx))
   171  	if cfg.progress != "" {
   172  		deps.work = c.s.progress.Start(ctx, cfg.progress, "Running...", c.params.WorkDoneToken, cancel)
   173  	}
   174  	runcmd := func() error {
   175  		defer release()
   176  		defer cancel()
   177  		err := run(ctx, deps)
   178  		if deps.work != nil {
   179  			switch {
   180  			case errors.Is(err, context.Canceled):
   181  				deps.work.End(ctx, CommandCanceled)
   182  			case err != nil:
   183  				event.Error(ctx, "command error", err)
   184  				deps.work.End(ctx, CommandFailed)
   185  			default:
   186  				deps.work.End(ctx, CommandCompleted)
   187  			}
   188  		}
   189  		return err
   190  	}
   191  	if cfg.async {
   192  		go func() {
   193  			if err := runcmd(); err != nil {
   194  				showMessage(ctx, c.s.client, protocol.Error, err.Error())
   195  			}
   196  		}()
   197  		return nil
   198  	}
   199  	return runcmd()
   200  }
   201  
   202  func (c *commandHandler) ApplyFix(ctx context.Context, args command.ApplyFixArgs) (*protocol.WorkspaceEdit, error) {
   203  	var result *protocol.WorkspaceEdit
   204  	err := c.run(ctx, commandConfig{
   205  		// Note: no progress here. Applying fixes should be quick.
   206  		forURI: args.URI,
   207  	}, func(ctx context.Context, deps commandDeps) error {
   208  		edits, err := golang.ApplyFix(ctx, args.Fix, deps.snapshot, deps.fh, args.Range)
   209  		if err != nil {
   210  			return err
   211  		}
   212  		changes := []protocol.DocumentChanges{} // must be a slice
   213  		for _, edit := range edits {
   214  			edit := edit
   215  			changes = append(changes, protocol.DocumentChanges{
   216  				TextDocumentEdit: &edit,
   217  			})
   218  		}
   219  		edit := protocol.WorkspaceEdit{
   220  			DocumentChanges: changes,
   221  		}
   222  		if args.ResolveEdits {
   223  			result = &edit
   224  			return nil
   225  		}
   226  		r, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
   227  			Edit: edit,
   228  		})
   229  		if err != nil {
   230  			return err
   231  		}
   232  		if !r.Applied {
   233  			return errors.New(r.FailureReason)
   234  		}
   235  		return nil
   236  	})
   237  	return result, err
   238  }
   239  
   240  func (c *commandHandler) RegenerateCgo(ctx context.Context, args command.URIArg) error {
   241  	return c.run(ctx, commandConfig{
   242  		progress: "Regenerating Cgo",
   243  	}, func(ctx context.Context, _ commandDeps) error {
   244  		return c.modifyState(ctx, FromRegenerateCgo, func() (*cache.Snapshot, func(), error) {
   245  			// Resetting the view causes cgo to be regenerated via `go list`.
   246  			v, err := c.s.session.ResetView(ctx, args.URI)
   247  			if err != nil {
   248  				return nil, nil, err
   249  			}
   250  			return v.Snapshot()
   251  		})
   252  	})
   253  }
   254  
   255  // modifyState performs an operation that modifies the snapshot state.
   256  //
   257  // It causes a snapshot diagnosis for the provided ModificationSource.
   258  func (c *commandHandler) modifyState(ctx context.Context, source ModificationSource, work func() (*cache.Snapshot, func(), error)) error {
   259  	var wg sync.WaitGroup // tracks work done on behalf of this function, incl. diagnostics
   260  	wg.Add(1)
   261  	defer wg.Done()
   262  
   263  	// Track progress on this operation for testing.
   264  	if c.s.Options().VerboseWorkDoneProgress {
   265  		work := c.s.progress.Start(ctx, DiagnosticWorkTitle(source), "Calculating file diagnostics...", nil, nil)
   266  		go func() {
   267  			wg.Wait()
   268  			work.End(ctx, "Done.")
   269  		}()
   270  	}
   271  	snapshot, release, err := work()
   272  	if err != nil {
   273  		return err
   274  	}
   275  	wg.Add(1)
   276  	go func() {
   277  		c.s.diagnoseSnapshot(snapshot, nil, 0)
   278  		release()
   279  		wg.Done()
   280  	}()
   281  	return nil
   282  }
   283  
   284  func (c *commandHandler) CheckUpgrades(ctx context.Context, args command.CheckUpgradesArgs) error {
   285  	return c.run(ctx, commandConfig{
   286  		forURI:   args.URI,
   287  		progress: "Checking for upgrades",
   288  	}, func(ctx context.Context, deps commandDeps) error {
   289  		return c.modifyState(ctx, FromCheckUpgrades, func() (*cache.Snapshot, func(), error) {
   290  			upgrades, err := c.s.getUpgrades(ctx, deps.snapshot, args.URI, args.Modules)
   291  			if err != nil {
   292  				return nil, nil, err
   293  			}
   294  			return c.s.session.InvalidateView(ctx, deps.snapshot.View(), cache.StateChange{
   295  				ModuleUpgrades: map[protocol.DocumentURI]map[string]string{args.URI: upgrades},
   296  			})
   297  		})
   298  	})
   299  }
   300  
   301  func (c *commandHandler) AddDependency(ctx context.Context, args command.DependencyArgs) error {
   302  	return c.GoGetModule(ctx, args)
   303  }
   304  
   305  func (c *commandHandler) UpgradeDependency(ctx context.Context, args command.DependencyArgs) error {
   306  	return c.GoGetModule(ctx, args)
   307  }
   308  
   309  func (c *commandHandler) ResetGoModDiagnostics(ctx context.Context, args command.ResetGoModDiagnosticsArgs) error {
   310  	return c.run(ctx, commandConfig{
   311  		forURI: args.URI,
   312  	}, func(ctx context.Context, deps commandDeps) error {
   313  		return c.modifyState(ctx, FromResetGoModDiagnostics, func() (*cache.Snapshot, func(), error) {
   314  			return c.s.session.InvalidateView(ctx, deps.snapshot.View(), cache.StateChange{
   315  				ModuleUpgrades: map[protocol.DocumentURI]map[string]string{
   316  					deps.fh.URI(): nil,
   317  				},
   318  				Vulns: map[protocol.DocumentURI]*vulncheck.Result{
   319  					deps.fh.URI(): nil,
   320  				},
   321  			})
   322  		})
   323  	})
   324  }
   325  
   326  func (c *commandHandler) GoGetModule(ctx context.Context, args command.DependencyArgs) error {
   327  	return c.run(ctx, commandConfig{
   328  		progress: "Running go get",
   329  		forURI:   args.URI,
   330  	}, func(ctx context.Context, deps commandDeps) error {
   331  		return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI, func(invoke func(...string) (*bytes.Buffer, error)) error {
   332  			return runGoGetModule(invoke, args.AddRequire, args.GoCmdArgs)
   333  		})
   334  	})
   335  }
   336  
   337  // TODO(rFindley): UpdateGoSum, Tidy, and Vendor could probably all be one command.
   338  func (c *commandHandler) UpdateGoSum(ctx context.Context, args command.URIArgs) error {
   339  	return c.run(ctx, commandConfig{
   340  		progress: "Updating go.sum",
   341  	}, func(ctx context.Context, _ commandDeps) error {
   342  		for _, uri := range args.URIs {
   343  			fh, snapshot, release, err := c.s.fileOf(ctx, uri)
   344  			if err != nil {
   345  				return err
   346  			}
   347  			defer release()
   348  			if err := c.s.runGoModUpdateCommands(ctx, snapshot, fh.URI(), func(invoke func(...string) (*bytes.Buffer, error)) error {
   349  				_, err := invoke("list", "all")
   350  				return err
   351  			}); err != nil {
   352  				return err
   353  			}
   354  		}
   355  		return nil
   356  	})
   357  }
   358  
   359  func (c *commandHandler) Tidy(ctx context.Context, args command.URIArgs) error {
   360  	return c.run(ctx, commandConfig{
   361  		requireSave: true,
   362  		progress:    "Running go mod tidy",
   363  	}, func(ctx context.Context, _ commandDeps) error {
   364  		for _, uri := range args.URIs {
   365  			fh, snapshot, release, err := c.s.fileOf(ctx, uri)
   366  			if err != nil {
   367  				return err
   368  			}
   369  			defer release()
   370  			if err := c.s.runGoModUpdateCommands(ctx, snapshot, fh.URI(), func(invoke func(...string) (*bytes.Buffer, error)) error {
   371  				_, err := invoke("mod", "tidy")
   372  				return err
   373  			}); err != nil {
   374  				return err
   375  			}
   376  		}
   377  		return nil
   378  	})
   379  }
   380  
   381  func (c *commandHandler) Vendor(ctx context.Context, args command.URIArg) error {
   382  	return c.run(ctx, commandConfig{
   383  		requireSave: true,
   384  		progress:    "Running go mod vendor",
   385  		forURI:      args.URI,
   386  	}, func(ctx context.Context, deps commandDeps) error {
   387  		// Use RunGoCommandPiped here so that we don't compete with any other go
   388  		// command invocations. go mod vendor deletes modules.txt before recreating
   389  		// it, and therefore can run into file locking issues on Windows if that
   390  		// file is in use by another process, such as go list.
   391  		//
   392  		// If golang/go#44119 is resolved, go mod vendor will instead modify
   393  		// modules.txt in-place. In that case we could theoretically allow this
   394  		// command to run concurrently.
   395  		stderr := new(bytes.Buffer)
   396  		err := deps.snapshot.RunGoCommandPiped(ctx, cache.Normal|cache.AllowNetwork, &gocommand.Invocation{
   397  			Verb:       "mod",
   398  			Args:       []string{"vendor"},
   399  			WorkingDir: filepath.Dir(args.URI.Path()),
   400  		}, &bytes.Buffer{}, stderr)
   401  		if err != nil {
   402  			return fmt.Errorf("running go mod vendor failed: %v\nstderr:\n%s", err, stderr.String())
   403  		}
   404  		return nil
   405  	})
   406  }
   407  
   408  func (c *commandHandler) EditGoDirective(ctx context.Context, args command.EditGoDirectiveArgs) error {
   409  	return c.run(ctx, commandConfig{
   410  		requireSave: true, // if go.mod isn't saved it could cause a problem
   411  		forURI:      args.URI,
   412  	}, func(ctx context.Context, _ commandDeps) error {
   413  		fh, snapshot, release, err := c.s.fileOf(ctx, args.URI)
   414  		if err != nil {
   415  			return err
   416  		}
   417  		defer release()
   418  		if err := c.s.runGoModUpdateCommands(ctx, snapshot, fh.URI(), func(invoke func(...string) (*bytes.Buffer, error)) error {
   419  			_, err := invoke("mod", "edit", "-go", args.Version)
   420  			return err
   421  		}); err != nil {
   422  			return err
   423  		}
   424  		return nil
   425  	})
   426  }
   427  
   428  func (c *commandHandler) RemoveDependency(ctx context.Context, args command.RemoveDependencyArgs) error {
   429  	return c.run(ctx, commandConfig{
   430  		progress: "Removing dependency",
   431  		forURI:   args.URI,
   432  	}, func(ctx context.Context, deps commandDeps) error {
   433  		// See the documentation for OnlyDiagnostic.
   434  		//
   435  		// TODO(rfindley): In Go 1.17+, we will be able to use the go command
   436  		// without checking if the module is tidy.
   437  		if args.OnlyDiagnostic {
   438  			return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI, func(invoke func(...string) (*bytes.Buffer, error)) error {
   439  				if err := runGoGetModule(invoke, false, []string{args.ModulePath + "@none"}); err != nil {
   440  					return err
   441  				}
   442  				_, err := invoke("mod", "tidy")
   443  				return err
   444  			})
   445  		}
   446  		pm, err := deps.snapshot.ParseMod(ctx, deps.fh)
   447  		if err != nil {
   448  			return err
   449  		}
   450  		edits, err := dropDependency(pm, args.ModulePath)
   451  		if err != nil {
   452  			return err
   453  		}
   454  		response, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
   455  			Edit: protocol.WorkspaceEdit{
   456  				DocumentChanges: documentChanges(deps.fh, edits),
   457  			},
   458  		})
   459  		if err != nil {
   460  			return err
   461  		}
   462  		if !response.Applied {
   463  			return fmt.Errorf("edits not applied because of %s", response.FailureReason)
   464  		}
   465  		return nil
   466  	})
   467  }
   468  
   469  // dropDependency returns the edits to remove the given require from the go.mod
   470  // file.
   471  func dropDependency(pm *cache.ParsedModule, modulePath string) ([]protocol.TextEdit, error) {
   472  	// We need a private copy of the parsed go.mod file, since we're going to
   473  	// modify it.
   474  	copied, err := modfile.Parse("", pm.Mapper.Content, nil)
   475  	if err != nil {
   476  		return nil, err
   477  	}
   478  	if err := copied.DropRequire(modulePath); err != nil {
   479  		return nil, err
   480  	}
   481  	copied.Cleanup()
   482  	newContent, err := copied.Format()
   483  	if err != nil {
   484  		return nil, err
   485  	}
   486  	// Calculate the edits to be made due to the change.
   487  	diff := diff.Bytes(pm.Mapper.Content, newContent)
   488  	return protocol.EditsFromDiffEdits(pm.Mapper, diff)
   489  }
   490  
   491  func (c *commandHandler) Test(ctx context.Context, uri protocol.DocumentURI, tests, benchmarks []string) error {
   492  	return c.RunTests(ctx, command.RunTestsArgs{
   493  		URI:        uri,
   494  		Tests:      tests,
   495  		Benchmarks: benchmarks,
   496  	})
   497  }
   498  
   499  func (c *commandHandler) RunTests(ctx context.Context, args command.RunTestsArgs) error {
   500  	return c.run(ctx, commandConfig{
   501  		async:       true,
   502  		progress:    "Running go test",
   503  		requireSave: true,
   504  		forURI:      args.URI,
   505  	}, func(ctx context.Context, deps commandDeps) error {
   506  		return c.runTests(ctx, deps.snapshot, deps.work, args.URI, args.Tests, args.Benchmarks)
   507  	})
   508  }
   509  
   510  func (c *commandHandler) runTests(ctx context.Context, snapshot *cache.Snapshot, work *progress.WorkDone, uri protocol.DocumentURI, tests, benchmarks []string) error {
   511  	// TODO: fix the error reporting when this runs async.
   512  	meta, err := golang.NarrowestMetadataForFile(ctx, snapshot, uri)
   513  	if err != nil {
   514  		return err
   515  	}
   516  	pkgPath := string(meta.ForTest)
   517  
   518  	// create output
   519  	buf := &bytes.Buffer{}
   520  	ew := progress.NewEventWriter(ctx, "test")
   521  	out := io.MultiWriter(ew, progress.NewWorkDoneWriter(ctx, work), buf)
   522  
   523  	// Run `go test -run Func` on each test.
   524  	var failedTests int
   525  	for _, funcName := range tests {
   526  		inv := &gocommand.Invocation{
   527  			Verb:       "test",
   528  			Args:       []string{pkgPath, "-v", "-count=1", fmt.Sprintf("-run=^%s$", regexp.QuoteMeta(funcName))},
   529  			WorkingDir: filepath.Dir(uri.Path()),
   530  		}
   531  		if err := snapshot.RunGoCommandPiped(ctx, cache.Normal, inv, out, out); err != nil {
   532  			if errors.Is(err, context.Canceled) {
   533  				return err
   534  			}
   535  			failedTests++
   536  		}
   537  	}
   538  
   539  	// Run `go test -run=^$ -bench Func` on each test.
   540  	var failedBenchmarks int
   541  	for _, funcName := range benchmarks {
   542  		inv := &gocommand.Invocation{
   543  			Verb:       "test",
   544  			Args:       []string{pkgPath, "-v", "-run=^$", fmt.Sprintf("-bench=^%s$", regexp.QuoteMeta(funcName))},
   545  			WorkingDir: filepath.Dir(uri.Path()),
   546  		}
   547  		if err := snapshot.RunGoCommandPiped(ctx, cache.Normal, inv, out, out); err != nil {
   548  			if errors.Is(err, context.Canceled) {
   549  				return err
   550  			}
   551  			failedBenchmarks++
   552  		}
   553  	}
   554  
   555  	var title string
   556  	if len(tests) > 0 && len(benchmarks) > 0 {
   557  		title = "tests and benchmarks"
   558  	} else if len(tests) > 0 {
   559  		title = "tests"
   560  	} else if len(benchmarks) > 0 {
   561  		title = "benchmarks"
   562  	} else {
   563  		return errors.New("No functions were provided")
   564  	}
   565  	message := fmt.Sprintf("all %s passed", title)
   566  	if failedTests > 0 && failedBenchmarks > 0 {
   567  		message = fmt.Sprintf("%d / %d tests failed and %d / %d benchmarks failed", failedTests, len(tests), failedBenchmarks, len(benchmarks))
   568  	} else if failedTests > 0 {
   569  		message = fmt.Sprintf("%d / %d tests failed", failedTests, len(tests))
   570  	} else if failedBenchmarks > 0 {
   571  		message = fmt.Sprintf("%d / %d benchmarks failed", failedBenchmarks, len(benchmarks))
   572  	}
   573  	if failedTests > 0 || failedBenchmarks > 0 {
   574  		message += "\n" + buf.String()
   575  	}
   576  
   577  	showMessage(ctx, c.s.client, protocol.Info, message)
   578  
   579  	if failedTests > 0 || failedBenchmarks > 0 {
   580  		return errors.New("gopls.test command failed")
   581  	}
   582  	return nil
   583  }
   584  
   585  func (c *commandHandler) Generate(ctx context.Context, args command.GenerateArgs) error {
   586  	title := "Running go generate ."
   587  	if args.Recursive {
   588  		title = "Running go generate ./..."
   589  	}
   590  	return c.run(ctx, commandConfig{
   591  		requireSave: true,
   592  		progress:    title,
   593  		forURI:      args.Dir,
   594  	}, func(ctx context.Context, deps commandDeps) error {
   595  		er := progress.NewEventWriter(ctx, "generate")
   596  
   597  		pattern := "."
   598  		if args.Recursive {
   599  			pattern = "./..."
   600  		}
   601  		inv := &gocommand.Invocation{
   602  			Verb:       "generate",
   603  			Args:       []string{"-x", pattern},
   604  			WorkingDir: args.Dir.Path(),
   605  		}
   606  		stderr := io.MultiWriter(er, progress.NewWorkDoneWriter(ctx, deps.work))
   607  		if err := deps.snapshot.RunGoCommandPiped(ctx, cache.AllowNetwork, inv, er, stderr); err != nil {
   608  			return err
   609  		}
   610  		return nil
   611  	})
   612  }
   613  
   614  func (c *commandHandler) GoGetPackage(ctx context.Context, args command.GoGetPackageArgs) error {
   615  	return c.run(ctx, commandConfig{
   616  		forURI:   args.URI,
   617  		progress: "Running go get",
   618  	}, func(ctx context.Context, deps commandDeps) error {
   619  		// Run on a throwaway go.mod, otherwise it'll write to the real one.
   620  		stdout, err := deps.snapshot.RunGoCommandDirect(ctx, cache.WriteTemporaryModFile|cache.AllowNetwork, &gocommand.Invocation{
   621  			Verb:       "list",
   622  			Args:       []string{"-f", "{{.Module.Path}}@{{.Module.Version}}", args.Pkg},
   623  			WorkingDir: filepath.Dir(args.URI.Path()),
   624  		})
   625  		if err != nil {
   626  			return err
   627  		}
   628  		ver := strings.TrimSpace(stdout.String())
   629  		return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI, func(invoke func(...string) (*bytes.Buffer, error)) error {
   630  			if args.AddRequire {
   631  				if err := addModuleRequire(invoke, []string{ver}); err != nil {
   632  					return err
   633  				}
   634  			}
   635  			_, err := invoke(append([]string{"get", "-d"}, args.Pkg)...)
   636  			return err
   637  		})
   638  	})
   639  }
   640  
   641  func (s *server) runGoModUpdateCommands(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, run func(invoke func(...string) (*bytes.Buffer, error)) error) error {
   642  	newModBytes, newSumBytes, err := snapshot.RunGoModUpdateCommands(ctx, filepath.Dir(uri.Path()), run)
   643  	if err != nil {
   644  		return err
   645  	}
   646  	modURI := snapshot.GoModForFile(uri)
   647  	sumURI := protocol.URIFromPath(strings.TrimSuffix(modURI.Path(), ".mod") + ".sum")
   648  	modEdits, err := collectFileEdits(ctx, snapshot, modURI, newModBytes)
   649  	if err != nil {
   650  		return err
   651  	}
   652  	sumEdits, err := collectFileEdits(ctx, snapshot, sumURI, newSumBytes)
   653  	if err != nil {
   654  		return err
   655  	}
   656  	return applyFileEdits(ctx, s.client, append(sumEdits, modEdits...))
   657  }
   658  
   659  // collectFileEdits collects any file edits required to transform the snapshot
   660  // file specified by uri to the provided new content.
   661  //
   662  // If the file is not open, collectFileEdits simply writes the new content to
   663  // disk.
   664  //
   665  // TODO(rfindley): fix this API asymmetry. It should be up to the caller to
   666  // write the file or apply the edits.
   667  func collectFileEdits(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, newContent []byte) ([]protocol.TextDocumentEdit, error) {
   668  	fh, err := snapshot.ReadFile(ctx, uri)
   669  	if err != nil {
   670  		return nil, err
   671  	}
   672  	oldContent, err := fh.Content()
   673  	if err != nil && !os.IsNotExist(err) {
   674  		return nil, err
   675  	}
   676  
   677  	if bytes.Equal(oldContent, newContent) {
   678  		return nil, nil
   679  	}
   680  
   681  	// Sending a workspace edit to a closed file causes VS Code to open the
   682  	// file and leave it unsaved. We would rather apply the changes directly,
   683  	// especially to go.sum, which should be mostly invisible to the user.
   684  	if !snapshot.IsOpen(uri) {
   685  		err := os.WriteFile(uri.Path(), newContent, 0666)
   686  		return nil, err
   687  	}
   688  
   689  	m := protocol.NewMapper(fh.URI(), oldContent)
   690  	diff := diff.Bytes(oldContent, newContent)
   691  	edits, err := protocol.EditsFromDiffEdits(m, diff)
   692  	if err != nil {
   693  		return nil, err
   694  	}
   695  	return []protocol.TextDocumentEdit{{
   696  		TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{
   697  			Version: fh.Version(),
   698  			TextDocumentIdentifier: protocol.TextDocumentIdentifier{
   699  				URI: uri,
   700  			},
   701  		},
   702  		Edits: protocol.AsAnnotatedTextEdits(edits),
   703  	}}, nil
   704  }
   705  
   706  func applyFileEdits(ctx context.Context, cli protocol.Client, edits []protocol.TextDocumentEdit) error {
   707  	if len(edits) == 0 {
   708  		return nil
   709  	}
   710  	response, err := cli.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
   711  		Edit: protocol.WorkspaceEdit{
   712  			DocumentChanges: protocol.TextDocumentEditsToDocumentChanges(edits),
   713  		},
   714  	})
   715  	if err != nil {
   716  		return err
   717  	}
   718  	if !response.Applied {
   719  		return fmt.Errorf("edits not applied because of %s", response.FailureReason)
   720  	}
   721  	return nil
   722  }
   723  
   724  func runGoGetModule(invoke func(...string) (*bytes.Buffer, error), addRequire bool, args []string) error {
   725  	if addRequire {
   726  		if err := addModuleRequire(invoke, args); err != nil {
   727  			return err
   728  		}
   729  	}
   730  	_, err := invoke(append([]string{"get", "-d"}, args...)...)
   731  	return err
   732  }
   733  
   734  func addModuleRequire(invoke func(...string) (*bytes.Buffer, error), args []string) error {
   735  	// Using go get to create a new dependency results in an
   736  	// `// indirect` comment we may not want. The only way to avoid it
   737  	// is to add the require as direct first. Then we can use go get to
   738  	// update go.sum and tidy up.
   739  	_, err := invoke(append([]string{"mod", "edit", "-require"}, args...)...)
   740  	return err
   741  }
   742  
   743  // TODO(rfindley): inline.
   744  func (s *server) getUpgrades(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, modules []string) (map[string]string, error) {
   745  	stdout, err := snapshot.RunGoCommandDirect(ctx, cache.Normal|cache.AllowNetwork, &gocommand.Invocation{
   746  		Verb:       "list",
   747  		Args:       append([]string{"-m", "-u", "-json"}, modules...),
   748  		ModFlag:    "readonly", // necessary when vendor is present (golang/go#66055)
   749  		WorkingDir: filepath.Dir(uri.Path()),
   750  	})
   751  	if err != nil {
   752  		return nil, err
   753  	}
   754  
   755  	upgrades := map[string]string{}
   756  	for dec := json.NewDecoder(stdout); dec.More(); {
   757  		mod := &gocommand.ModuleJSON{}
   758  		if err := dec.Decode(mod); err != nil {
   759  			return nil, err
   760  		}
   761  		if mod.Update == nil {
   762  			continue
   763  		}
   764  		upgrades[mod.Path] = mod.Update.Version
   765  	}
   766  	return upgrades, nil
   767  }
   768  
   769  func (c *commandHandler) GCDetails(ctx context.Context, uri protocol.DocumentURI) error {
   770  	return c.ToggleGCDetails(ctx, command.URIArg{URI: uri})
   771  }
   772  
   773  func (c *commandHandler) ToggleGCDetails(ctx context.Context, args command.URIArg) error {
   774  	return c.run(ctx, commandConfig{
   775  		requireSave: true,
   776  		progress:    "Toggling GC Details",
   777  		forURI:      args.URI,
   778  	}, func(ctx context.Context, deps commandDeps) error {
   779  		return c.modifyState(ctx, FromToggleGCDetails, func() (*cache.Snapshot, func(), error) {
   780  			meta, err := golang.NarrowestMetadataForFile(ctx, deps.snapshot, deps.fh.URI())
   781  			if err != nil {
   782  				return nil, nil, err
   783  			}
   784  			wantDetails := !deps.snapshot.WantGCDetails(meta.ID) // toggle the gc details state
   785  			return c.s.session.InvalidateView(ctx, deps.snapshot.View(), cache.StateChange{
   786  				GCDetails: map[metadata.PackageID]bool{
   787  					meta.ID: wantDetails,
   788  				},
   789  			})
   790  		})
   791  	})
   792  }
   793  
   794  func (c *commandHandler) ListKnownPackages(ctx context.Context, args command.URIArg) (command.ListKnownPackagesResult, error) {
   795  	var result command.ListKnownPackagesResult
   796  	err := c.run(ctx, commandConfig{
   797  		progress: "Listing packages",
   798  		forURI:   args.URI,
   799  	}, func(ctx context.Context, deps commandDeps) error {
   800  		pkgs, err := golang.KnownPackagePaths(ctx, deps.snapshot, deps.fh)
   801  		for _, pkg := range pkgs {
   802  			result.Packages = append(result.Packages, string(pkg))
   803  		}
   804  		return err
   805  	})
   806  	return result, err
   807  }
   808  
   809  func (c *commandHandler) ListImports(ctx context.Context, args command.URIArg) (command.ListImportsResult, error) {
   810  	var result command.ListImportsResult
   811  	err := c.run(ctx, commandConfig{
   812  		forURI: args.URI,
   813  	}, func(ctx context.Context, deps commandDeps) error {
   814  		fh, err := deps.snapshot.ReadFile(ctx, args.URI)
   815  		if err != nil {
   816  			return err
   817  		}
   818  		pgf, err := deps.snapshot.ParseGo(ctx, fh, parsego.ParseHeader)
   819  		if err != nil {
   820  			return err
   821  		}
   822  		fset := tokeninternal.FileSetFor(pgf.Tok)
   823  		for _, group := range astutil.Imports(fset, pgf.File) {
   824  			for _, imp := range group {
   825  				if imp.Path == nil {
   826  					continue
   827  				}
   828  				var name string
   829  				if imp.Name != nil {
   830  					name = imp.Name.Name
   831  				}
   832  				result.Imports = append(result.Imports, command.FileImport{
   833  					Path: string(metadata.UnquoteImportPath(imp)),
   834  					Name: name,
   835  				})
   836  			}
   837  		}
   838  		meta, err := golang.NarrowestMetadataForFile(ctx, deps.snapshot, args.URI)
   839  		if err != nil {
   840  			return err // e.g. cancelled
   841  		}
   842  		for pkgPath := range meta.DepsByPkgPath {
   843  			result.PackageImports = append(result.PackageImports,
   844  				command.PackageImport{Path: string(pkgPath)})
   845  		}
   846  		sort.Slice(result.PackageImports, func(i, j int) bool {
   847  			return result.PackageImports[i].Path < result.PackageImports[j].Path
   848  		})
   849  		return nil
   850  	})
   851  	return result, err
   852  }
   853  
   854  func (c *commandHandler) AddImport(ctx context.Context, args command.AddImportArgs) error {
   855  	return c.run(ctx, commandConfig{
   856  		progress: "Adding import",
   857  		forURI:   args.URI,
   858  	}, func(ctx context.Context, deps commandDeps) error {
   859  		edits, err := golang.AddImport(ctx, deps.snapshot, deps.fh, args.ImportPath)
   860  		if err != nil {
   861  			return fmt.Errorf("could not add import: %v", err)
   862  		}
   863  		if _, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
   864  			Edit: protocol.WorkspaceEdit{
   865  				DocumentChanges: documentChanges(deps.fh, edits),
   866  			},
   867  		}); err != nil {
   868  			return fmt.Errorf("could not apply import edits: %v", err)
   869  		}
   870  		return nil
   871  	})
   872  }
   873  
   874  func (c *commandHandler) StartDebugging(ctx context.Context, args command.DebuggingArgs) (result command.DebuggingResult, _ error) {
   875  	addr := args.Addr
   876  	if addr == "" {
   877  		addr = "localhost:0"
   878  	}
   879  	di := debug.GetInstance(ctx)
   880  	if di == nil {
   881  		return result, errors.New("internal error: server has no debugging instance")
   882  	}
   883  	listenedAddr, err := di.Serve(ctx, addr)
   884  	if err != nil {
   885  		return result, fmt.Errorf("starting debug server: %w", err)
   886  	}
   887  	result.URLs = []string{"http://" + listenedAddr}
   888  	openClientBrowser(ctx, c.s.client, result.URLs[0])
   889  	return result, nil
   890  }
   891  
   892  func (c *commandHandler) StartProfile(ctx context.Context, args command.StartProfileArgs) (result command.StartProfileResult, _ error) {
   893  	file, err := os.CreateTemp("", "gopls-profile-*")
   894  	if err != nil {
   895  		return result, fmt.Errorf("creating temp profile file: %v", err)
   896  	}
   897  
   898  	c.s.ongoingProfileMu.Lock()
   899  	defer c.s.ongoingProfileMu.Unlock()
   900  
   901  	if c.s.ongoingProfile != nil {
   902  		file.Close() // ignore error
   903  		return result, fmt.Errorf("profile already started (for %q)", c.s.ongoingProfile.Name())
   904  	}
   905  
   906  	if err := pprof.StartCPUProfile(file); err != nil {
   907  		file.Close() // ignore error
   908  		return result, fmt.Errorf("starting profile: %v", err)
   909  	}
   910  
   911  	c.s.ongoingProfile = file
   912  	return result, nil
   913  }
   914  
   915  func (c *commandHandler) StopProfile(ctx context.Context, args command.StopProfileArgs) (result command.StopProfileResult, _ error) {
   916  	c.s.ongoingProfileMu.Lock()
   917  	defer c.s.ongoingProfileMu.Unlock()
   918  
   919  	prof := c.s.ongoingProfile
   920  	c.s.ongoingProfile = nil
   921  
   922  	if prof == nil {
   923  		return result, fmt.Errorf("no ongoing profile")
   924  	}
   925  
   926  	pprof.StopCPUProfile()
   927  	if err := prof.Close(); err != nil {
   928  		return result, fmt.Errorf("closing profile file: %v", err)
   929  	}
   930  	result.File = prof.Name()
   931  	return result, nil
   932  }
   933  
   934  func (c *commandHandler) FetchVulncheckResult(ctx context.Context, arg command.URIArg) (map[protocol.DocumentURI]*vulncheck.Result, error) {
   935  	ret := map[protocol.DocumentURI]*vulncheck.Result{}
   936  	err := c.run(ctx, commandConfig{forURI: arg.URI}, func(ctx context.Context, deps commandDeps) error {
   937  		if deps.snapshot.Options().Vulncheck == settings.ModeVulncheckImports {
   938  			for _, modfile := range deps.snapshot.View().ModFiles() {
   939  				res, err := deps.snapshot.ModVuln(ctx, modfile)
   940  				if err != nil {
   941  					return err
   942  				}
   943  				ret[modfile] = res
   944  			}
   945  		}
   946  		// Overwrite if there is any govulncheck-based result.
   947  		for modfile, result := range deps.snapshot.Vulnerabilities() {
   948  			ret[modfile] = result
   949  		}
   950  		return nil
   951  	})
   952  	return ret, err
   953  }
   954  
   955  func (c *commandHandler) RunGovulncheck(ctx context.Context, args command.VulncheckArgs) (command.RunVulncheckResult, error) {
   956  	if args.URI == "" {
   957  		return command.RunVulncheckResult{}, errors.New("VulncheckArgs is missing URI field")
   958  	}
   959  
   960  	// Return the workdone token so that clients can identify when this
   961  	// vulncheck invocation is complete.
   962  	//
   963  	// Since the run function executes asynchronously, we use a channel to
   964  	// synchronize the start of the run and return the token.
   965  	tokenChan := make(chan protocol.ProgressToken, 1)
   966  	err := c.run(ctx, commandConfig{
   967  		async:       true, // need to be async to be cancellable
   968  		progress:    "govulncheck",
   969  		requireSave: true,
   970  		forURI:      args.URI,
   971  	}, func(ctx context.Context, deps commandDeps) error {
   972  		tokenChan <- deps.work.Token()
   973  
   974  		workDoneWriter := progress.NewWorkDoneWriter(ctx, deps.work)
   975  		dir := filepath.Dir(args.URI.Path())
   976  		pattern := args.Pattern
   977  
   978  		result, err := scan.RunGovulncheck(ctx, pattern, deps.snapshot, dir, workDoneWriter)
   979  		if err != nil {
   980  			return err
   981  		}
   982  
   983  		snapshot, release, err := c.s.session.InvalidateView(ctx, deps.snapshot.View(), cache.StateChange{
   984  			Vulns: map[protocol.DocumentURI]*vulncheck.Result{args.URI: result},
   985  		})
   986  		if err != nil {
   987  			return err
   988  		}
   989  		defer release()
   990  		c.s.diagnoseSnapshot(snapshot, nil, 0)
   991  
   992  		affecting := make(map[string]bool, len(result.Entries))
   993  		for _, finding := range result.Findings {
   994  			if len(finding.Trace) > 1 { // at least 2 frames if callstack exists (vulnerability, entry)
   995  				affecting[finding.OSV] = true
   996  			}
   997  		}
   998  		if len(affecting) == 0 {
   999  			showMessage(ctx, c.s.client, protocol.Info, "No vulnerabilities found")
  1000  			return nil
  1001  		}
  1002  		affectingOSVs := make([]string, 0, len(affecting))
  1003  		for id := range affecting {
  1004  			affectingOSVs = append(affectingOSVs, id)
  1005  		}
  1006  		sort.Strings(affectingOSVs)
  1007  
  1008  		showMessage(ctx, c.s.client, protocol.Warning, fmt.Sprintf("Found %v", strings.Join(affectingOSVs, ", ")))
  1009  
  1010  		return nil
  1011  	})
  1012  	if err != nil {
  1013  		return command.RunVulncheckResult{}, err
  1014  	}
  1015  	select {
  1016  	case <-ctx.Done():
  1017  		return command.RunVulncheckResult{}, ctx.Err()
  1018  	case token := <-tokenChan:
  1019  		return command.RunVulncheckResult{Token: token}, nil
  1020  	}
  1021  }
  1022  
  1023  // MemStats implements the MemStats command. It returns an error as a
  1024  // future-proof API, but the resulting error is currently always nil.
  1025  func (c *commandHandler) MemStats(ctx context.Context) (command.MemStatsResult, error) {
  1026  	// GC a few times for stable results.
  1027  	runtime.GC()
  1028  	runtime.GC()
  1029  	runtime.GC()
  1030  	var m runtime.MemStats
  1031  	runtime.ReadMemStats(&m)
  1032  	return command.MemStatsResult{
  1033  		HeapAlloc:  m.HeapAlloc,
  1034  		HeapInUse:  m.HeapInuse,
  1035  		TotalAlloc: m.TotalAlloc,
  1036  	}, nil
  1037  }
  1038  
  1039  // WorkspaceStats implements the WorkspaceStats command, reporting information
  1040  // about the current state of the loaded workspace for the current session.
  1041  func (c *commandHandler) WorkspaceStats(ctx context.Context) (command.WorkspaceStatsResult, error) {
  1042  	var res command.WorkspaceStatsResult
  1043  	res.Files = c.s.session.Cache().FileStats()
  1044  
  1045  	for _, view := range c.s.session.Views() {
  1046  		vs, err := collectViewStats(ctx, view)
  1047  		if err != nil {
  1048  			return res, err
  1049  		}
  1050  		res.Views = append(res.Views, vs)
  1051  	}
  1052  	return res, nil
  1053  }
  1054  
  1055  func collectViewStats(ctx context.Context, view *cache.View) (command.ViewStats, error) {
  1056  	s, release, err := view.Snapshot()
  1057  	if err != nil {
  1058  		return command.ViewStats{}, err
  1059  	}
  1060  	defer release()
  1061  
  1062  	allMD, err := s.AllMetadata(ctx)
  1063  	if err != nil {
  1064  		return command.ViewStats{}, err
  1065  	}
  1066  	allPackages := collectPackageStats(allMD)
  1067  
  1068  	wsMD, err := s.WorkspaceMetadata(ctx)
  1069  	if err != nil {
  1070  		return command.ViewStats{}, err
  1071  	}
  1072  	workspacePackages := collectPackageStats(wsMD)
  1073  
  1074  	var ids []golang.PackageID
  1075  	for _, mp := range wsMD {
  1076  		ids = append(ids, mp.ID)
  1077  	}
  1078  
  1079  	diags, err := s.PackageDiagnostics(ctx, ids...)
  1080  	if err != nil {
  1081  		return command.ViewStats{}, err
  1082  	}
  1083  
  1084  	ndiags := 0
  1085  	for _, d := range diags {
  1086  		ndiags += len(d)
  1087  	}
  1088  
  1089  	return command.ViewStats{
  1090  		GoCommandVersion:  view.GoVersionString(),
  1091  		AllPackages:       allPackages,
  1092  		WorkspacePackages: workspacePackages,
  1093  		Diagnostics:       ndiags,
  1094  	}, nil
  1095  }
  1096  
  1097  func collectPackageStats(mps []*metadata.Package) command.PackageStats {
  1098  	var stats command.PackageStats
  1099  	stats.Packages = len(mps)
  1100  	modules := make(map[string]bool)
  1101  
  1102  	for _, mp := range mps {
  1103  		n := len(mp.CompiledGoFiles)
  1104  		stats.CompiledGoFiles += n
  1105  		if n > stats.LargestPackage {
  1106  			stats.LargestPackage = n
  1107  		}
  1108  		if mp.Module != nil {
  1109  			modules[mp.Module.Path] = true
  1110  		}
  1111  	}
  1112  	stats.Modules = len(modules)
  1113  
  1114  	return stats
  1115  }
  1116  
  1117  // RunGoWorkCommand invokes `go work <args>` with the provided arguments.
  1118  //
  1119  // args.InitFirst controls whether to first run `go work init`. This allows a
  1120  // single command to both create and recursively populate a go.work file -- as
  1121  // of writing there is no `go work init -r`.
  1122  //
  1123  // Some thought went into implementing this command. Unlike the go.mod commands
  1124  // above, this command simply invokes the go command and relies on the client
  1125  // to notify gopls of file changes via didChangeWatchedFile notifications.
  1126  // We could instead run these commands with GOWORK set to a temp file, but that
  1127  // poses the following problems:
  1128  //   - directory locations in the resulting temp go.work file will be computed
  1129  //     relative to the directory containing that go.work. If the go.work is in a
  1130  //     tempdir, the directories will need to be translated to/from that dir.
  1131  //   - it would be simpler to use a temp go.work file in the workspace
  1132  //     directory, or whichever directory contains the real go.work file, but
  1133  //     that sets a bad precedent of writing to a user-owned directory. We
  1134  //     shouldn't start doing that.
  1135  //   - Sending workspace edits to create a go.work file would require using
  1136  //     the CreateFile resource operation, which would need to be tested in every
  1137  //     client as we haven't used it before. We don't have time for that right
  1138  //     now.
  1139  //
  1140  // Therefore, we simply require that the current go.work file is saved (if it
  1141  // exists), and delegate to the go command.
  1142  func (c *commandHandler) RunGoWorkCommand(ctx context.Context, args command.RunGoWorkArgs) error {
  1143  	return c.run(ctx, commandConfig{
  1144  		progress: "Running go work command",
  1145  		forView:  args.ViewID,
  1146  	}, func(ctx context.Context, deps commandDeps) (runErr error) {
  1147  		snapshot := deps.snapshot
  1148  		view := snapshot.View()
  1149  		viewDir := snapshot.Folder().Path()
  1150  
  1151  		if view.Type() != cache.GoWorkView && view.GoWork() != "" {
  1152  			// If we are not using an existing go.work file, GOWORK must be explicitly off.
  1153  			// TODO(rfindley): what about GO111MODULE=off?
  1154  			return fmt.Errorf("cannot modify go.work files when GOWORK=off")
  1155  		}
  1156  
  1157  		var gowork string
  1158  		// If the user has explicitly set GOWORK=off, we should warn them
  1159  		// explicitly and avoid potentially misleading errors below.
  1160  		if view.GoWork() != "" {
  1161  			gowork = view.GoWork().Path()
  1162  			fh, err := snapshot.ReadFile(ctx, view.GoWork())
  1163  			if err != nil {
  1164  				return err // e.g. canceled
  1165  			}
  1166  			if !fh.SameContentsOnDisk() {
  1167  				return fmt.Errorf("must save workspace file %s before running go work commands", view.GoWork())
  1168  			}
  1169  		} else {
  1170  			if !args.InitFirst {
  1171  				// If go.work does not exist, we should have detected that and asked
  1172  				// for InitFirst.
  1173  				return bug.Errorf("internal error: cannot run go work command: required go.work file not found")
  1174  			}
  1175  			gowork = filepath.Join(viewDir, "go.work")
  1176  			if err := c.invokeGoWork(ctx, viewDir, gowork, []string{"init"}); err != nil {
  1177  				return fmt.Errorf("running `go work init`: %v", err)
  1178  			}
  1179  		}
  1180  
  1181  		return c.invokeGoWork(ctx, viewDir, gowork, args.Args)
  1182  	})
  1183  }
  1184  
  1185  func (c *commandHandler) invokeGoWork(ctx context.Context, viewDir, gowork string, args []string) error {
  1186  	inv := gocommand.Invocation{
  1187  		Verb:       "work",
  1188  		Args:       args,
  1189  		WorkingDir: viewDir,
  1190  		Env:        append(os.Environ(), fmt.Sprintf("GOWORK=%s", gowork)),
  1191  	}
  1192  	if _, err := c.s.session.GoCommandRunner().Run(ctx, inv); err != nil {
  1193  		return fmt.Errorf("running go work command: %v", err)
  1194  	}
  1195  	return nil
  1196  }
  1197  
  1198  // showMessage causes the client to show a progress or error message.
  1199  //
  1200  // It reports whether it succeeded. If it fails, it writes an error to
  1201  // the server log, so most callers can safely ignore the result.
  1202  func showMessage(ctx context.Context, cli protocol.Client, typ protocol.MessageType, message string) bool {
  1203  	err := cli.ShowMessage(ctx, &protocol.ShowMessageParams{
  1204  		Type:    typ,
  1205  		Message: message,
  1206  	})
  1207  	if err != nil {
  1208  		event.Error(ctx, "client.showMessage: %v", err)
  1209  		return false
  1210  	}
  1211  	return true
  1212  }
  1213  
  1214  // openClientBrowser causes the LSP client to open the specified URL
  1215  // in an external browser.
  1216  func openClientBrowser(ctx context.Context, cli protocol.Client, url protocol.URI) {
  1217  	showDocumentImpl(ctx, cli, url, nil)
  1218  }
  1219  
  1220  // openClientEditor causes the LSP client to open the specified document
  1221  // and select the indicated range.
  1222  func openClientEditor(ctx context.Context, cli protocol.Client, loc protocol.Location) {
  1223  	showDocumentImpl(ctx, cli, protocol.URI(loc.URI), &loc.Range)
  1224  }
  1225  
  1226  func showDocumentImpl(ctx context.Context, cli protocol.Client, url protocol.URI, rangeOpt *protocol.Range) {
  1227  	// In principle we shouldn't send a showDocument request to a
  1228  	// client that doesn't support it, as reported by
  1229  	// ShowDocumentClientCapabilities. But even clients that do
  1230  	// support it may defer the real work of opening the document
  1231  	// asynchronously, to avoid deadlocks due to rentrancy.
  1232  	//
  1233  	// For example: client sends request to server; server sends
  1234  	// showDocument to client; client opens editor; editor causes
  1235  	// new RPC to be sent to server, which is still busy with
  1236  	// previous request. (This happens in eglot.)
  1237  	//
  1238  	// So we can't rely on the success/failure information.
  1239  	// That's the reason this function doesn't return an error.
  1240  
  1241  	// "External" means run the system-wide handler (e.g. open(1)
  1242  	// on macOS or xdg-open(1) on Linux) for this URL, ignoring
  1243  	// TakeFocus and Selection. Note that this may still end up
  1244  	// opening the same editor (e.g. VSCode) for a file: URL.
  1245  	res, err := cli.ShowDocument(ctx, &protocol.ShowDocumentParams{
  1246  		URI:       url,
  1247  		External:  rangeOpt == nil,
  1248  		TakeFocus: true,
  1249  		Selection: rangeOpt, // optional
  1250  	})
  1251  	if err != nil {
  1252  		event.Error(ctx, "client.showDocument: %v", err)
  1253  	} else if res != nil && !res.Success {
  1254  		event.Log(ctx, fmt.Sprintf("client declined to open document %v", url))
  1255  	}
  1256  }
  1257  
  1258  func (c *commandHandler) ChangeSignature(ctx context.Context, args command.ChangeSignatureArgs) (*protocol.WorkspaceEdit, error) {
  1259  	var result *protocol.WorkspaceEdit
  1260  	err := c.run(ctx, commandConfig{
  1261  		forURI: args.RemoveParameter.URI,
  1262  	}, func(ctx context.Context, deps commandDeps) error {
  1263  		// For now, gopls only supports removing unused parameters.
  1264  		changes, err := golang.RemoveUnusedParameter(ctx, deps.fh, args.RemoveParameter.Range, deps.snapshot)
  1265  		if err != nil {
  1266  			return err
  1267  		}
  1268  		edit := protocol.WorkspaceEdit{
  1269  			DocumentChanges: changes,
  1270  		}
  1271  		if args.ResolveEdits {
  1272  			result = &edit
  1273  			return nil
  1274  		}
  1275  		r, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{
  1276  			Edit: edit,
  1277  		})
  1278  		if !r.Applied {
  1279  			return fmt.Errorf("failed to apply edits: %v", r.FailureReason)
  1280  		}
  1281  
  1282  		return nil
  1283  	})
  1284  	return result, err
  1285  }
  1286  
  1287  func (c *commandHandler) DiagnoseFiles(ctx context.Context, args command.DiagnoseFilesArgs) error {
  1288  	return c.run(ctx, commandConfig{
  1289  		progress: "Diagnose files",
  1290  	}, func(ctx context.Context, _ commandDeps) error {
  1291  
  1292  		// TODO(rfindley): even better would be textDocument/diagnostics (golang/go#60122).
  1293  		// Though note that implementing pull diagnostics may cause some servers to
  1294  		// request diagnostics in an ad-hoc manner, and break our intentional pacing.
  1295  
  1296  		ctx, done := event.Start(ctx, "lsp.server.DiagnoseFiles")
  1297  		defer done()
  1298  
  1299  		snapshots := make(map[*cache.Snapshot]bool)
  1300  		for _, uri := range args.Files {
  1301  			fh, snapshot, release, err := c.s.fileOf(ctx, uri)
  1302  			if err != nil {
  1303  				return err
  1304  			}
  1305  			if snapshots[snapshot] || snapshot.FileKind(fh) != file.Go {
  1306  				release()
  1307  				continue
  1308  			}
  1309  			defer release()
  1310  			snapshots[snapshot] = true
  1311  		}
  1312  
  1313  		var wg sync.WaitGroup
  1314  		for snapshot := range snapshots {
  1315  			snapshot := snapshot
  1316  			wg.Add(1)
  1317  			go func() {
  1318  				defer wg.Done()
  1319  				c.s.diagnoseSnapshot(snapshot, nil, 0)
  1320  			}()
  1321  		}
  1322  		wg.Wait()
  1323  
  1324  		return nil
  1325  	})
  1326  }
  1327  
  1328  func (c *commandHandler) Views(ctx context.Context) ([]command.View, error) {
  1329  	var summaries []command.View
  1330  	for _, view := range c.s.session.Views() {
  1331  		summaries = append(summaries, command.View{
  1332  			ID:         view.ID(),
  1333  			Type:       view.Type().String(),
  1334  			Root:       view.Root(),
  1335  			Folder:     view.Folder().Dir,
  1336  			EnvOverlay: view.EnvOverlay(),
  1337  		})
  1338  	}
  1339  	return summaries, nil
  1340  }