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