cuelang.org/go@v0.10.1/internal/golangorgx/gopls/cmd/cmd.go (about) 1 // Copyright 2018 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 cmd handles the gopls command line. 6 // It contains a handler for each of the modes, along with all the flag handling 7 // and the command line output format. 8 package cmd 9 10 import ( 11 "context" 12 "flag" 13 "fmt" 14 "log" 15 "os" 16 "path/filepath" 17 "reflect" 18 "sort" 19 "strings" 20 "sync" 21 "text/tabwriter" 22 "time" 23 24 "cuelang.org/go/internal/golangorgx/gopls/cache" 25 "cuelang.org/go/internal/golangorgx/gopls/debug" 26 "cuelang.org/go/internal/golangorgx/gopls/filecache" 27 "cuelang.org/go/internal/golangorgx/gopls/lsprpc" 28 "cuelang.org/go/internal/golangorgx/gopls/protocol" 29 "cuelang.org/go/internal/golangorgx/gopls/protocol/command" 30 "cuelang.org/go/internal/golangorgx/gopls/server" 31 "cuelang.org/go/internal/golangorgx/gopls/settings" 32 "cuelang.org/go/internal/golangorgx/gopls/util/browser" 33 bugpkg "cuelang.org/go/internal/golangorgx/gopls/util/bug" 34 "cuelang.org/go/internal/golangorgx/gopls/util/constraints" 35 "cuelang.org/go/internal/golangorgx/tools/diff" 36 "cuelang.org/go/internal/golangorgx/tools/jsonrpc2" 37 "cuelang.org/go/internal/golangorgx/tools/tool" 38 ) 39 40 // Application is the main application as passed to tool.Main 41 // It handles the main command line parsing and dispatch to the sub commands. 42 type Application struct { 43 // Core application flags 44 45 // Embed the basic profiling flags supported by the tool package 46 tool.Profile 47 48 // We include the server configuration directly for now, so the flags work 49 // even without the verb. 50 // TODO: Remove this when we stop allowing the serve verb by default. 51 Serve Serve 52 53 // the options configuring function to invoke when building a server 54 options func(*settings.Options) 55 56 // Support for remote LSP server. 57 Remote string `flag:"remote" help:"forward all commands to a remote lsp specified by this flag. With no special prefix, this is assumed to be a TCP address. If prefixed by 'unix;', the subsequent address is assumed to be a unix domain socket. If 'auto', or prefixed by 'auto;', the remote address is automatically resolved based on the executing environment."` 58 59 // Verbose enables verbose logging. 60 Verbose bool `flag:"v,verbose" help:"verbose output"` 61 62 // VeryVerbose enables a higher level of verbosity in logging output. 63 VeryVerbose bool `flag:"vv,veryverbose" help:"very verbose output"` 64 65 // Control ocagent export of telemetry 66 OCAgent string `flag:"ocagent" help:"the address of the ocagent (e.g. http://localhost:55678), or off"` 67 68 // PrepareOptions is called to update the options when a new view is built. 69 // It is primarily to allow the behavior of gopls to be modified by hooks. 70 PrepareOptions func(*settings.Options) 71 72 // editFlags holds flags that control how file edit operations 73 // are applied, in particular when the server makes an ApplyEdits 74 // downcall to the client. Present only for commands that apply edits. 75 editFlags *EditFlags 76 } 77 78 // EditFlags defines flags common to {fix,format,imports,rename} 79 // that control how edits are applied to the client's files. 80 // 81 // The type is exported for flag reflection. 82 // 83 // The -write, -diff, and -list flags are orthogonal but any 84 // of them suppresses the default behavior, which is to print 85 // the edited file contents. 86 type EditFlags struct { 87 Write bool `flag:"w,write" help:"write edited content to source files"` 88 Preserve bool `flag:"preserve" help:"with -write, make copies of original files"` 89 Diff bool `flag:"d,diff" help:"display diffs instead of edited file content"` 90 List bool `flag:"l,list" help:"display names of edited files"` 91 } 92 93 func (app *Application) verbose() bool { 94 return app.Verbose || app.VeryVerbose 95 } 96 97 // New returns a new Application ready to run. 98 func New(options func(*settings.Options)) *Application { 99 app := &Application{ 100 options: options, 101 OCAgent: "off", //TODO: Remove this line to default the exporter to on 102 103 Serve: Serve{ 104 RemoteListenTimeout: 1 * time.Minute, 105 }, 106 } 107 app.Serve.app = app 108 return app 109 } 110 111 // Name implements tool.Application returning the binary name. 112 func (app *Application) Name() string { return "gopls" } 113 114 // Usage implements tool.Application returning empty extra argument usage. 115 func (app *Application) Usage() string { return "" } 116 117 // ShortHelp implements tool.Application returning the main binary help. 118 func (app *Application) ShortHelp() string { 119 return "" 120 } 121 122 // DetailedHelp implements tool.Application returning the main binary help. 123 // This includes the short help for all the sub commands. 124 func (app *Application) DetailedHelp(f *flag.FlagSet) { 125 w := tabwriter.NewWriter(f.Output(), 0, 0, 2, ' ', 0) 126 defer w.Flush() 127 128 fmt.Fprint(w, ` 129 gopls is a Go language server. 130 131 It is typically used with an editor to provide language features. When no 132 command is specified, gopls will default to the 'serve' command. The language 133 features can also be accessed via the gopls command-line interface. 134 135 Usage: 136 gopls help [<subject>] 137 138 Command: 139 `) 140 fmt.Fprint(w, "\nMain\t\n") 141 for _, c := range app.mainCommands() { 142 fmt.Fprintf(w, " %s\t%s\n", c.Name(), c.ShortHelp()) 143 } 144 fmt.Fprint(w, "\t\nFeatures\t\n") 145 for _, c := range app.featureCommands() { 146 fmt.Fprintf(w, " %s\t%s\n", c.Name(), c.ShortHelp()) 147 } 148 if app.verbose() { 149 fmt.Fprint(w, "\t\nInternal Use Only\t\n") 150 for _, c := range app.internalCommands() { 151 fmt.Fprintf(w, " %s\t%s\n", c.Name(), c.ShortHelp()) 152 } 153 } 154 fmt.Fprint(w, "\nflags:\n") 155 printFlagDefaults(f) 156 } 157 158 // this is a slightly modified version of flag.PrintDefaults to give us control 159 func printFlagDefaults(s *flag.FlagSet) { 160 var flags [][]*flag.Flag 161 seen := map[flag.Value]int{} 162 s.VisitAll(func(f *flag.Flag) { 163 if i, ok := seen[f.Value]; !ok { 164 seen[f.Value] = len(flags) 165 flags = append(flags, []*flag.Flag{f}) 166 } else { 167 flags[i] = append(flags[i], f) 168 } 169 }) 170 for _, entry := range flags { 171 sort.SliceStable(entry, func(i, j int) bool { 172 return len(entry[i].Name) < len(entry[j].Name) 173 }) 174 var b strings.Builder 175 for i, f := range entry { 176 switch i { 177 case 0: 178 b.WriteString(" -") 179 default: 180 b.WriteString(",-") 181 } 182 b.WriteString(f.Name) 183 } 184 185 f := entry[0] 186 name, usage := flag.UnquoteUsage(f) 187 if len(name) > 0 { 188 b.WriteString("=") 189 b.WriteString(name) 190 } 191 // Boolean flags of one ASCII letter are so common we 192 // treat them specially, putting their usage on the same line. 193 if b.Len() <= 4 { // space, space, '-', 'x'. 194 b.WriteString("\t") 195 } else { 196 // Four spaces before the tab triggers good alignment 197 // for both 4- and 8-space tab stops. 198 b.WriteString("\n \t") 199 } 200 b.WriteString(strings.ReplaceAll(usage, "\n", "\n \t")) 201 if !isZeroValue(f, f.DefValue) { 202 if reflect.TypeOf(f.Value).Elem().Name() == "stringValue" { 203 fmt.Fprintf(&b, " (default %q)", f.DefValue) 204 } else { 205 fmt.Fprintf(&b, " (default %v)", f.DefValue) 206 } 207 } 208 fmt.Fprint(s.Output(), b.String(), "\n") 209 } 210 } 211 212 // isZeroValue is copied from the flags package 213 func isZeroValue(f *flag.Flag, value string) bool { 214 // Build a zero value of the flag's Value type, and see if the 215 // result of calling its String method equals the value passed in. 216 // This works unless the Value type is itself an interface type. 217 typ := reflect.TypeOf(f.Value) 218 var z reflect.Value 219 if typ.Kind() == reflect.Ptr { 220 z = reflect.New(typ.Elem()) 221 } else { 222 z = reflect.Zero(typ) 223 } 224 return value == z.Interface().(flag.Value).String() 225 } 226 227 // Run takes the args after top level flag processing, and invokes the correct 228 // sub command as specified by the first argument. 229 // If no arguments are passed it will invoke the server sub command, as a 230 // temporary measure for compatibility. 231 func (app *Application) Run(ctx context.Context, args ...string) error { 232 // In the category of "things we can do while waiting for the Go command": 233 // Pre-initialize the filecache, which takes ~50ms to hash the gopls 234 // executable, and immediately runs a gc. 235 filecache.Start() 236 237 ctx = debug.WithInstance(ctx, app.OCAgent) 238 if len(args) == 0 { 239 s := flag.NewFlagSet(app.Name(), flag.ExitOnError) 240 return tool.Run(ctx, s, &app.Serve, args) 241 } 242 command, args := args[0], args[1:] 243 for _, c := range app.Commands() { 244 if c.Name() == command { 245 s := flag.NewFlagSet(app.Name(), flag.ExitOnError) 246 return tool.Run(ctx, s, c, args) 247 } 248 } 249 return tool.CommandLineErrorf("Unknown command %v", command) 250 } 251 252 // Commands returns the set of commands supported by the gopls tool on the 253 // command line. 254 // The command is specified by the first non flag argument. 255 func (app *Application) Commands() []tool.Application { 256 var commands []tool.Application 257 commands = append(commands, app.mainCommands()...) 258 commands = append(commands, app.featureCommands()...) 259 commands = append(commands, app.internalCommands()...) 260 return commands 261 } 262 263 func (app *Application) mainCommands() []tool.Application { 264 return []tool.Application{ 265 &app.Serve, 266 &version{app: app}, 267 &bug{app: app}, 268 &help{app: app}, 269 &apiJSON{app: app}, 270 &licenses{app: app}, 271 } 272 } 273 274 func (app *Application) internalCommands() []tool.Application { 275 return []tool.Application{} 276 } 277 278 func (app *Application) featureCommands() []tool.Application { 279 return []tool.Application{ 280 &format{app: app}, 281 newRemote(app, ""), 282 newRemote(app, "inspect"), 283 } 284 } 285 286 var ( 287 internalMu sync.Mutex 288 internalConnections = make(map[string]*connection) 289 ) 290 291 // connect creates and initializes a new in-process gopls session. 292 // 293 // If onProgress is set, it is called for each new progress notification. 294 func (app *Application) connect(ctx context.Context, onProgress func(*protocol.ProgressParams)) (*connection, error) { 295 switch { 296 case app.Remote == "": 297 client := newClient(app, onProgress) 298 options := settings.DefaultOptions(app.options) 299 server := server.New(cache.NewSession(ctx, cache.New(nil)), client, options) 300 conn := newConnection(server, client) 301 if err := conn.initialize(protocol.WithClient(ctx, client), app.options); err != nil { 302 return nil, err 303 } 304 return conn, nil 305 306 default: 307 return app.connectRemote(ctx, app.Remote) 308 } 309 } 310 311 func (app *Application) connectRemote(ctx context.Context, remote string) (*connection, error) { 312 conn, err := lsprpc.ConnectToRemote(ctx, remote) 313 if err != nil { 314 return nil, err 315 } 316 stream := jsonrpc2.NewHeaderStream(conn) 317 cc := jsonrpc2.NewConn(stream) 318 server := protocol.ServerDispatcher(cc) 319 client := newClient(app, nil) 320 connection := newConnection(server, client) 321 ctx = protocol.WithClient(ctx, connection.client) 322 cc.Go(ctx, 323 protocol.Handlers( 324 protocol.ClientHandler(client, jsonrpc2.MethodNotFound))) 325 return connection, connection.initialize(ctx, app.options) 326 } 327 328 func (c *connection) initialize(ctx context.Context, options func(*settings.Options)) error { 329 wd, err := os.Getwd() 330 if err != nil { 331 return fmt.Errorf("finding workdir: %v", err) 332 } 333 params := &protocol.ParamInitialize{} 334 params.RootURI = protocol.URIFromPath(wd) 335 params.Capabilities.Workspace.Configuration = true 336 337 // Make sure to respect configured options when sending initialize request. 338 opts := settings.DefaultOptions(options) 339 // If you add an additional option here, you must update the map key in connect. 340 params.Capabilities.TextDocument.Hover = &protocol.HoverClientCapabilities{ 341 ContentFormat: []protocol.MarkupKind{opts.PreferredContentFormat}, 342 } 343 params.Capabilities.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport = opts.HierarchicalDocumentSymbolSupport 344 params.Capabilities.TextDocument.SemanticTokens = protocol.SemanticTokensClientCapabilities{} 345 params.Capabilities.TextDocument.SemanticTokens.Formats = []protocol.TokenFormat{"relative"} 346 params.Capabilities.TextDocument.SemanticTokens.Requests.Range = &protocol.Or_ClientSemanticTokensRequestOptions_range{Value: true} 347 //params.Capabilities.TextDocument.SemanticTokens.Requests.Range.Value = true 348 params.Capabilities.TextDocument.SemanticTokens.Requests.Full = &protocol.Or_ClientSemanticTokensRequestOptions_full{Value: true} 349 params.Capabilities.TextDocument.SemanticTokens.TokenTypes = protocol.SemanticTypes() 350 params.Capabilities.TextDocument.SemanticTokens.TokenModifiers = protocol.SemanticModifiers() 351 352 // If the subcommand has registered a progress handler, report the progress 353 // capability. 354 if c.client.onProgress != nil { 355 params.Capabilities.Window.WorkDoneProgress = true 356 } 357 358 params.InitializationOptions = map[string]interface{}{ 359 "symbolMatcher": string(opts.SymbolMatcher), 360 } 361 if _, err := c.Server.Initialize(ctx, params); err != nil { 362 return err 363 } 364 if err := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil { 365 return err 366 } 367 return nil 368 } 369 370 type connection struct { 371 protocol.Server 372 client *cmdClient 373 } 374 375 // cmdClient defines the protocol.Client interface behavior of the gopls CLI tool. 376 type cmdClient struct { 377 app *Application 378 onProgress func(*protocol.ProgressParams) 379 380 filesMu sync.Mutex // guards files map and each cmdFile.diagnostics 381 files map[protocol.DocumentURI]*cmdFile 382 } 383 384 type cmdFile struct { 385 uri protocol.DocumentURI 386 mapper *protocol.Mapper 387 err error 388 diagnostics []protocol.Diagnostic 389 } 390 391 func newClient(app *Application, onProgress func(*protocol.ProgressParams)) *cmdClient { 392 return &cmdClient{ 393 app: app, 394 onProgress: onProgress, 395 files: make(map[protocol.DocumentURI]*cmdFile), 396 } 397 } 398 399 func newConnection(server protocol.Server, client *cmdClient) *connection { 400 return &connection{ 401 Server: server, 402 client: client, 403 } 404 } 405 406 func (c *cmdClient) CodeLensRefresh(context.Context) error { return nil } 407 408 func (c *cmdClient) FoldingRangeRefresh(context.Context) error { return nil } 409 410 func (c *cmdClient) LogTrace(context.Context, *protocol.LogTraceParams) error { return nil } 411 412 func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { 413 fmt.Fprintf(os.Stderr, "%s: %s\n", p.Type, p.Message) 414 return nil 415 } 416 417 func (c *cmdClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) { 418 return nil, nil 419 } 420 421 func (c *cmdClient) LogMessage(ctx context.Context, p *protocol.LogMessageParams) error { 422 // This logic causes server logging to be double-prefixed with a timestamp. 423 // 2023/11/08 10:50:21 Error:2023/11/08 10:50:21 <actual message> 424 // TODO(adonovan): print just p.Message, plus a newline if needed? 425 switch p.Type { 426 case protocol.Error: 427 log.Print("Error:", p.Message) 428 case protocol.Warning: 429 log.Print("Warning:", p.Message) 430 case protocol.Info: 431 if c.app.verbose() { 432 log.Print("Info:", p.Message) 433 } 434 case protocol.Log: 435 if c.app.verbose() { 436 log.Print("Log:", p.Message) 437 } 438 default: 439 if c.app.verbose() { 440 log.Print(p.Message) 441 } 442 } 443 return nil 444 } 445 446 func (c *cmdClient) Event(ctx context.Context, t *interface{}) error { return nil } 447 448 func (c *cmdClient) RegisterCapability(ctx context.Context, p *protocol.RegistrationParams) error { 449 return nil 450 } 451 452 func (c *cmdClient) UnregisterCapability(ctx context.Context, p *protocol.UnregistrationParams) error { 453 return nil 454 } 455 456 func (c *cmdClient) WorkspaceFolders(ctx context.Context) ([]protocol.WorkspaceFolder, error) { 457 return nil, nil 458 } 459 460 func (c *cmdClient) Configuration(ctx context.Context, p *protocol.ParamConfiguration) ([]interface{}, error) { 461 results := make([]interface{}, len(p.Items)) 462 for i, item := range p.Items { 463 if item.Section != "gopls" { 464 continue 465 } 466 m := map[string]interface{}{ 467 "analyses": map[string]any{ 468 "fillreturns": true, 469 "nonewvars": true, 470 "noresultvalues": true, 471 "undeclaredname": true, 472 }, 473 } 474 if c.app.VeryVerbose { 475 m["verboseOutput"] = true 476 } 477 results[i] = m 478 } 479 return results, nil 480 } 481 482 func (c *cmdClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResult, error) { 483 if err := c.applyWorkspaceEdit(&p.Edit); err != nil { 484 return &protocol.ApplyWorkspaceEditResult{FailureReason: err.Error()}, nil 485 } 486 return &protocol.ApplyWorkspaceEditResult{Applied: true}, nil 487 } 488 489 // applyWorkspaceEdit applies a complete WorkspaceEdit to the client's 490 // files, honoring the preferred edit mode specified by cli.app.editMode. 491 // (Used by rename and by ApplyEdit downcalls.) 492 func (cli *cmdClient) applyWorkspaceEdit(edit *protocol.WorkspaceEdit) error { 493 var orderedURIs []protocol.DocumentURI 494 edits := map[protocol.DocumentURI][]protocol.TextEdit{} 495 for _, c := range edit.DocumentChanges { 496 if c.TextDocumentEdit != nil { 497 uri := c.TextDocumentEdit.TextDocument.URI 498 edits[uri] = append(edits[uri], protocol.AsTextEdits(c.TextDocumentEdit.Edits)...) 499 orderedURIs = append(orderedURIs, uri) 500 } 501 if c.RenameFile != nil { 502 return fmt.Errorf("client does not support file renaming (%s -> %s)", 503 c.RenameFile.OldURI, 504 c.RenameFile.NewURI) 505 } 506 } 507 sortSlice(orderedURIs) 508 for _, uri := range orderedURIs { 509 f := cli.openFile(uri) 510 if f.err != nil { 511 return f.err 512 } 513 if err := applyTextEdits(f.mapper, edits[uri], cli.app.editFlags); err != nil { 514 return err 515 } 516 } 517 return nil 518 } 519 520 func sortSlice[T constraints.Ordered](slice []T) { 521 sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] }) 522 } 523 524 // applyTextEdits applies a list of edits to the mapper file content, 525 // using the preferred edit mode. It is a no-op if there are no edits. 526 func applyTextEdits(mapper *protocol.Mapper, edits []protocol.TextEdit, flags *EditFlags) error { 527 if len(edits) == 0 { 528 return nil 529 } 530 newContent, renameEdits, err := protocol.ApplyEdits(mapper, edits) 531 if err != nil { 532 return err 533 } 534 535 filename := mapper.URI.Path() 536 537 if flags.List { 538 fmt.Println(filename) 539 } 540 541 if flags.Write { 542 if flags.Preserve { 543 if err := os.Rename(filename, filename+".orig"); err != nil { 544 return err 545 } 546 } 547 if err := os.WriteFile(filename, newContent, 0644); err != nil { 548 return err 549 } 550 } 551 552 if flags.Diff { 553 unified, err := diff.ToUnified(filename+".orig", filename, string(mapper.Content), renameEdits, diff.DefaultContextLines) 554 if err != nil { 555 return err 556 } 557 fmt.Print(unified) 558 } 559 560 // No flags: just print edited file content. 561 // TODO(adonovan): how is this ever useful with multiple files? 562 if !(flags.List || flags.Write || flags.Diff) { 563 os.Stdout.Write(newContent) 564 } 565 566 return nil 567 } 568 569 func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error { 570 // Don't worry about diagnostics without versions. 571 if p.Version == 0 { 572 return nil 573 } 574 575 c.filesMu.Lock() 576 defer c.filesMu.Unlock() 577 578 file := c.getFile(p.URI) 579 file.diagnostics = append(file.diagnostics, p.Diagnostics...) 580 581 // Perform a crude in-place deduplication. 582 // TODO(golang/go#60122): replace the gopls.diagnose_files 583 // command with support for textDocument/diagnostic, 584 // so that we don't need to do this de-duplication. 585 type key [6]interface{} 586 seen := make(map[key]bool) 587 out := file.diagnostics[:0] 588 for _, d := range file.diagnostics { 589 var codeHref string 590 if desc := d.CodeDescription; desc != nil { 591 codeHref = desc.Href 592 } 593 k := key{d.Range, d.Severity, d.Code, codeHref, d.Source, d.Message} 594 if !seen[k] { 595 seen[k] = true 596 out = append(out, d) 597 } 598 } 599 file.diagnostics = out 600 601 return nil 602 } 603 604 func (c *cmdClient) Progress(_ context.Context, params *protocol.ProgressParams) error { 605 if c.onProgress != nil { 606 c.onProgress(params) 607 } 608 return nil 609 } 610 611 func (c *cmdClient) ShowDocument(ctx context.Context, params *protocol.ShowDocumentParams) (*protocol.ShowDocumentResult, error) { 612 var success bool 613 if params.External { 614 // Open URI in external browser. 615 success = browser.Open(params.URI) 616 } else { 617 // Open file in editor, optionally taking focus and selecting a range. 618 // (cmdClient has no editor. Should it fork+exec $EDITOR?) 619 log.Printf("Server requested that client editor open %q (takeFocus=%t, selection=%+v)", 620 params.URI, params.TakeFocus, params.Selection) 621 success = true 622 } 623 return &protocol.ShowDocumentResult{Success: success}, nil 624 } 625 626 func (c *cmdClient) WorkDoneProgressCreate(context.Context, *protocol.WorkDoneProgressCreateParams) error { 627 return nil 628 } 629 630 func (c *cmdClient) DiagnosticRefresh(context.Context) error { 631 return nil 632 } 633 634 func (c *cmdClient) InlayHintRefresh(context.Context) error { 635 return nil 636 } 637 638 func (c *cmdClient) SemanticTokensRefresh(context.Context) error { 639 return nil 640 } 641 642 func (c *cmdClient) InlineValueRefresh(context.Context) error { 643 return nil 644 } 645 646 func (c *cmdClient) getFile(uri protocol.DocumentURI) *cmdFile { 647 file, found := c.files[uri] 648 if !found || file.err != nil { 649 file = &cmdFile{ 650 uri: uri, 651 } 652 c.files[uri] = file 653 } 654 if file.mapper == nil { 655 content, err := os.ReadFile(uri.Path()) 656 if err != nil { 657 file.err = fmt.Errorf("getFile: %v: %v", uri, err) 658 return file 659 } 660 file.mapper = protocol.NewMapper(uri, content) 661 } 662 return file 663 } 664 665 func (c *cmdClient) openFile(uri protocol.DocumentURI) *cmdFile { 666 c.filesMu.Lock() 667 defer c.filesMu.Unlock() 668 return c.getFile(uri) 669 } 670 671 // TODO(adonovan): provide convenience helpers to: 672 // - map a (URI, protocol.Range) to a MappedRange; 673 // - parse a command-line argument to a MappedRange. 674 func (c *connection) openFile(ctx context.Context, uri protocol.DocumentURI) (*cmdFile, error) { 675 file := c.client.openFile(uri) 676 if file.err != nil { 677 return nil, file.err 678 } 679 680 p := &protocol.DidOpenTextDocumentParams{ 681 TextDocument: protocol.TextDocumentItem{ 682 URI: uri, 683 LanguageID: "go", 684 Version: 1, 685 Text: string(file.mapper.Content), 686 }, 687 } 688 if err := c.Server.DidOpen(ctx, p); err != nil { 689 // TODO(adonovan): is this assignment concurrency safe? 690 file.err = fmt.Errorf("%v: %v", uri, err) 691 return nil, file.err 692 } 693 return file, nil 694 } 695 696 func (c *connection) semanticTokens(ctx context.Context, p *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) { 697 // use range to avoid limits on full 698 resp, err := c.Server.SemanticTokensRange(ctx, p) 699 if err != nil { 700 return nil, err 701 } 702 return resp, nil 703 } 704 705 func (c *connection) diagnoseFiles(ctx context.Context, files []protocol.DocumentURI) error { 706 cmd, err := command.NewDiagnoseFilesCommand("Diagnose files", command.DiagnoseFilesArgs{ 707 Files: files, 708 }) 709 if err != nil { 710 return err 711 } 712 _, err = c.ExecuteCommand(ctx, &protocol.ExecuteCommandParams{ 713 Command: cmd.Command, 714 Arguments: cmd.Arguments, 715 }) 716 return err 717 } 718 719 func (c *connection) terminate(ctx context.Context) { 720 //TODO: do we need to handle errors on these calls? 721 c.Shutdown(ctx) 722 //TODO: right now calling exit terminates the process, we should rethink that 723 //server.Exit(ctx) 724 } 725 726 // Implement io.Closer. 727 func (c *cmdClient) Close() error { 728 return nil 729 } 730 731 // -- conversions to span (UTF-8) domain -- 732 733 // locationSpan converts a protocol (UTF-16) Location to a (UTF-8) span. 734 // Precondition: the URIs of Location and Mapper match. 735 func (f *cmdFile) locationSpan(loc protocol.Location) (span, error) { 736 // TODO(adonovan): check that l.URI matches m.URI. 737 return f.rangeSpan(loc.Range) 738 } 739 740 // rangeSpan converts a protocol (UTF-16) range to a (UTF-8) span. 741 // The resulting span has valid Positions and Offsets. 742 func (f *cmdFile) rangeSpan(r protocol.Range) (span, error) { 743 start, end, err := f.mapper.RangeOffsets(r) 744 if err != nil { 745 return span{}, err 746 } 747 return f.offsetSpan(start, end) 748 } 749 750 // offsetSpan converts a byte-offset interval to a (UTF-8) span. 751 // The resulting span contains line, column, and offset information. 752 func (f *cmdFile) offsetSpan(start, end int) (span, error) { 753 if start > end { 754 return span{}, fmt.Errorf("start offset (%d) > end (%d)", start, end) 755 } 756 startPoint, err := offsetPoint(f.mapper, start) 757 if err != nil { 758 return span{}, fmt.Errorf("start: %v", err) 759 } 760 endPoint, err := offsetPoint(f.mapper, end) 761 if err != nil { 762 return span{}, fmt.Errorf("end: %v", err) 763 } 764 return newSpan(f.mapper.URI, startPoint, endPoint), nil 765 } 766 767 // offsetPoint converts a byte offset to a span (UTF-8) point. 768 // The resulting point contains line, column, and offset information. 769 func offsetPoint(m *protocol.Mapper, offset int) (point, error) { 770 if !(0 <= offset && offset <= len(m.Content)) { 771 return point{}, fmt.Errorf("invalid offset %d (want 0-%d)", offset, len(m.Content)) 772 } 773 line, col8 := m.OffsetLineCol8(offset) 774 return newPoint(line, col8, offset), nil 775 } 776 777 // -- conversions from span (UTF-8) domain -- 778 779 // spanLocation converts a (UTF-8) span to a protocol (UTF-16) range. 780 // Precondition: the URIs of spanLocation and Mapper match. 781 func (f *cmdFile) spanLocation(s span) (protocol.Location, error) { 782 rng, err := f.spanRange(s) 783 if err != nil { 784 return protocol.Location{}, err 785 } 786 return f.mapper.RangeLocation(rng), nil 787 } 788 789 // spanRange converts a (UTF-8) span to a protocol (UTF-16) range. 790 // Precondition: the URIs of span and Mapper match. 791 func (f *cmdFile) spanRange(s span) (protocol.Range, error) { 792 // Assert that we aren't using the wrong mapper. 793 // We check only the base name, and case insensitively, 794 // because we can't assume clean paths, no symbolic links, 795 // case-sensitive directories. The authoritative answer 796 // requires querying the file system, and we don't want 797 // to do that. 798 if !strings.EqualFold(filepath.Base(string(f.mapper.URI)), filepath.Base(string(s.URI()))) { 799 return protocol.Range{}, bugpkg.Errorf("mapper is for file %q instead of %q", f.mapper.URI, s.URI()) 800 } 801 start, err := pointPosition(f.mapper, s.Start()) 802 if err != nil { 803 return protocol.Range{}, fmt.Errorf("start: %w", err) 804 } 805 end, err := pointPosition(f.mapper, s.End()) 806 if err != nil { 807 return protocol.Range{}, fmt.Errorf("end: %w", err) 808 } 809 return protocol.Range{Start: start, End: end}, nil 810 } 811 812 // pointPosition converts a valid span (UTF-8) point to a protocol (UTF-16) position. 813 func pointPosition(m *protocol.Mapper, p point) (protocol.Position, error) { 814 if p.HasPosition() { 815 return m.LineCol8Position(p.Line(), p.Column()) 816 } 817 if p.HasOffset() { 818 return m.OffsetPosition(p.Offset()) 819 } 820 return protocol.Position{}, fmt.Errorf("point has neither offset nor line/column") 821 }