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