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 }