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