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{
177 func (app *Application) featureCommands() []tool.Application {
178 return []tool.Application{
179 &callHierarchy{app: app},
181 &definition{app: app},
182 &foldingRanges{app: app},
184 &highlight{app: app},
185 &implementation{app: app},
189 &prepareRename{app: app},
190 &references{app: app},
193 &signature{app: app},
194 &suggestedFix{app: app},
196 &workspace{app: app},
197 &workspaceSymbol{app: app},
202 internalMu sync.Mutex
203 internalConnections = make(map[string]*connection)
206 func (app *Application) connect(ctx context.Context) (*connection, error) {
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@"):
215 defer internalMu.Unlock()
216 opts := source.DefaultOptions().Clone()
217 if app.options != nil {
220 key := fmt.Sprintf("%s %v", app.wd, opts)
221 if c := internalConnections[key]; c != nil {
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)
230 internalConnections[key] = connection
231 return connection, nil
233 return app.connectRemote(ctx, app.Remote)
237 // CloseTestConnections terminates shared connections used in command tests. It
238 // should only be called from tests.
239 func CloseTestConnections(ctx context.Context) {
240 for _, c := range internalConnections {
246 func (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)
253 stream := jsonrpc2.NewHeaderStream(conn)
254 cc := jsonrpc2.NewConn(stream)
255 connection.Server = protocol.ServerDispatcher(cc)
256 ctx = protocol.WithClient(ctx, connection.Client)
259 protocol.ClientHandler(connection.Client,
260 jsonrpc2.MethodNotFound)))
261 return connection, connection.initialize(ctx, app.options)
264 var matcherString = map[source.SymbolMatcher]string{
265 source.SymbolFuzzy: "fuzzy",
266 source.SymbolCaseSensitive: "caseSensitive",
267 source.SymbolCaseInsensitive: "caseInsensitive",
270 func (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
275 // Make sure to respect configured options when sending initialize request.
276 opts := source.DefaultOptions().Clone()
280 params.Capabilities.TextDocument.Hover = protocol.HoverClientCapabilities{
281 ContentFormat: []protocol.MarkupKind{opts.PreferredContentFormat},
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],
293 if _, err := c.Server.Initialize(ctx, params); err != nil {
296 if err := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
302 type connection struct {
307 type cmdClient struct {
312 diagnosticsMu sync.Mutex
313 diagnosticsDone chan struct{}
316 files map[span.URI]*cmdFile
319 type cmdFile struct {
321 mapper *protocol.ColumnMapper
324 diagnostics []protocol.Diagnostic
327 func newConnection(app *Application) *connection {
331 fset: token.NewFileSet(),
332 files: make(map[span.URI]*cmdFile),
337 // fileURI converts a DocumentURI to a file:// span.URI, panicking if it's not a file.
338 func fileURI(uri protocol.DocumentURI) span.URI {
339 sURI := uri.SpanURI()
341 panic(fmt.Sprintf("%q is not a file URI", uri))
346 func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { return nil }
348 func (c *cmdClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) {
352 func (c *cmdClient) LogMessage(ctx context.Context, p *protocol.LogMessageParams) error {
355 log.Print("Error:", p.Message)
356 case protocol.Warning:
357 log.Print("Warning:", p.Message)
360 log.Print("Info:", p.Message)
364 log.Print("Log:", p.Message)
374 func (c *cmdClient) Event(ctx context.Context, t *interface{}) error { return nil }
376 func (c *cmdClient) RegisterCapability(ctx context.Context, p *protocol.RegistrationParams) error {
380 func (c *cmdClient) UnregisterCapability(ctx context.Context, p *protocol.UnregistrationParams) error {
384 func (c *cmdClient) WorkspaceFolders(ctx context.Context) ([]protocol.WorkspaceFolder, error) {
388 func (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" {
394 env := map[string]interface{}{}
395 for _, value := range c.app.env {
396 l := strings.SplitN(value, "=", 2)
402 m := map[string]interface{}{
404 "analyses": map[string]bool{
407 "noresultvalues": true,
408 "undeclaredname": true,
411 if c.app.VeryVerbose {
412 m["verboseOutput"] = true
419 func (c *cmdClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResponse, error) {
420 return &protocol.ApplyWorkspaceEditResponse{Applied: false, FailureReason: "not implemented"}, nil
423 func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error {
424 if p.URI == "gopls://diagnostics-done" {
425 close(c.diagnosticsDone)
427 // Don't worry about diagnostics without versions.
433 defer c.filesMu.Unlock()
435 file := c.getFile(ctx, fileURI(p.URI))
436 file.diagnostics = p.Diagnostics
440 func (c *cmdClient) Progress(context.Context, *protocol.ProgressParams) error {
444 func (c *cmdClient) WorkDoneProgressCreate(context.Context, *protocol.WorkDoneProgressCreateParams) error {
448 func (c *cmdClient) getFile(ctx context.Context, uri span.URI) *cmdFile {
449 file, found := c.files[uri]
450 if !found || file.err != nil {
456 if file.mapper == nil {
457 fname := uri.Filename()
458 content, err := ioutil.ReadFile(fname)
460 file.err = errors.Errorf("getFile: %v: %v", uri, err)
463 f := c.fset.AddFile(fname, -1, len(content))
464 f.SetLinesForContent(content)
465 converter := span.NewContentConverter(fname, content)
466 file.mapper = &protocol.ColumnMapper{
468 Converter: converter,
475 func (c *connection) AddFile(ctx context.Context, uri span.URI) *cmdFile {
476 c.Client.filesMu.Lock()
477 defer c.Client.filesMu.Unlock()
479 file := c.Client.getFile(ctx, uri)
480 // This should never happen.
484 err: fmt.Errorf("no file found for %s", uri),
487 if file.err != nil || file.added {
491 p := &protocol.DidOpenTextDocumentParams{
492 TextDocument: protocol.TextDocumentItem{
493 URI: protocol.URIFromSpanURI(uri),
494 LanguageID: source.DetectLanguage("", file.uri.Filename()).String(),
496 Text: string(file.mapper.Content),
499 if err := c.Server.DidOpen(ctx, p); err != nil {
500 file.err = errors.Errorf("%v: %v", uri, err)
505 func (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),
511 resp, err := c.Server.SemanticTokensFull(ctx, p)
518 func (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))
523 c.Client.diagnosticsMu.Lock()
524 defer c.Client.diagnosticsMu.Unlock()
526 c.Client.diagnosticsDone = make(chan struct{})
527 _, err := c.Server.NonstandardRequest(ctx, "gopls/diagnoseFiles", map[string]interface{}{"files": untypedFiles})
528 <-c.Client.diagnosticsDone
532 func (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
537 //TODO: do we need to handle errors on these calls?
539 //TODO: right now calling exit terminates the process, we should rethink that
543 // Implement io.Closer.
544 func (c *cmdClient) Close() error {