github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/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 "go/token" 15 "io/ioutil" 16 "log" 17 "os" 18 "reflect" 19 "sort" 20 "strings" 21 "sync" 22 "text/tabwriter" 23 "time" 24 25 "github.com/powerman/golang-tools/internal/jsonrpc2" 26 "github.com/powerman/golang-tools/internal/lsp" 27 "github.com/powerman/golang-tools/internal/lsp/cache" 28 "github.com/powerman/golang-tools/internal/lsp/debug" 29 "github.com/powerman/golang-tools/internal/lsp/lsprpc" 30 "github.com/powerman/golang-tools/internal/lsp/protocol" 31 "github.com/powerman/golang-tools/internal/lsp/source" 32 "github.com/powerman/golang-tools/internal/span" 33 "github.com/powerman/golang-tools/internal/tool" 34 "github.com/powerman/golang-tools/internal/xcontext" 35 errors "golang.org/x/xerrors" 36 ) 37 38 // Application is the main application as passed to tool.Main 39 // It handles the main command line parsing and dispatch to the sub commands. 40 type Application struct { 41 // Core application flags 42 43 // Embed the basic profiling flags supported by the tool package 44 tool.Profile 45 46 // We include the server configuration directly for now, so the flags work 47 // even without the verb. 48 // TODO: Remove this when we stop allowing the serve verb by default. 49 Serve Serve 50 51 // the options configuring function to invoke when building a server 52 options func(*source.Options) 53 54 // The name of the binary, used in help and telemetry. 55 name string 56 57 // The working directory to run commands in. 58 wd string 59 60 // The environment variables to use. 61 env []string 62 63 // Support for remote LSP server. 64 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."` 65 66 // Verbose enables verbose logging. 67 Verbose bool `flag:"v,verbose" help:"verbose output"` 68 69 // VeryVerbose enables a higher level of verbosity in logging output. 70 VeryVerbose bool `flag:"vv,veryverbose" help:"very verbose output"` 71 72 // Control ocagent export of telemetry 73 OCAgent string `flag:"ocagent" help:"the address of the ocagent (e.g. http://localhost:55678), or off"` 74 75 // PrepareOptions is called to update the options when a new view is built. 76 // It is primarily to allow the behavior of gopls to be modified by hooks. 77 PrepareOptions func(*source.Options) 78 } 79 80 func (app *Application) verbose() bool { 81 return app.Verbose || app.VeryVerbose 82 } 83 84 // New returns a new Application ready to run. 85 func New(name, wd string, env []string, options func(*source.Options)) *Application { 86 if wd == "" { 87 wd, _ = os.Getwd() 88 } 89 app := &Application{ 90 options: options, 91 name: name, 92 wd: wd, 93 env: env, 94 OCAgent: "off", //TODO: Remove this line to default the exporter to on 95 96 Serve: Serve{ 97 RemoteListenTimeout: 1 * time.Minute, 98 }, 99 } 100 app.Serve.app = app 101 return app 102 } 103 104 // Name implements tool.Application returning the binary name. 105 func (app *Application) Name() string { return app.name } 106 107 // Usage implements tool.Application returning empty extra argument usage. 108 func (app *Application) Usage() string { return "" } 109 110 // ShortHelp implements tool.Application returning the main binary help. 111 func (app *Application) ShortHelp() string { 112 return "" 113 } 114 115 // DetailedHelp implements tool.Application returning the main binary help. 116 // This includes the short help for all the sub commands. 117 func (app *Application) DetailedHelp(f *flag.FlagSet) { 118 w := tabwriter.NewWriter(f.Output(), 0, 0, 2, ' ', 0) 119 defer w.Flush() 120 121 fmt.Fprint(w, ` 122 gopls is a Go language server. 123 124 It is typically used with an editor to provide language features. When no 125 command is specified, gopls will default to the 'serve' command. The language 126 features can also be accessed via the gopls command-line interface. 127 128 Usage: 129 gopls help [<subject>] 130 131 Command: 132 `) 133 fmt.Fprint(w, "\nMain\t\n") 134 for _, c := range app.mainCommands() { 135 fmt.Fprintf(w, " %s\t%s\n", c.Name(), c.ShortHelp()) 136 } 137 fmt.Fprint(w, "\t\nFeatures\t\n") 138 for _, c := range app.featureCommands() { 139 fmt.Fprintf(w, " %s\t%s\n", c.Name(), c.ShortHelp()) 140 } 141 fmt.Fprint(w, "\nflags:\n") 142 printFlagDefaults(f) 143 } 144 145 // this is a slightly modified version of flag.PrintDefaults to give us control 146 func printFlagDefaults(s *flag.FlagSet) { 147 var flags [][]*flag.Flag 148 seen := map[flag.Value]int{} 149 s.VisitAll(func(f *flag.Flag) { 150 if i, ok := seen[f.Value]; !ok { 151 seen[f.Value] = len(flags) 152 flags = append(flags, []*flag.Flag{f}) 153 } else { 154 flags[i] = append(flags[i], f) 155 } 156 }) 157 for _, entry := range flags { 158 sort.SliceStable(entry, func(i, j int) bool { 159 return len(entry[i].Name) < len(entry[j].Name) 160 }) 161 var b strings.Builder 162 for i, f := range entry { 163 switch i { 164 case 0: 165 b.WriteString(" -") 166 default: 167 b.WriteString(",-") 168 } 169 b.WriteString(f.Name) 170 } 171 172 f := entry[0] 173 name, usage := flag.UnquoteUsage(f) 174 if len(name) > 0 { 175 b.WriteString("=") 176 b.WriteString(name) 177 } 178 // Boolean flags of one ASCII letter are so common we 179 // treat them specially, putting their usage on the same line. 180 if b.Len() <= 4 { // space, space, '-', 'x'. 181 b.WriteString("\t") 182 } else { 183 // Four spaces before the tab triggers good alignment 184 // for both 4- and 8-space tab stops. 185 b.WriteString("\n \t") 186 } 187 b.WriteString(strings.ReplaceAll(usage, "\n", "\n \t")) 188 if !isZeroValue(f, f.DefValue) { 189 if reflect.TypeOf(f.Value).Elem().Name() == "stringValue" { 190 fmt.Fprintf(&b, " (default %q)", f.DefValue) 191 } else { 192 fmt.Fprintf(&b, " (default %v)", f.DefValue) 193 } 194 } 195 fmt.Fprint(s.Output(), b.String(), "\n") 196 } 197 } 198 199 // isZeroValue is copied from the flags package 200 func isZeroValue(f *flag.Flag, value string) bool { 201 // Build a zero value of the flag's Value type, and see if the 202 // result of calling its String method equals the value passed in. 203 // This works unless the Value type is itself an interface type. 204 typ := reflect.TypeOf(f.Value) 205 var z reflect.Value 206 if typ.Kind() == reflect.Ptr { 207 z = reflect.New(typ.Elem()) 208 } else { 209 z = reflect.Zero(typ) 210 } 211 return value == z.Interface().(flag.Value).String() 212 } 213 214 // Run takes the args after top level flag processing, and invokes the correct 215 // sub command as specified by the first argument. 216 // If no arguments are passed it will invoke the server sub command, as a 217 // temporary measure for compatibility. 218 func (app *Application) Run(ctx context.Context, args ...string) error { 219 ctx = debug.WithInstance(ctx, app.wd, app.OCAgent) 220 if len(args) == 0 { 221 s := flag.NewFlagSet(app.Name(), flag.ExitOnError) 222 return tool.Run(ctx, s, &app.Serve, args) 223 } 224 command, args := args[0], args[1:] 225 for _, c := range app.Commands() { 226 if c.Name() == command { 227 s := flag.NewFlagSet(app.Name(), flag.ExitOnError) 228 return tool.Run(ctx, s, c, args) 229 } 230 } 231 return tool.CommandLineErrorf("Unknown command %v", command) 232 } 233 234 // commands returns the set of commands supported by the gopls tool on the 235 // command line. 236 // The command is specified by the first non flag argument. 237 func (app *Application) Commands() []tool.Application { 238 var commands []tool.Application 239 commands = append(commands, app.mainCommands()...) 240 commands = append(commands, app.featureCommands()...) 241 return commands 242 } 243 244 func (app *Application) mainCommands() []tool.Application { 245 return []tool.Application{ 246 &app.Serve, 247 &version{app: app}, 248 &bug{app: app}, 249 &apiJSON{app: app}, 250 &licenses{app: app}, 251 } 252 } 253 254 func (app *Application) featureCommands() []tool.Application { 255 return []tool.Application{ 256 &callHierarchy{app: app}, 257 &check{app: app}, 258 &definition{app: app}, 259 &foldingRanges{app: app}, 260 &format{app: app}, 261 &highlight{app: app}, 262 &implementation{app: app}, 263 &imports{app: app}, 264 newRemote(app, ""), 265 newRemote(app, "inspect"), 266 &links{app: app}, 267 &prepareRename{app: app}, 268 &references{app: app}, 269 &rename{app: app}, 270 &semtok{app: app}, 271 &signature{app: app}, 272 &suggestedFix{app: app}, 273 &symbols{app: app}, 274 newWorkspace(app), 275 &workspaceSymbol{app: app}, 276 &vulncheck{app: app}, 277 } 278 } 279 280 var ( 281 internalMu sync.Mutex 282 internalConnections = make(map[string]*connection) 283 ) 284 285 func (app *Application) connect(ctx context.Context) (*connection, error) { 286 switch { 287 case app.Remote == "": 288 connection := newConnection(app) 289 connection.Server = lsp.NewServer(cache.New(app.options).NewSession(ctx), connection.Client) 290 ctx = protocol.WithClient(ctx, connection.Client) 291 return connection, connection.initialize(ctx, app.options) 292 case strings.HasPrefix(app.Remote, "internal@"): 293 internalMu.Lock() 294 defer internalMu.Unlock() 295 opts := source.DefaultOptions().Clone() 296 if app.options != nil { 297 app.options(opts) 298 } 299 key := fmt.Sprintf("%s %v %v %v", app.wd, opts.PreferredContentFormat, opts.HierarchicalDocumentSymbolSupport, opts.SymbolMatcher) 300 if c := internalConnections[key]; c != nil { 301 return c, nil 302 } 303 remote := app.Remote[len("internal@"):] 304 ctx := xcontext.Detach(ctx) //TODO:a way of shutting down the internal server 305 connection, err := app.connectRemote(ctx, remote) 306 if err != nil { 307 return nil, err 308 } 309 internalConnections[key] = connection 310 return connection, nil 311 default: 312 return app.connectRemote(ctx, app.Remote) 313 } 314 } 315 316 // CloseTestConnections terminates shared connections used in command tests. It 317 // should only be called from tests. 318 func CloseTestConnections(ctx context.Context) { 319 for _, c := range internalConnections { 320 c.Shutdown(ctx) 321 c.Exit(ctx) 322 } 323 } 324 325 func (app *Application) connectRemote(ctx context.Context, remote string) (*connection, error) { 326 connection := newConnection(app) 327 conn, err := lsprpc.ConnectToRemote(ctx, remote) 328 if err != nil { 329 return nil, err 330 } 331 stream := jsonrpc2.NewHeaderStream(conn) 332 cc := jsonrpc2.NewConn(stream) 333 connection.Server = protocol.ServerDispatcher(cc) 334 ctx = protocol.WithClient(ctx, connection.Client) 335 cc.Go(ctx, 336 protocol.Handlers( 337 protocol.ClientHandler(connection.Client, 338 jsonrpc2.MethodNotFound))) 339 return connection, connection.initialize(ctx, app.options) 340 } 341 342 var matcherString = map[source.SymbolMatcher]string{ 343 source.SymbolFuzzy: "fuzzy", 344 source.SymbolCaseSensitive: "caseSensitive", 345 source.SymbolCaseInsensitive: "caseInsensitive", 346 } 347 348 func (c *connection) initialize(ctx context.Context, options func(*source.Options)) error { 349 params := &protocol.ParamInitialize{} 350 params.RootURI = protocol.URIFromPath(c.Client.app.wd) 351 params.Capabilities.Workspace.Configuration = true 352 353 // Make sure to respect configured options when sending initialize request. 354 opts := source.DefaultOptions().Clone() 355 if options != nil { 356 options(opts) 357 } 358 // If you add an additional option here, you must update the map key in connect. 359 params.Capabilities.TextDocument.Hover = protocol.HoverClientCapabilities{ 360 ContentFormat: []protocol.MarkupKind{opts.PreferredContentFormat}, 361 } 362 params.Capabilities.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport = opts.HierarchicalDocumentSymbolSupport 363 params.Capabilities.TextDocument.SemanticTokens = protocol.SemanticTokensClientCapabilities{} 364 params.Capabilities.TextDocument.SemanticTokens.Formats = []string{"relative"} 365 params.Capabilities.TextDocument.SemanticTokens.Requests.Range = true 366 params.Capabilities.TextDocument.SemanticTokens.Requests.Full = true 367 params.Capabilities.TextDocument.SemanticTokens.TokenTypes = lsp.SemanticTypes() 368 params.Capabilities.TextDocument.SemanticTokens.TokenModifiers = lsp.SemanticModifiers() 369 params.InitializationOptions = map[string]interface{}{ 370 "symbolMatcher": matcherString[opts.SymbolMatcher], 371 } 372 if _, err := c.Server.Initialize(ctx, params); err != nil { 373 return err 374 } 375 if err := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil { 376 return err 377 } 378 return nil 379 } 380 381 type connection struct { 382 protocol.Server 383 Client *cmdClient 384 } 385 386 type cmdClient struct { 387 protocol.Server 388 app *Application 389 fset *token.FileSet 390 391 diagnosticsMu sync.Mutex 392 diagnosticsDone chan struct{} 393 394 filesMu sync.Mutex 395 files map[span.URI]*cmdFile 396 } 397 398 type cmdFile struct { 399 uri span.URI 400 mapper *protocol.ColumnMapper 401 err error 402 added bool 403 diagnostics []protocol.Diagnostic 404 } 405 406 func newConnection(app *Application) *connection { 407 return &connection{ 408 Client: &cmdClient{ 409 app: app, 410 fset: token.NewFileSet(), 411 files: make(map[span.URI]*cmdFile), 412 }, 413 } 414 } 415 416 // fileURI converts a DocumentURI to a file:// span.URI, panicking if it's not a file. 417 func fileURI(uri protocol.DocumentURI) span.URI { 418 sURI := uri.SpanURI() 419 if !sURI.IsFile() { 420 panic(fmt.Sprintf("%q is not a file URI", uri)) 421 } 422 return sURI 423 } 424 425 func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { return nil } 426 427 func (c *cmdClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) { 428 return nil, nil 429 } 430 431 func (c *cmdClient) LogMessage(ctx context.Context, p *protocol.LogMessageParams) error { 432 switch p.Type { 433 case protocol.Error: 434 log.Print("Error:", p.Message) 435 case protocol.Warning: 436 log.Print("Warning:", p.Message) 437 case protocol.Info: 438 if c.app.verbose() { 439 log.Print("Info:", p.Message) 440 } 441 case protocol.Log: 442 if c.app.verbose() { 443 log.Print("Log:", p.Message) 444 } 445 default: 446 if c.app.verbose() { 447 log.Print(p.Message) 448 } 449 } 450 return nil 451 } 452 453 func (c *cmdClient) Event(ctx context.Context, t *interface{}) error { return nil } 454 455 func (c *cmdClient) RegisterCapability(ctx context.Context, p *protocol.RegistrationParams) error { 456 return nil 457 } 458 459 func (c *cmdClient) UnregisterCapability(ctx context.Context, p *protocol.UnregistrationParams) error { 460 return nil 461 } 462 463 func (c *cmdClient) WorkspaceFolders(ctx context.Context) ([]protocol.WorkspaceFolder, error) { 464 return nil, nil 465 } 466 467 func (c *cmdClient) Configuration(ctx context.Context, p *protocol.ParamConfiguration) ([]interface{}, error) { 468 results := make([]interface{}, len(p.Items)) 469 for i, item := range p.Items { 470 if item.Section != "gopls" { 471 continue 472 } 473 env := map[string]interface{}{} 474 for _, value := range c.app.env { 475 l := strings.SplitN(value, "=", 2) 476 if len(l) != 2 { 477 continue 478 } 479 env[l[0]] = l[1] 480 } 481 m := map[string]interface{}{ 482 "env": env, 483 "analyses": map[string]bool{ 484 "fillreturns": true, 485 "nonewvars": true, 486 "noresultvalues": true, 487 "undeclaredname": true, 488 }, 489 } 490 if c.app.VeryVerbose { 491 m["verboseOutput"] = true 492 } 493 results[i] = m 494 } 495 return results, nil 496 } 497 498 func (c *cmdClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResult, error) { 499 return &protocol.ApplyWorkspaceEditResult{Applied: false, FailureReason: "not implemented"}, nil 500 } 501 502 func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error { 503 if p.URI == "gopls://diagnostics-done" { 504 close(c.diagnosticsDone) 505 } 506 // Don't worry about diagnostics without versions. 507 if p.Version == 0 { 508 return nil 509 } 510 511 c.filesMu.Lock() 512 defer c.filesMu.Unlock() 513 514 file := c.getFile(ctx, fileURI(p.URI)) 515 file.diagnostics = p.Diagnostics 516 return nil 517 } 518 519 func (c *cmdClient) Progress(context.Context, *protocol.ProgressParams) error { 520 return nil 521 } 522 523 func (c *cmdClient) ShowDocument(context.Context, *protocol.ShowDocumentParams) (*protocol.ShowDocumentResult, error) { 524 return nil, nil 525 } 526 527 func (c *cmdClient) WorkDoneProgressCreate(context.Context, *protocol.WorkDoneProgressCreateParams) error { 528 return nil 529 } 530 531 func (c *cmdClient) getFile(ctx context.Context, uri span.URI) *cmdFile { 532 file, found := c.files[uri] 533 if !found || file.err != nil { 534 file = &cmdFile{ 535 uri: uri, 536 } 537 c.files[uri] = file 538 } 539 if file.mapper == nil { 540 fname := uri.Filename() 541 content, err := ioutil.ReadFile(fname) 542 if err != nil { 543 file.err = errors.Errorf("getFile: %v: %v", uri, err) 544 return file 545 } 546 f := c.fset.AddFile(fname, -1, len(content)) 547 f.SetLinesForContent(content) 548 converter := span.NewContentConverter(fname, content) 549 file.mapper = &protocol.ColumnMapper{ 550 URI: uri, 551 Converter: converter, 552 Content: content, 553 } 554 } 555 return file 556 } 557 558 func (c *connection) AddFile(ctx context.Context, uri span.URI) *cmdFile { 559 c.Client.filesMu.Lock() 560 defer c.Client.filesMu.Unlock() 561 562 file := c.Client.getFile(ctx, uri) 563 // This should never happen. 564 if file == nil { 565 return &cmdFile{ 566 uri: uri, 567 err: fmt.Errorf("no file found for %s", uri), 568 } 569 } 570 if file.err != nil || file.added { 571 return file 572 } 573 file.added = true 574 p := &protocol.DidOpenTextDocumentParams{ 575 TextDocument: protocol.TextDocumentItem{ 576 URI: protocol.URIFromSpanURI(uri), 577 LanguageID: "go", 578 Version: 1, 579 Text: string(file.mapper.Content), 580 }, 581 } 582 if err := c.Server.DidOpen(ctx, p); err != nil { 583 file.err = errors.Errorf("%v: %v", uri, err) 584 } 585 return file 586 } 587 588 func (c *connection) semanticTokens(ctx context.Context, p *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) { 589 // use range to avoid limits on full 590 resp, err := c.Server.SemanticTokensRange(ctx, p) 591 if err != nil { 592 return nil, err 593 } 594 return resp, nil 595 } 596 597 func (c *connection) diagnoseFiles(ctx context.Context, files []span.URI) error { 598 var untypedFiles []interface{} 599 for _, file := range files { 600 untypedFiles = append(untypedFiles, string(file)) 601 } 602 c.Client.diagnosticsMu.Lock() 603 defer c.Client.diagnosticsMu.Unlock() 604 605 c.Client.diagnosticsDone = make(chan struct{}) 606 _, err := c.Server.NonstandardRequest(ctx, "gopls/diagnoseFiles", map[string]interface{}{"files": untypedFiles}) 607 if err != nil { 608 close(c.Client.diagnosticsDone) 609 return err 610 } 611 612 <-c.Client.diagnosticsDone 613 return nil 614 } 615 616 func (c *connection) terminate(ctx context.Context) { 617 if strings.HasPrefix(c.Client.app.Remote, "internal@") { 618 // internal connections need to be left alive for the next test 619 return 620 } 621 //TODO: do we need to handle errors on these calls? 622 c.Shutdown(ctx) 623 //TODO: right now calling exit terminates the process, we should rethink that 624 //server.Exit(ctx) 625 } 626 627 // Implement io.Closer. 628 func (c *cmdClient) Close() error { 629 return nil 630 }