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/protocol" 26 "golang.org/x/tools/internal/lsp/source" 27 "golang.org/x/tools/internal/span" 28 "golang.org/x/tools/internal/telemetry/export" 29 "golang.org/x/tools/internal/telemetry/export/ocagent" 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:"*EXPERIMENTAL* - forward all commands to a remote lsp"` 62 63 // Enable verbose logging 64 Verbose bool `flag:"v" help:"Verbose output"` 65 66 // Control ocagent export of telemetry 67 OCAgent string `flag:"ocagent" help:"The address of the ocagent, or off"` 68 69 // PrepareOptions is called to update the options when a new view is built. 70 // It is primarily to allow the behavior of gopls to be modified by hooks. 71 PrepareOptions func(*source.Options) 72} 73 74// Returns a new Application ready to run. 75func New(name, wd string, env []string, options func(*source.Options)) *Application { 76 if wd == "" { 77 wd, _ = os.Getwd() 78 } 79 app := &Application{ 80 options: options, 81 name: name, 82 wd: wd, 83 env: env, 84 OCAgent: "off", //TODO: Remove this line to default the exporter to on 85 } 86 return app 87} 88 89// Name implements tool.Application returning the binary name. 90func (app *Application) Name() string { return app.name } 91 92// Usage implements tool.Application returning empty extra argument usage. 93func (app *Application) Usage() string { return "<command> [command-flags] [command-args]" } 94 95// ShortHelp implements tool.Application returning the main binary help. 96func (app *Application) ShortHelp() string { 97 return "The Go Language source tools." 98} 99 100// DetailedHelp implements tool.Application returning the main binary help. 101// This includes the short help for all the sub commands. 102func (app *Application) DetailedHelp(f *flag.FlagSet) { 103 fmt.Fprint(f.Output(), ` 104Available commands are: 105`) 106 for _, c := range app.commands() { 107 fmt.Fprintf(f.Output(), " %s : %v\n", c.Name(), c.ShortHelp()) 108 } 109 fmt.Fprint(f.Output(), ` 110gopls flags are: 111`) 112 f.PrintDefaults() 113} 114 115// Run takes the args after top level flag processing, and invokes the correct 116// sub command as specified by the first argument. 117// If no arguments are passed it will invoke the server sub command, as a 118// temporary measure for compatibility. 119func (app *Application) Run(ctx context.Context, args ...string) error { 120 ocConfig := ocagent.Discover() 121 //TODO: we should not need to adjust the discovered configuration 122 ocConfig.Address = app.OCAgent 123 export.AddExporters(ocagent.Connect(ocConfig)) 124 app.Serve.app = app 125 if len(args) == 0 { 126 return tool.Run(ctx, &app.Serve, args) 127 } 128 command, args := args[0], args[1:] 129 for _, c := range app.commands() { 130 if c.Name() == command { 131 return tool.Run(ctx, c, args) 132 } 133 } 134 return tool.CommandLineErrorf("Unknown command %v", command) 135} 136 137// commands returns the set of commands supported by the gopls tool on the 138// command line. 139// The command is specified by the first non flag argument. 140func (app *Application) commands() []tool.Application { 141 return []tool.Application{ 142 &app.Serve, 143 &bug{}, 144 &check{app: app}, 145 &foldingRanges{app: app}, 146 &format{app: app}, 147 &links{app: app}, 148 &imports{app: app}, 149 &query{app: app}, 150 &references{app: app}, 151 &rename{app: app}, 152 &signature{app: app}, 153 &suggestedfix{app: app}, 154 &symbols{app: app}, 155 &version{app: app}, 156 } 157} 158 159var ( 160 internalMu sync.Mutex 161 internalConnections = make(map[string]*connection) 162) 163 164func (app *Application) connect(ctx context.Context) (*connection, error) { 165 switch app.Remote { 166 case "": 167 connection := newConnection(app) 168 ctx, connection.Server = lsp.NewClientServer(ctx, cache.New(app.options), connection.Client) 169 return connection, connection.initialize(ctx) 170 case "internal": 171 internalMu.Lock() 172 defer internalMu.Unlock() 173 if c := internalConnections[app.wd]; c != nil { 174 return c, nil 175 } 176 connection := newConnection(app) 177 ctx := xcontext.Detach(ctx) //TODO:a way of shutting down the internal server 178 cr, sw, _ := os.Pipe() 179 sr, cw, _ := os.Pipe() 180 var jc *jsonrpc2.Conn 181 ctx, jc, connection.Server = protocol.NewClient(ctx, jsonrpc2.NewHeaderStream(cr, cw), connection.Client) 182 go jc.Run(ctx) 183 go func() { 184 ctx, srv := lsp.NewServer(ctx, cache.New(app.options), jsonrpc2.NewHeaderStream(sr, sw)) 185 srv.Run(ctx) 186 }() 187 if err := connection.initialize(ctx); err != nil { 188 return nil, err 189 } 190 internalConnections[app.wd] = connection 191 return connection, nil 192 default: 193 connection := newConnection(app) 194 conn, err := net.Dial("tcp", app.Remote) 195 if err != nil { 196 return nil, err 197 } 198 stream := jsonrpc2.NewHeaderStream(conn, conn) 199 var jc *jsonrpc2.Conn 200 ctx, jc, connection.Server = protocol.NewClient(ctx, stream, connection.Client) 201 go jc.Run(ctx) 202 return connection, connection.initialize(ctx) 203 } 204} 205 206func (c *connection) initialize(ctx context.Context) error { 207 params := &protocol.ParamInitialize{} 208 params.RootURI = string(span.FileURI(c.Client.app.wd)) 209 params.Capabilities.Workspace.Configuration = true 210 params.Capabilities.TextDocument.Hover = protocol.HoverClientCapabilities{ 211 ContentFormat: []protocol.MarkupKind{protocol.PlainText}, 212 } 213 if _, err := c.Server.Initialize(ctx, params); err != nil { 214 return err 215 } 216 if err := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil { 217 return err 218 } 219 return nil 220} 221 222type connection struct { 223 protocol.Server 224 Client *cmdClient 225} 226 227type cmdClient struct { 228 protocol.Server 229 app *Application 230 fset *token.FileSet 231 232 filesMu sync.Mutex 233 files map[span.URI]*cmdFile 234} 235 236type cmdFile struct { 237 uri span.URI 238 mapper *protocol.ColumnMapper 239 err error 240 added bool 241 hasDiagnostics chan struct{} 242 diagnosticsMu sync.Mutex 243 diagnostics []protocol.Diagnostic 244} 245 246func newConnection(app *Application) *connection { 247 return &connection{ 248 Client: &cmdClient{ 249 app: app, 250 fset: token.NewFileSet(), 251 files: make(map[span.URI]*cmdFile), 252 }, 253 } 254} 255 256func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { return nil } 257 258func (c *cmdClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) { 259 return nil, nil 260} 261 262func (c *cmdClient) LogMessage(ctx context.Context, p *protocol.LogMessageParams) error { 263 switch p.Type { 264 case protocol.Error: 265 log.Print("Error:", p.Message) 266 case protocol.Warning: 267 log.Print("Warning:", p.Message) 268 case protocol.Info: 269 if c.app.Verbose { 270 log.Print("Info:", p.Message) 271 } 272 case protocol.Log: 273 if c.app.Verbose { 274 log.Print("Log:", p.Message) 275 } 276 default: 277 if c.app.Verbose { 278 log.Print(p.Message) 279 } 280 } 281 return nil 282} 283 284func (c *cmdClient) Event(ctx context.Context, t *interface{}) error { return nil } 285 286func (c *cmdClient) RegisterCapability(ctx context.Context, p *protocol.RegistrationParams) error { 287 return nil 288} 289 290func (c *cmdClient) UnregisterCapability(ctx context.Context, p *protocol.UnregistrationParams) error { 291 return nil 292} 293 294func (c *cmdClient) WorkspaceFolders(ctx context.Context) ([]protocol.WorkspaceFolder, error) { 295 return nil, nil 296} 297 298func (c *cmdClient) Configuration(ctx context.Context, p *protocol.ParamConfiguration) ([]interface{}, error) { 299 results := make([]interface{}, len(p.Items)) 300 for i, item := range p.Items { 301 if item.Section != "gopls" { 302 continue 303 } 304 env := map[string]interface{}{} 305 for _, value := range c.app.env { 306 l := strings.SplitN(value, "=", 2) 307 if len(l) != 2 { 308 continue 309 } 310 env[l[0]] = l[1] 311 } 312 results[i] = map[string]interface{}{ 313 "env": env, 314 "go-diff": true, 315 } 316 } 317 return results, nil 318} 319 320func (c *cmdClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResponse, error) { 321 return &protocol.ApplyWorkspaceEditResponse{Applied: false, FailureReason: "not implemented"}, nil 322} 323 324func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error { 325 c.filesMu.Lock() 326 defer c.filesMu.Unlock() 327 uri := span.URI(p.URI) 328 file := c.getFile(ctx, uri) 329 file.diagnosticsMu.Lock() 330 defer file.diagnosticsMu.Unlock() 331 hadDiagnostics := file.diagnostics != nil 332 file.diagnostics = p.Diagnostics 333 if file.diagnostics == nil { 334 file.diagnostics = []protocol.Diagnostic{} 335 } 336 if !hadDiagnostics { 337 close(file.hasDiagnostics) 338 } 339 return nil 340} 341 342func (c *cmdClient) getFile(ctx context.Context, uri span.URI) *cmdFile { 343 file, found := c.files[uri] 344 if !found || file.err != nil { 345 file = &cmdFile{ 346 uri: uri, 347 hasDiagnostics: make(chan struct{}), 348 } 349 c.files[uri] = file 350 } 351 if file.mapper == nil { 352 fname := uri.Filename() 353 content, err := ioutil.ReadFile(fname) 354 if err != nil { 355 file.err = errors.Errorf("getFile: %v: %v", uri, err) 356 return file 357 } 358 f := c.fset.AddFile(fname, -1, len(content)) 359 f.SetLinesForContent(content) 360 converter := span.NewContentConverter(fname, content) 361 file.mapper = &protocol.ColumnMapper{ 362 URI: uri, 363 Converter: converter, 364 Content: content, 365 } 366 } 367 return file 368} 369 370func (c *connection) AddFile(ctx context.Context, uri span.URI) *cmdFile { 371 c.Client.filesMu.Lock() 372 defer c.Client.filesMu.Unlock() 373 374 file := c.Client.getFile(ctx, uri) 375 // This should never happen. 376 if file == nil { 377 return &cmdFile{ 378 uri: uri, 379 err: fmt.Errorf("no file found for %s", uri), 380 } 381 } 382 if file.err != nil || file.added { 383 return file 384 } 385 file.added = true 386 p := &protocol.DidOpenTextDocumentParams{} 387 p.TextDocument.URI = string(uri) 388 p.TextDocument.Text = string(file.mapper.Content) 389 p.TextDocument.LanguageID = source.DetectLanguage("", file.uri.Filename()).String() 390 if err := c.Server.DidOpen(ctx, p); err != nil { 391 file.err = errors.Errorf("%v: %v", uri, err) 392 } 393 return file 394} 395 396func (c *connection) terminate(ctx context.Context) { 397 if c.Client.app.Remote == "internal" { 398 // internal connections need to be left alive for the next test 399 return 400 } 401 //TODO: do we need to handle errors on these calls? 402 c.Shutdown(ctx) 403 //TODO: right now calling exit terminates the process, we should rethink that 404 //server.Exit(ctx) 405} 406