diff options
Diffstat (limited to 'internal/lsp/command.go')
-rw-r--r-- | internal/lsp/command.go | 819 |
1 files changed, 0 insertions, 819 deletions
diff --git a/internal/lsp/command.go b/internal/lsp/command.go deleted file mode 100644 index 088fa57d5..000000000 --- a/internal/lsp/command.go +++ /dev/null @@ -1,819 +0,0 @@ -// Copyright 2020 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package lsp - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "sort" - "strings" - - "golang.org/x/mod/modfile" - "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/packages" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/gocommand" - "golang.org/x/tools/internal/lsp/command" - "golang.org/x/tools/internal/lsp/debug" - "golang.org/x/tools/internal/lsp/progress" - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/lsp/source" - "golang.org/x/tools/internal/span" - "golang.org/x/tools/internal/xcontext" - errors "golang.org/x/xerrors" -) - -func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) { - var found bool - for _, name := range s.session.Options().SupportedCommands { - if name == params.Command { - found = true - break - } - } - if !found { - return nil, fmt.Errorf("%s is not a supported command", params.Command) - } - - handler := &commandHandler{ - s: s, - params: params, - } - return command.Dispatch(ctx, params, handler) -} - -type commandHandler struct { - s *Server - params *protocol.ExecuteCommandParams -} - -// commandConfig configures common command set-up and execution. -type commandConfig struct { - async bool // whether to run the command asynchronously. Async commands can only return errors. - requireSave bool // whether all files must be saved for the command to work - progress string // title to use for progress reporting. If empty, no progress will be reported. - forURI protocol.DocumentURI // URI to resolve to a snapshot. If unset, snapshot will be nil. -} - -// commandDeps is evaluated from a commandConfig. Note that not all fields may -// be populated, depending on which configuration is set. See comments in-line -// for details. -type commandDeps struct { - snapshot source.Snapshot // present if cfg.forURI was set - fh source.VersionedFileHandle // present if cfg.forURI was set - work *progress.WorkDone // present cfg.progress was set -} - -type commandFunc func(context.Context, commandDeps) error - -func (c *commandHandler) run(ctx context.Context, cfg commandConfig, run commandFunc) (err error) { - if cfg.requireSave { - var unsaved []string - for _, overlay := range c.s.session.Overlays() { - if !overlay.Saved() { - unsaved = append(unsaved, overlay.URI().Filename()) - } - } - if len(unsaved) > 0 { - return errors.Errorf("All files must be saved first (unsaved: %v).", unsaved) - } - } - var deps commandDeps - if cfg.forURI != "" { - var ok bool - var release func() - deps.snapshot, deps.fh, ok, release, err = c.s.beginFileRequest(ctx, cfg.forURI, source.UnknownKind) - defer release() - if !ok { - if err != nil { - return err - } - return fmt.Errorf("invalid file URL: %v", cfg.forURI) - } - } - ctx, cancel := context.WithCancel(xcontext.Detach(ctx)) - if cfg.progress != "" { - deps.work = c.s.progress.Start(ctx, cfg.progress, "Running...", c.params.WorkDoneToken, cancel) - } - runcmd := func() error { - defer cancel() - err := run(ctx, deps) - if deps.work != nil { - switch { - case errors.Is(err, context.Canceled): - deps.work.End("canceled") - case err != nil: - event.Error(ctx, "command error", err) - deps.work.End("failed") - default: - deps.work.End("completed") - } - } - return err - } - if cfg.async { - go func() { - if err := runcmd(); err != nil { - if showMessageErr := c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{ - Type: protocol.Error, - Message: err.Error(), - }); showMessageErr != nil { - event.Error(ctx, fmt.Sprintf("failed to show message: %q", err.Error()), showMessageErr) - } - } - }() - return nil - } - return runcmd() -} - -func (c *commandHandler) ApplyFix(ctx context.Context, args command.ApplyFixArgs) error { - return c.run(ctx, commandConfig{ - // Note: no progress here. Applying fixes should be quick. - forURI: args.URI, - }, func(ctx context.Context, deps commandDeps) error { - edits, err := source.ApplyFix(ctx, args.Fix, deps.snapshot, deps.fh, args.Range) - if err != nil { - return err - } - r, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{ - Edit: protocol.WorkspaceEdit{ - DocumentChanges: edits, - }, - }) - if err != nil { - return err - } - if !r.Applied { - return errors.New(r.FailureReason) - } - return nil - }) -} - -func (c *commandHandler) RegenerateCgo(ctx context.Context, args command.URIArg) error { - return c.run(ctx, commandConfig{ - progress: "Regenerating Cgo", - }, func(ctx context.Context, deps commandDeps) error { - mod := source.FileModification{ - URI: args.URI.SpanURI(), - Action: source.InvalidateMetadata, - } - return c.s.didModifyFiles(ctx, []source.FileModification{mod}, FromRegenerateCgo) - }) -} - -func (c *commandHandler) CheckUpgrades(ctx context.Context, args command.CheckUpgradesArgs) error { - return c.run(ctx, commandConfig{ - forURI: args.URI, - progress: "Checking for upgrades", - }, func(ctx context.Context, deps commandDeps) error { - upgrades, err := c.s.getUpgrades(ctx, deps.snapshot, args.URI.SpanURI(), args.Modules) - if err != nil { - return err - } - deps.snapshot.View().RegisterModuleUpgrades(upgrades) - // Re-diagnose the snapshot to publish the new module diagnostics. - c.s.diagnoseSnapshot(deps.snapshot, nil, false) - return nil - }) -} - -func (c *commandHandler) AddDependency(ctx context.Context, args command.DependencyArgs) error { - return c.GoGetModule(ctx, args) -} - -func (c *commandHandler) UpgradeDependency(ctx context.Context, args command.DependencyArgs) error { - return c.GoGetModule(ctx, args) -} - -func (c *commandHandler) GoGetModule(ctx context.Context, args command.DependencyArgs) error { - return c.run(ctx, commandConfig{ - progress: "Running go get", - forURI: args.URI, - }, func(ctx context.Context, deps commandDeps) error { - return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI.SpanURI(), func(invoke func(...string) (*bytes.Buffer, error)) error { - return runGoGetModule(invoke, args.AddRequire, args.GoCmdArgs) - }) - }) -} - -// TODO(rFindley): UpdateGoSum, Tidy, and Vendor could probably all be one command. -func (c *commandHandler) UpdateGoSum(ctx context.Context, args command.URIArgs) error { - return c.run(ctx, commandConfig{ - progress: "Updating go.sum", - }, func(ctx context.Context, deps commandDeps) error { - for _, uri := range args.URIs { - snapshot, fh, ok, release, err := c.s.beginFileRequest(ctx, uri, source.UnknownKind) - defer release() - if !ok { - return err - } - if err := c.s.runGoModUpdateCommands(ctx, snapshot, fh.URI(), func(invoke func(...string) (*bytes.Buffer, error)) error { - _, err := invoke("list", "all") - return err - }); err != nil { - return err - } - } - return nil - }) -} - -func (c *commandHandler) Tidy(ctx context.Context, args command.URIArgs) error { - return c.run(ctx, commandConfig{ - requireSave: true, - progress: "Running go mod tidy", - }, func(ctx context.Context, deps commandDeps) error { - for _, uri := range args.URIs { - snapshot, fh, ok, release, err := c.s.beginFileRequest(ctx, uri, source.UnknownKind) - defer release() - if !ok { - return err - } - if err := c.s.runGoModUpdateCommands(ctx, snapshot, fh.URI(), func(invoke func(...string) (*bytes.Buffer, error)) error { - _, err := invoke("mod", "tidy") - return err - }); err != nil { - return err - } - } - return nil - }) -} - -func (c *commandHandler) Vendor(ctx context.Context, args command.URIArg) error { - return c.run(ctx, commandConfig{ - requireSave: true, - progress: "Running go mod vendor", - forURI: args.URI, - }, func(ctx context.Context, deps commandDeps) error { - _, err := deps.snapshot.RunGoCommandDirect(ctx, source.Normal|source.AllowNetwork, &gocommand.Invocation{ - Verb: "mod", - Args: []string{"vendor"}, - WorkingDir: filepath.Dir(args.URI.SpanURI().Filename()), - }) - return err - }) -} - -func (c *commandHandler) EditGoDirective(ctx context.Context, args command.EditGoDirectiveArgs) error { - return c.run(ctx, commandConfig{ - requireSave: true, // if go.mod isn't saved it could cause a problem - forURI: args.URI, - }, func(ctx context.Context, deps commandDeps) error { - snapshot, fh, ok, release, err := c.s.beginFileRequest(ctx, args.URI, source.UnknownKind) - defer release() - if !ok { - return err - } - if err := c.s.runGoModUpdateCommands(ctx, snapshot, fh.URI(), func(invoke func(...string) (*bytes.Buffer, error)) error { - _, err := invoke("mod", "edit", "-go", args.Version) - return err - }); err != nil { - return err - } - return nil - }) -} - -func (c *commandHandler) RemoveDependency(ctx context.Context, args command.RemoveDependencyArgs) error { - return c.run(ctx, commandConfig{ - progress: "Removing dependency", - forURI: args.URI, - }, func(ctx context.Context, deps commandDeps) error { - // If the module is tidied apart from the one unused diagnostic, we can - // run `go get module@none`, and then run `go mod tidy`. Otherwise, we - // must make textual edits. - // TODO(rstambler): In Go 1.17+, we will be able to use the go command - // without checking if the module is tidy. - if args.OnlyDiagnostic { - return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI.SpanURI(), func(invoke func(...string) (*bytes.Buffer, error)) error { - if err := runGoGetModule(invoke, false, []string{args.ModulePath + "@none"}); err != nil { - return err - } - _, err := invoke("mod", "tidy") - return err - }) - } - pm, err := deps.snapshot.ParseMod(ctx, deps.fh) - if err != nil { - return err - } - edits, err := dropDependency(deps.snapshot, pm, args.ModulePath) - if err != nil { - return err - } - response, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{ - Edit: protocol.WorkspaceEdit{ - DocumentChanges: []protocol.TextDocumentEdit{{ - TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{ - Version: deps.fh.Version(), - TextDocumentIdentifier: protocol.TextDocumentIdentifier{ - URI: protocol.URIFromSpanURI(deps.fh.URI()), - }, - }, - Edits: edits, - }}, - }, - }) - if err != nil { - return err - } - if !response.Applied { - return fmt.Errorf("edits not applied because of %s", response.FailureReason) - } - return nil - }) -} - -// dropDependency returns the edits to remove the given require from the go.mod -// file. -func dropDependency(snapshot source.Snapshot, pm *source.ParsedModule, modulePath string) ([]protocol.TextEdit, error) { - // We need a private copy of the parsed go.mod file, since we're going to - // modify it. - copied, err := modfile.Parse("", pm.Mapper.Content, nil) - if err != nil { - return nil, err - } - if err := copied.DropRequire(modulePath); err != nil { - return nil, err - } - copied.Cleanup() - newContent, err := copied.Format() - if err != nil { - return nil, err - } - // Calculate the edits to be made due to the change. - diff, err := snapshot.View().Options().ComputeEdits(pm.URI, string(pm.Mapper.Content), string(newContent)) - if err != nil { - return nil, err - } - return source.ToProtocolEdits(pm.Mapper, diff) -} - -func (c *commandHandler) Test(ctx context.Context, uri protocol.DocumentURI, tests, benchmarks []string) error { - return c.RunTests(ctx, command.RunTestsArgs{ - URI: uri, - Tests: tests, - Benchmarks: benchmarks, - }) -} - -func (c *commandHandler) RunTests(ctx context.Context, args command.RunTestsArgs) error { - return c.run(ctx, commandConfig{ - async: true, - progress: "Running go test", - requireSave: true, - forURI: args.URI, - }, func(ctx context.Context, deps commandDeps) error { - if err := c.runTests(ctx, deps.snapshot, deps.work, args.URI, args.Tests, args.Benchmarks); err != nil { - return errors.Errorf("running tests failed: %w", err) - } - return nil - }) -} - -func (c *commandHandler) runTests(ctx context.Context, snapshot source.Snapshot, work *progress.WorkDone, uri protocol.DocumentURI, tests, benchmarks []string) error { - // TODO: fix the error reporting when this runs async. - pkgs, err := snapshot.PackagesForFile(ctx, uri.SpanURI(), source.TypecheckWorkspace, false) - if err != nil { - return err - } - if len(pkgs) == 0 { - return fmt.Errorf("package could not be found for file: %s", uri.SpanURI().Filename()) - } - pkgPath := pkgs[0].ForTest() - - // create output - buf := &bytes.Buffer{} - ew := progress.NewEventWriter(ctx, "test") - out := io.MultiWriter(ew, progress.NewWorkDoneWriter(work), buf) - - // Run `go test -run Func` on each test. - var failedTests int - for _, funcName := range tests { - inv := &gocommand.Invocation{ - Verb: "test", - Args: []string{pkgPath, "-v", "-count=1", "-run", fmt.Sprintf("^%s$", funcName)}, - WorkingDir: filepath.Dir(uri.SpanURI().Filename()), - } - if err := snapshot.RunGoCommandPiped(ctx, source.Normal, inv, out, out); err != nil { - if errors.Is(err, context.Canceled) { - return err - } - failedTests++ - } - } - - // Run `go test -run=^$ -bench Func` on each test. - var failedBenchmarks int - for _, funcName := range benchmarks { - inv := &gocommand.Invocation{ - Verb: "test", - Args: []string{pkgPath, "-v", "-run=^$", "-bench", fmt.Sprintf("^%s$", funcName)}, - WorkingDir: filepath.Dir(uri.SpanURI().Filename()), - } - if err := snapshot.RunGoCommandPiped(ctx, source.Normal, inv, out, out); err != nil { - if errors.Is(err, context.Canceled) { - return err - } - failedBenchmarks++ - } - } - - var title string - if len(tests) > 0 && len(benchmarks) > 0 { - title = "tests and benchmarks" - } else if len(tests) > 0 { - title = "tests" - } else if len(benchmarks) > 0 { - title = "benchmarks" - } else { - return errors.New("No functions were provided") - } - message := fmt.Sprintf("all %s passed", title) - if failedTests > 0 && failedBenchmarks > 0 { - message = fmt.Sprintf("%d / %d tests failed and %d / %d benchmarks failed", failedTests, len(tests), failedBenchmarks, len(benchmarks)) - } else if failedTests > 0 { - message = fmt.Sprintf("%d / %d tests failed", failedTests, len(tests)) - } else if failedBenchmarks > 0 { - message = fmt.Sprintf("%d / %d benchmarks failed", failedBenchmarks, len(benchmarks)) - } - if failedTests > 0 || failedBenchmarks > 0 { - message += "\n" + buf.String() - } - - return c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{ - Type: protocol.Info, - Message: message, - }) -} - -func (c *commandHandler) Generate(ctx context.Context, args command.GenerateArgs) error { - title := "Running go generate ." - if args.Recursive { - title = "Running go generate ./..." - } - return c.run(ctx, commandConfig{ - requireSave: true, - progress: title, - forURI: args.Dir, - }, func(ctx context.Context, deps commandDeps) error { - er := progress.NewEventWriter(ctx, "generate") - - pattern := "." - if args.Recursive { - pattern = "./..." - } - inv := &gocommand.Invocation{ - Verb: "generate", - Args: []string{"-x", pattern}, - WorkingDir: args.Dir.SpanURI().Filename(), - } - stderr := io.MultiWriter(er, progress.NewWorkDoneWriter(deps.work)) - if err := deps.snapshot.RunGoCommandPiped(ctx, source.Normal, inv, er, stderr); err != nil { - return err - } - return nil - }) -} - -func (c *commandHandler) GoGetPackage(ctx context.Context, args command.GoGetPackageArgs) error { - return c.run(ctx, commandConfig{ - forURI: args.URI, - progress: "Running go get", - }, func(ctx context.Context, deps commandDeps) error { - // Run on a throwaway go.mod, otherwise it'll write to the real one. - stdout, err := deps.snapshot.RunGoCommandDirect(ctx, source.WriteTemporaryModFile|source.AllowNetwork, &gocommand.Invocation{ - Verb: "list", - Args: []string{"-f", "{{.Module.Path}}@{{.Module.Version}}", args.Pkg}, - WorkingDir: filepath.Dir(args.URI.SpanURI().Filename()), - }) - if err != nil { - return err - } - ver := strings.TrimSpace(stdout.String()) - return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI.SpanURI(), func(invoke func(...string) (*bytes.Buffer, error)) error { - if args.AddRequire { - if err := addModuleRequire(invoke, []string{ver}); err != nil { - return err - } - } - _, err := invoke(append([]string{"get", "-d"}, args.Pkg)...) - return err - }) - }) -} - -func (s *Server) runGoModUpdateCommands(ctx context.Context, snapshot source.Snapshot, uri span.URI, run func(invoke func(...string) (*bytes.Buffer, error)) error) error { - tmpModfile, newModBytes, newSumBytes, err := snapshot.RunGoCommands(ctx, true, filepath.Dir(uri.Filename()), run) - if err != nil { - return err - } - if !tmpModfile { - return nil - } - modURI := snapshot.GoModForFile(uri) - sumURI := span.URIFromPath(strings.TrimSuffix(modURI.Filename(), ".mod") + ".sum") - modEdits, err := applyFileEdits(ctx, snapshot, modURI, newModBytes) - if err != nil { - return err - } - sumEdits, err := applyFileEdits(ctx, snapshot, sumURI, newSumBytes) - if err != nil { - return err - } - changes := append(sumEdits, modEdits...) - if len(changes) == 0 { - return nil - } - response, err := s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{ - Edit: protocol.WorkspaceEdit{ - DocumentChanges: changes, - }, - }) - if err != nil { - return err - } - if !response.Applied { - return fmt.Errorf("edits not applied because of %s", response.FailureReason) - } - return nil -} - -func applyFileEdits(ctx context.Context, snapshot source.Snapshot, uri span.URI, newContent []byte) ([]protocol.TextDocumentEdit, error) { - fh, err := snapshot.GetVersionedFile(ctx, uri) - if err != nil { - return nil, err - } - oldContent, err := fh.Read() - if err != nil && !os.IsNotExist(err) { - return nil, err - } - if bytes.Equal(oldContent, newContent) { - return nil, nil - } - - // Sending a workspace edit to a closed file causes VS Code to open the - // file and leave it unsaved. We would rather apply the changes directly, - // especially to go.sum, which should be mostly invisible to the user. - if !snapshot.IsOpen(uri) { - err := ioutil.WriteFile(uri.Filename(), newContent, 0666) - return nil, err - } - - m := &protocol.ColumnMapper{ - URI: fh.URI(), - Converter: span.NewContentConverter(fh.URI().Filename(), oldContent), - Content: oldContent, - } - diff, err := snapshot.View().Options().ComputeEdits(uri, string(oldContent), string(newContent)) - if err != nil { - return nil, err - } - edits, err := source.ToProtocolEdits(m, diff) - if err != nil { - return nil, err - } - return []protocol.TextDocumentEdit{{ - TextDocument: protocol.OptionalVersionedTextDocumentIdentifier{ - Version: fh.Version(), - TextDocumentIdentifier: protocol.TextDocumentIdentifier{ - URI: protocol.URIFromSpanURI(uri), - }, - }, - Edits: edits, - }}, nil -} - -func runGoGetModule(invoke func(...string) (*bytes.Buffer, error), addRequire bool, args []string) error { - if addRequire { - if err := addModuleRequire(invoke, args); err != nil { - return err - } - } - _, err := invoke(append([]string{"get", "-d"}, args...)...) - return err -} - -func addModuleRequire(invoke func(...string) (*bytes.Buffer, error), args []string) error { - // Using go get to create a new dependency results in an - // `// indirect` comment we may not want. The only way to avoid it - // is to add the require as direct first. Then we can use go get to - // update go.sum and tidy up. - _, err := invoke(append([]string{"mod", "edit", "-require"}, args...)...) - return err -} - -func (s *Server) getUpgrades(ctx context.Context, snapshot source.Snapshot, uri span.URI, modules []string) (map[string]string, error) { - stdout, err := snapshot.RunGoCommandDirect(ctx, source.Normal|source.AllowNetwork, &gocommand.Invocation{ - Verb: "list", - Args: append([]string{"-m", "-u", "-json"}, modules...), - WorkingDir: filepath.Dir(uri.Filename()), - ModFlag: "readonly", - }) - if err != nil { - return nil, err - } - - upgrades := map[string]string{} - for dec := json.NewDecoder(stdout); dec.More(); { - mod := &gocommand.ModuleJSON{} - if err := dec.Decode(mod); err != nil { - return nil, err - } - if mod.Update == nil { - continue - } - upgrades[mod.Path] = mod.Update.Version - } - return upgrades, nil -} - -func (c *commandHandler) GCDetails(ctx context.Context, uri protocol.DocumentURI) error { - return c.ToggleGCDetails(ctx, command.URIArg{URI: uri}) -} - -func (c *commandHandler) ToggleGCDetails(ctx context.Context, args command.URIArg) error { - return c.run(ctx, commandConfig{ - requireSave: true, - progress: "Toggling GC Details", - forURI: args.URI, - }, func(ctx context.Context, deps commandDeps) error { - pkg, err := deps.snapshot.PackageForFile(ctx, deps.fh.URI(), source.TypecheckWorkspace, source.NarrowestPackage) - if err != nil { - return err - } - c.s.gcOptimizationDetailsMu.Lock() - if _, ok := c.s.gcOptimizationDetails[pkg.ID()]; ok { - delete(c.s.gcOptimizationDetails, pkg.ID()) - c.s.clearDiagnosticSource(gcDetailsSource) - } else { - c.s.gcOptimizationDetails[pkg.ID()] = struct{}{} - } - c.s.gcOptimizationDetailsMu.Unlock() - c.s.diagnoseSnapshot(deps.snapshot, nil, false) - return nil - }) -} - -func (c *commandHandler) GenerateGoplsMod(ctx context.Context, args command.URIArg) error { - // TODO: go back to using URI - return c.run(ctx, commandConfig{ - requireSave: true, - progress: "Generating gopls.mod", - }, func(ctx context.Context, deps commandDeps) error { - views := c.s.session.Views() - if len(views) != 1 { - return fmt.Errorf("cannot resolve view: have %d views", len(views)) - } - v := views[0] - snapshot, release := v.Snapshot(ctx) - defer release() - modFile, err := snapshot.BuildGoplsMod(ctx) - if err != nil { - return errors.Errorf("getting workspace mod file: %w", err) - } - content, err := modFile.Format() - if err != nil { - return errors.Errorf("formatting mod file: %w", err) - } - filename := filepath.Join(snapshot.View().Folder().Filename(), "gopls.mod") - if err := ioutil.WriteFile(filename, content, 0644); err != nil { - return errors.Errorf("writing mod file: %w", err) - } - return nil - }) -} - -func (c *commandHandler) ListKnownPackages(ctx context.Context, args command.URIArg) (command.ListKnownPackagesResult, error) { - var result command.ListKnownPackagesResult - err := c.run(ctx, commandConfig{ - progress: "Listing packages", - forURI: args.URI, - }, func(ctx context.Context, deps commandDeps) error { - var err error - result.Packages, err = source.KnownPackages(ctx, deps.snapshot, deps.fh) - return err - }) - return result, err -} - -func (c *commandHandler) ListImports(ctx context.Context, args command.URIArg) (command.ListImportsResult, error) { - var result command.ListImportsResult - err := c.run(ctx, commandConfig{ - forURI: args.URI, - }, func(ctx context.Context, deps commandDeps) error { - pkg, err := deps.snapshot.PackageForFile(ctx, args.URI.SpanURI(), source.TypecheckWorkspace, source.NarrowestPackage) - if err != nil { - return err - } - pgf, err := pkg.File(args.URI.SpanURI()) - if err != nil { - return err - } - for _, group := range astutil.Imports(deps.snapshot.FileSet(), pgf.File) { - for _, imp := range group { - if imp.Path == nil { - continue - } - var name string - if imp.Name != nil { - name = imp.Name.Name - } - result.Imports = append(result.Imports, command.FileImport{ - Path: source.ImportPath(imp), - Name: name, - }) - } - } - for _, imp := range pkg.Imports() { - result.PackageImports = append(result.PackageImports, command.PackageImport{ - Path: imp.PkgPath(), // This might be the vendored path under GOPATH vendoring, in which case it's a bug. - }) - } - sort.Slice(result.PackageImports, func(i, j int) bool { - return result.PackageImports[i].Path < result.PackageImports[j].Path - }) - return nil - }) - return result, err -} - -func (c *commandHandler) AddImport(ctx context.Context, args command.AddImportArgs) error { - return c.run(ctx, commandConfig{ - progress: "Adding import", - forURI: args.URI, - }, func(ctx context.Context, deps commandDeps) error { - edits, err := source.AddImport(ctx, deps.snapshot, deps.fh, args.ImportPath) - if err != nil { - return fmt.Errorf("could not add import: %v", err) - } - if _, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{ - Edit: protocol.WorkspaceEdit{ - DocumentChanges: documentChanges(deps.fh, edits), - }, - }); err != nil { - return fmt.Errorf("could not apply import edits: %v", err) - } - return nil - }) -} - -func (c *commandHandler) StartDebugging(ctx context.Context, args command.DebuggingArgs) (result command.DebuggingResult, _ error) { - addr := args.Addr - if addr == "" { - addr = "localhost:0" - } - di := debug.GetInstance(ctx) - if di == nil { - return result, errors.New("internal error: server has no debugging instance") - } - listenedAddr, err := di.Serve(ctx, addr) - if err != nil { - return result, errors.Errorf("starting debug server: %w", err) - } - result.URLs = []string{"http://" + listenedAddr} - return result, nil -} - -func (c *commandHandler) RunVulncheckExp(ctx context.Context, args command.VulncheckArgs) (result command.VulncheckResult, _ error) { - err := c.run(ctx, commandConfig{ - progress: "Running vulncheck", - requireSave: true, - forURI: args.Dir, // Will dir work? - }, func(ctx context.Context, deps commandDeps) error { - view := deps.snapshot.View() - opts := view.Options() - if opts == nil || opts.Hooks.Govulncheck == nil { - return errors.New("vulncheck feature is not available") - } - - buildFlags := opts.BuildFlags // XXX: is session.Options equivalent to view.Options? - var viewEnv []string - if e := opts.EnvSlice(); e != nil { - viewEnv = append(os.Environ(), e...) - } - cfg := &packages.Config{ - Context: ctx, - Tests: true, // TODO(hyangah): add a field in args. - BuildFlags: buildFlags, - Env: viewEnv, - Dir: view.Folder().Filename(), - // TODO(hyangah): configure overlay - } - var err error - result, err = opts.Hooks.Govulncheck(ctx, cfg, args) - return err - }) - return result, err -} |