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