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