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", app.wd, opts)
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 params.Capabilities.TextDocument.Hover = protocol.HoverClientCapabilities{
282 ContentFormat: []protocol.MarkupKind{opts.PreferredContentFormat},
284 params.Capabilities.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport = opts.HierarchicalDocumentSymbolSupport
285 params.Capabilities.TextDocument.SemanticTokens = protocol.SemanticTokensClientCapabilities{}
286 params.Capabilities.TextDocument.SemanticTokens.Formats = []string{"relative"}
287 params.Capabilities.TextDocument.SemanticTokens.Requests.Range = true
288 params.Capabilities.TextDocument.SemanticTokens.Requests.Full = true
289 params.Capabilities.TextDocument.SemanticTokens.TokenTypes = lsp.SemanticTypes()
290 params.Capabilities.TextDocument.SemanticTokens.TokenModifiers = lsp.SemanticModifiers()
291 params.InitializationOptions = map[string]interface{}{
292 "symbolMatcher": matcherString[opts.SymbolMatcher],
294 if _, err := c.Server.Initialize(ctx, params); err != nil {
297 if err := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
303 type connection struct {
308 type cmdClient struct {
313 diagnosticsMu sync.Mutex
314 diagnosticsDone chan struct{}
317 files map[span.URI]*cmdFile
320 type cmdFile struct {
322 mapper *protocol.ColumnMapper
325 diagnostics []protocol.Diagnostic
328 func newConnection(app *Application) *connection {
332 fset: token.NewFileSet(),
333 files: make(map[span.URI]*cmdFile),
338 // fileURI converts a DocumentURI to a file:// span.URI, panicking if it's not a file.
339 func fileURI(uri protocol.DocumentURI) span.URI {
340 sURI := uri.SpanURI()
342 panic(fmt.Sprintf("%q is not a file URI", uri))
347 func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { return nil }
349 func (c *cmdClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) {
353 func (c *cmdClient) LogMessage(ctx context.Context, p *protocol.LogMessageParams) error {
356 log.Print("Error:", p.Message)
357 case protocol.Warning:
358 log.Print("Warning:", p.Message)
361 log.Print("Info:", p.Message)
365 log.Print("Log:", p.Message)
375 func (c *cmdClient) Event(ctx context.Context, t *interface{}) error { return nil }
377 func (c *cmdClient) RegisterCapability(ctx context.Context, p *protocol.RegistrationParams) error {
381 func (c *cmdClient) UnregisterCapability(ctx context.Context, p *protocol.UnregistrationParams) error {
385 func (c *cmdClient) WorkspaceFolders(ctx context.Context) ([]protocol.WorkspaceFolder, error) {
389 func (c *cmdClient) Configuration(ctx context.Context, p *protocol.ParamConfiguration) ([]interface{}, error) {
390 results := make([]interface{}, len(p.Items))
391 for i, item := range p.Items {
392 if item.Section != "gopls" {
395 env := map[string]interface{}{}
396 for _, value := range c.app.env {
397 l := strings.SplitN(value, "=", 2)
403 m := map[string]interface{}{
405 "analyses": map[string]bool{
408 "noresultvalues": true,
409 "undeclaredname": true,
412 if c.app.VeryVerbose {
413 m["verboseOutput"] = true
420 func (c *cmdClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResponse, error) {
421 return &protocol.ApplyWorkspaceEditResponse{Applied: false, FailureReason: "not implemented"}, nil
424 func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error {
425 if p.URI == "gopls://diagnostics-done" {
426 close(c.diagnosticsDone)
428 // Don't worry about diagnostics without versions.
434 defer c.filesMu.Unlock()
436 file := c.getFile(ctx, fileURI(p.URI))
437 file.diagnostics = p.Diagnostics
441 func (c *cmdClient) Progress(context.Context, *protocol.ProgressParams) error {
445 func (c *cmdClient) WorkDoneProgressCreate(context.Context, *protocol.WorkDoneProgressCreateParams) error {
449 func (c *cmdClient) getFile(ctx context.Context, uri span.URI) *cmdFile {
450 file, found := c.files[uri]
451 if !found || file.err != nil {
457 if file.mapper == nil {
458 fname := uri.Filename()
459 content, err := ioutil.ReadFile(fname)
461 file.err = errors.Errorf("getFile: %v: %v", uri, err)
464 f := c.fset.AddFile(fname, -1, len(content))
465 f.SetLinesForContent(content)
466 converter := span.NewContentConverter(fname, content)
467 file.mapper = &protocol.ColumnMapper{
469 Converter: converter,
476 func (c *connection) AddFile(ctx context.Context, uri span.URI) *cmdFile {
477 c.Client.filesMu.Lock()
478 defer c.Client.filesMu.Unlock()
480 file := c.Client.getFile(ctx, uri)
481 // This should never happen.
485 err: fmt.Errorf("no file found for %s", uri),
488 if file.err != nil || file.added {
492 p := &protocol.DidOpenTextDocumentParams{
493 TextDocument: protocol.TextDocumentItem{
494 URI: protocol.URIFromSpanURI(uri),
495 LanguageID: source.DetectLanguage("", file.uri.Filename()).String(),
497 Text: string(file.mapper.Content),
500 if err := c.Server.DidOpen(ctx, p); err != nil {
501 file.err = errors.Errorf("%v: %v", uri, err)
506 func (c *connection) semanticTokens(ctx context.Context, file span.URI) (*protocol.SemanticTokens, error) {
507 p := &protocol.SemanticTokensParams{
508 TextDocument: protocol.TextDocumentIdentifier{
509 URI: protocol.URIFromSpanURI(file),
512 resp, err := c.Server.SemanticTokensFull(ctx, p)
519 func (c *connection) diagnoseFiles(ctx context.Context, files []span.URI) error {
520 var untypedFiles []interface{}
521 for _, file := range files {
522 untypedFiles = append(untypedFiles, string(file))
524 c.Client.diagnosticsMu.Lock()
525 defer c.Client.diagnosticsMu.Unlock()
527 c.Client.diagnosticsDone = make(chan struct{})
528 _, err := c.Server.NonstandardRequest(ctx, "gopls/diagnoseFiles", map[string]interface{}{"files": untypedFiles})
529 <-c.Client.diagnosticsDone
533 func (c *connection) terminate(ctx context.Context) {
534 if strings.HasPrefix(c.Client.app.Remote, "internal@") {
535 // internal connections need to be left alive for the next test
538 //TODO: do we need to handle errors on these calls?
540 //TODO: right now calling exit terminates the process, we should rethink that
544 // Implement io.Closer.
545 func (c *cmdClient) Close() error {