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.
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.
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"
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.
37 type Application struct {
38 // Core application flags
40 // Embed the basic profiling flags supported by the tool package
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.
48 // the options configuring function to invoke when building a server
49 options func(*source.Options)
51 // The name of the binary, used in help and telemetry.
54 // The working directory to run commands in.
57 // The environment variables to use.
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."`
63 // Verbose enables verbose logging.
64 Verbose bool `flag:"v" help:"verbose output"`
66 // VeryVerbose enables a higher level of verbosity in logging output.
67 VeryVerbose bool `flag:"vv" help:"very verbose output"`
69 // Control ocagent export of telemetry
70 OCAgent string `flag:"ocagent" help:"the address of the ocagent (e.g. http://localhost:55678), or off"`
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)
77 func (app *Application) verbose() bool {
78 return app.Verbose || app.VeryVerbose
81 // New returns a new Application ready to run.
82 func New(name, wd string, env []string, options func(*source.Options)) *Application {
91 OCAgent: "off", //TODO: Remove this line to default the exporter to on
94 RemoteListenTimeout: 1 * time.Minute,
100 // Name implements tool.Application returning the binary name.
101 func (app *Application) Name() string { return app.name }
103 // Usage implements tool.Application returning empty extra argument usage.
104 func (app *Application) Usage() string { return "<command> [command-flags] [command-args]" }
106 // ShortHelp implements tool.Application returning the main binary help.
107 func (app *Application) ShortHelp() string {
108 return "The Go Language source tools."
111 // DetailedHelp implements tool.Application returning the main binary help.
112 // This includes the short help for all the sub commands.
113 func (app *Application) DetailedHelp(f *flag.FlagSet) {
114 fmt.Fprint(f.Output(), `
115 gopls is a Go language server. It is typically used with an editor to provide
116 language features. When no command is specified, gopls will default to the 'serve'
117 command. The language features can also be accessed via the gopls command-line interface.
119 Available commands are:
121 fmt.Fprint(f.Output(), `
124 for _, c := range app.mainCommands() {
125 fmt.Fprintf(f.Output(), " %s : %v\n", c.Name(), c.ShortHelp())
127 fmt.Fprint(f.Output(), `
130 for _, c := range app.featureCommands() {
131 fmt.Fprintf(f.Output(), " %s : %v\n", c.Name(), c.ShortHelp())
133 fmt.Fprint(f.Output(), `
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.
143 func (app *Application) Run(ctx context.Context, args ...string) error {
144 ctx = debug.WithInstance(ctx, app.wd, app.OCAgent)
147 return tool.Run(ctx, &app.Serve, args)
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)
155 return tool.CommandLineErrorf("Unknown command %v", command)
158 // commands returns the set of commands supported by the gopls tool on the
160 // The command is specified by the first non flag argument.
161 func (app *Application) commands() []tool.Application {
162 var commands []tool.Application
163 commands = append(commands, app.mainCommands()...)
164 commands = append(commands, app.featureCommands()...)
168 func (app *Application) mainCommands() []tool.Application {
169 return []tool.Application{
178 func (app *Application) featureCommands() []tool.Application {
179 return []tool.Application{
180 &callHierarchy{app: app},
182 &definition{app: app},
183 &foldingRanges{app: app},
185 &highlight{app: app},
186 &implementation{app: app},
190 &prepareRename{app: app},
191 &references{app: app},
194 &signature{app: app},
195 &suggestedFix{app: app},
197 &workspace{app: app},
198 &workspaceSymbol{app: app},
203 internalMu sync.Mutex
204 internalConnections = make(map[string]*connection)
207 func (app *Application) connect(ctx context.Context) (*connection, error) {
209 case app.Remote == "":
210 connection := newConnection(app)
211 connection.Server = lsp.NewServer(cache.New(ctx, app.options).NewSession(ctx), connection.Client)
212 ctx = protocol.WithClient(ctx, connection.Client)
213 return connection, connection.initialize(ctx, app.options)
214 case strings.HasPrefix(app.Remote, "internal@"):
216 defer internalMu.Unlock()
217 opts := source.DefaultOptions().Clone()
218 if app.options != nil {
221 key := fmt.Sprintf("%s %v %v %v", app.wd, opts.PreferredContentFormat, opts.HierarchicalDocumentSymbolSupport, opts.SymbolMatcher)
222 if c := internalConnections[key]; c != nil {
225 remote := app.Remote[len("internal@"):]
226 ctx := xcontext.Detach(ctx) //TODO:a way of shutting down the internal server
227 connection, err := app.connectRemote(ctx, remote)
231 internalConnections[key] = connection
232 return connection, nil
234 return app.connectRemote(ctx, app.Remote)
238 // CloseTestConnections terminates shared connections used in command tests. It
239 // should only be called from tests.
240 func CloseTestConnections(ctx context.Context) {
241 for _, c := range internalConnections {
247 func (app *Application) connectRemote(ctx context.Context, remote string) (*connection, error) {
248 connection := newConnection(app)
249 network, addr := parseAddr(remote)
250 conn, err := lsprpc.ConnectToRemote(ctx, network, addr)
254 stream := jsonrpc2.NewHeaderStream(conn)
255 cc := jsonrpc2.NewConn(stream)
256 connection.Server = protocol.ServerDispatcher(cc)
257 ctx = protocol.WithClient(ctx, connection.Client)
260 protocol.ClientHandler(connection.Client,
261 jsonrpc2.MethodNotFound)))
262 return connection, connection.initialize(ctx, app.options)
265 var matcherString = map[source.SymbolMatcher]string{
266 source.SymbolFuzzy: "fuzzy",
267 source.SymbolCaseSensitive: "caseSensitive",
268 source.SymbolCaseInsensitive: "caseInsensitive",
271 func (c *connection) initialize(ctx context.Context, options func(*source.Options)) error {
272 params := &protocol.ParamInitialize{}
273 params.RootURI = protocol.URIFromPath(c.Client.app.wd)
274 params.Capabilities.Workspace.Configuration = true
276 // Make sure to respect configured options when sending initialize request.
277 opts := source.DefaultOptions().Clone()
281 // If you add an additional option here, you must update the map key in connect.
282 params.Capabilities.TextDocument.Hover = protocol.HoverClientCapabilities{
283 ContentFormat: []protocol.MarkupKind{opts.PreferredContentFormat},
285 params.Capabilities.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport = opts.HierarchicalDocumentSymbolSupport
286 params.Capabilities.TextDocument.SemanticTokens = protocol.SemanticTokensClientCapabilities{}
287 params.Capabilities.TextDocument.SemanticTokens.Formats = []string{"relative"}
288 params.Capabilities.TextDocument.SemanticTokens.Requests.Range = true
289 params.Capabilities.TextDocument.SemanticTokens.Requests.Full = true
290 params.Capabilities.TextDocument.SemanticTokens.TokenTypes = lsp.SemanticTypes()
291 params.Capabilities.TextDocument.SemanticTokens.TokenModifiers = lsp.SemanticModifiers()
292 params.InitializationOptions = map[string]interface{}{
293 "symbolMatcher": matcherString[opts.SymbolMatcher],
295 if _, err := c.Server.Initialize(ctx, params); err != nil {
298 if err := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
304 type connection struct {
309 type cmdClient struct {
314 diagnosticsMu sync.Mutex
315 diagnosticsDone chan struct{}
318 files map[span.URI]*cmdFile
321 type cmdFile struct {
323 mapper *protocol.ColumnMapper
326 diagnostics []protocol.Diagnostic
329 func newConnection(app *Application) *connection {
333 fset: token.NewFileSet(),
334 files: make(map[span.URI]*cmdFile),
339 // fileURI converts a DocumentURI to a file:// span.URI, panicking if it's not a file.
340 func fileURI(uri protocol.DocumentURI) span.URI {
341 sURI := uri.SpanURI()
343 panic(fmt.Sprintf("%q is not a file URI", uri))
348 func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { return nil }
350 func (c *cmdClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) {
354 func (c *cmdClient) LogMessage(ctx context.Context, p *protocol.LogMessageParams) error {
357 log.Print("Error:", p.Message)
358 case protocol.Warning:
359 log.Print("Warning:", p.Message)
362 log.Print("Info:", p.Message)
366 log.Print("Log:", p.Message)
376 func (c *cmdClient) Event(ctx context.Context, t *interface{}) error { return nil }
378 func (c *cmdClient) RegisterCapability(ctx context.Context, p *protocol.RegistrationParams) error {
382 func (c *cmdClient) UnregisterCapability(ctx context.Context, p *protocol.UnregistrationParams) error {
386 func (c *cmdClient) WorkspaceFolders(ctx context.Context) ([]protocol.WorkspaceFolder, error) {
390 func (c *cmdClient) Configuration(ctx context.Context, p *protocol.ParamConfiguration) ([]interface{}, error) {
391 results := make([]interface{}, len(p.Items))
392 for i, item := range p.Items {
393 if item.Section != "gopls" {
396 env := map[string]interface{}{}
397 for _, value := range c.app.env {
398 l := strings.SplitN(value, "=", 2)
404 m := map[string]interface{}{
406 "analyses": map[string]bool{
409 "noresultvalues": true,
410 "undeclaredname": true,
413 if c.app.VeryVerbose {
414 m["verboseOutput"] = true
421 func (c *cmdClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResponse, error) {
422 return &protocol.ApplyWorkspaceEditResponse{Applied: false, FailureReason: "not implemented"}, nil
425 func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error {
426 if p.URI == "gopls://diagnostics-done" {
427 close(c.diagnosticsDone)
429 // Don't worry about diagnostics without versions.
435 defer c.filesMu.Unlock()
437 file := c.getFile(ctx, fileURI(p.URI))
438 file.diagnostics = p.Diagnostics
442 func (c *cmdClient) Progress(context.Context, *protocol.ProgressParams) error {
446 func (c *cmdClient) WorkDoneProgressCreate(context.Context, *protocol.WorkDoneProgressCreateParams) error {
450 func (c *cmdClient) getFile(ctx context.Context, uri span.URI) *cmdFile {
451 file, found := c.files[uri]
452 if !found || file.err != nil {
458 if file.mapper == nil {
459 fname := uri.Filename()
460 content, err := ioutil.ReadFile(fname)
462 file.err = errors.Errorf("getFile: %v: %v", uri, err)
465 f := c.fset.AddFile(fname, -1, len(content))
466 f.SetLinesForContent(content)
467 converter := span.NewContentConverter(fname, content)
468 file.mapper = &protocol.ColumnMapper{
470 Converter: converter,
477 func (c *connection) AddFile(ctx context.Context, uri span.URI) *cmdFile {
478 c.Client.filesMu.Lock()
479 defer c.Client.filesMu.Unlock()
481 file := c.Client.getFile(ctx, uri)
482 // This should never happen.
486 err: fmt.Errorf("no file found for %s", uri),
489 if file.err != nil || file.added {
493 p := &protocol.DidOpenTextDocumentParams{
494 TextDocument: protocol.TextDocumentItem{
495 URI: protocol.URIFromSpanURI(uri),
496 LanguageID: source.DetectLanguage("", file.uri.Filename()).String(),
498 Text: string(file.mapper.Content),
501 if err := c.Server.DidOpen(ctx, p); err != nil {
502 file.err = errors.Errorf("%v: %v", uri, err)
507 func (c *connection) semanticTokens(ctx context.Context, file span.URI) (*protocol.SemanticTokens, error) {
508 p := &protocol.SemanticTokensParams{
509 TextDocument: protocol.TextDocumentIdentifier{
510 URI: protocol.URIFromSpanURI(file),
513 resp, err := c.Server.SemanticTokensFull(ctx, p)
520 func (c *connection) diagnoseFiles(ctx context.Context, files []span.URI) error {
521 var untypedFiles []interface{}
522 for _, file := range files {
523 untypedFiles = append(untypedFiles, string(file))
525 c.Client.diagnosticsMu.Lock()
526 defer c.Client.diagnosticsMu.Unlock()
528 c.Client.diagnosticsDone = make(chan struct{})
529 _, err := c.Server.NonstandardRequest(ctx, "gopls/diagnoseFiles", map[string]interface{}{"files": untypedFiles})
530 <-c.Client.diagnosticsDone
534 func (c *connection) terminate(ctx context.Context) {
535 if strings.HasPrefix(c.Client.app.Remote, "internal@") {
536 // internal connections need to be left alive for the next test
539 //TODO: do we need to handle errors on these calls?
541 //TODO: right now calling exit terminates the process, we should rethink that
545 // Implement io.Closer.
546 func (c *cmdClient) Close() error {