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