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	"os"
18	"strings"
19	"sync"
20	"time"
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/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"
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:"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."`
62
63	// Verbose enables verbose logging.
64	Verbose bool `flag:"v" help:"verbose output"`
65
66	// VeryVerbose enables a higher level of verbosity in logging output.
67	VeryVerbose bool `flag:"vv" help:"very verbose output"`
68
69	// Control ocagent export of telemetry
70	OCAgent string `flag:"ocagent" help:"the address of the ocagent (e.g. http://localhost:55678), or off"`
71
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)
75}
76
77func (app *Application) verbose() bool {
78	return app.Verbose || app.VeryVerbose
79}
80
81// New returns a new Application ready to run.
82func New(name, wd string, env []string, options func(*source.Options)) *Application {
83	if wd == "" {
84		wd, _ = os.Getwd()
85	}
86	app := &Application{
87		options: options,
88		name:    name,
89		wd:      wd,
90		env:     env,
91		OCAgent: "off", //TODO: Remove this line to default the exporter to on
92
93		Serve: Serve{
94			RemoteListenTimeout: 1 * time.Minute,
95		},
96	}
97	return app
98}
99
100// Name implements tool.Application returning the binary name.
101func (app *Application) Name() string { return app.name }
102
103// Usage implements tool.Application returning empty extra argument usage.
104func (app *Application) Usage() string { return "<command> [command-flags] [command-args]" }
105
106// ShortHelp implements tool.Application returning the main binary help.
107func (app *Application) ShortHelp() string {
108	return "The Go Language source tools."
109}
110
111// DetailedHelp implements tool.Application returning the main binary help.
112// This includes the short help for all the sub commands.
113func (app *Application) DetailedHelp(f *flag.FlagSet) {
114	fmt.Fprint(f.Output(), `
115gopls is a Go language server. It is typically used with an editor to provide
116language features. When no command is specified, gopls will default to the 'serve'
117command. The language features can also be accessed via the gopls command-line interface.
118
119Available commands are:
120`)
121	fmt.Fprint(f.Output(), `
122main:
123`)
124	for _, c := range app.mainCommands() {
125		fmt.Fprintf(f.Output(), "  %s : %v\n", c.Name(), c.ShortHelp())
126	}
127	fmt.Fprint(f.Output(), `
128features:
129`)
130	for _, c := range app.featureCommands() {
131		fmt.Fprintf(f.Output(), "  %s : %v\n", c.Name(), c.ShortHelp())
132	}
133	fmt.Fprint(f.Output(), `
134gopls flags are:
135`)
136	f.PrintDefaults()
137}
138
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.
143func (app *Application) Run(ctx context.Context, args ...string) error {
144	ctx = debug.WithInstance(ctx, app.wd, app.OCAgent)
145	app.Serve.app = app
146	if len(args) == 0 {
147		return tool.Run(ctx, &app.Serve, args)
148	}
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)
153		}
154	}
155	return tool.CommandLineErrorf("Unknown command %v", command)
156}
157
158// commands returns the set of commands supported by the gopls tool on the
159// command line.
160// The command is specified by the first non flag argument.
161func (app *Application) commands() []tool.Application {
162	var commands []tool.Application
163	commands = append(commands, app.mainCommands()...)
164	commands = append(commands, app.featureCommands()...)
165	return commands
166}
167
168func (app *Application) mainCommands() []tool.Application {
169	return []tool.Application{
170		&app.Serve,
171		&version{app: app},
172		&bug{},
173		&apiJSON{},
174	}
175}
176
177func (app *Application) featureCommands() []tool.Application {
178	return []tool.Application{
179		&callHierarchy{app: app},
180		&check{app: app},
181		&definition{app: app},
182		&foldingRanges{app: app},
183		&format{app: app},
184		&highlight{app: app},
185		&implementation{app: app},
186		&imports{app: app},
187		&inspect{app: app},
188		&links{app: app},
189		&prepareRename{app: app},
190		&references{app: app},
191		&rename{app: app},
192		&semtok{app: app},
193		&signature{app: app},
194		&suggestedFix{app: app},
195		&symbols{app: app},
196		&workspace{app: app},
197		&workspaceSymbol{app: app},
198	}
199}
200
201var (
202	internalMu          sync.Mutex
203	internalConnections = make(map[string]*connection)
204)
205
206func (app *Application) connect(ctx context.Context) (*connection, error) {
207	switch {
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@"):
214		internalMu.Lock()
215		defer internalMu.Unlock()
216		opts := source.DefaultOptions().Clone()
217		if app.options != nil {
218			app.options(opts)
219		}
220		key := fmt.Sprintf("%s %v", app.wd, opts)
221		if c := internalConnections[key]; c != nil {
222			return c, nil
223		}
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)
227		if err != nil {
228			return nil, err
229		}
230		internalConnections[key] = connection
231		return connection, nil
232	default:
233		return app.connectRemote(ctx, app.Remote)
234	}
235}
236
237// CloseTestConnections terminates shared connections used in command tests. It
238// should only be called from tests.
239func CloseTestConnections(ctx context.Context) {
240	for _, c := range internalConnections {
241		c.Shutdown(ctx)
242		c.Exit(ctx)
243	}
244}
245
246func (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)
250	if err != nil {
251		return nil, err
252	}
253	stream := jsonrpc2.NewHeaderStream(conn)
254	cc := jsonrpc2.NewConn(stream)
255	connection.Server = protocol.ServerDispatcher(cc)
256	ctx = protocol.WithClient(ctx, connection.Client)
257	cc.Go(ctx,
258		protocol.Handlers(
259			protocol.ClientHandler(connection.Client,
260				jsonrpc2.MethodNotFound)))
261	return connection, connection.initialize(ctx, app.options)
262}
263
264var matcherString = map[source.SymbolMatcher]string{
265	source.SymbolFuzzy:           "fuzzy",
266	source.SymbolCaseSensitive:   "caseSensitive",
267	source.SymbolCaseInsensitive: "caseInsensitive",
268}
269
270func (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
274
275	// Make sure to respect configured options when sending initialize request.
276	opts := source.DefaultOptions().Clone()
277	if options != nil {
278		options(opts)
279	}
280	params.Capabilities.TextDocument.Hover = protocol.HoverClientCapabilities{
281		ContentFormat: []protocol.MarkupKind{opts.PreferredContentFormat},
282	}
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],
292	}
293	if _, err := c.Server.Initialize(ctx, params); err != nil {
294		return err
295	}
296	if err := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
297		return err
298	}
299	return nil
300}
301
302type connection struct {
303	protocol.Server
304	Client *cmdClient
305}
306
307type cmdClient struct {
308	protocol.Server
309	app  *Application
310	fset *token.FileSet
311
312	diagnosticsMu   sync.Mutex
313	diagnosticsDone chan struct{}
314
315	filesMu sync.Mutex
316	files   map[span.URI]*cmdFile
317}
318
319type cmdFile struct {
320	uri         span.URI
321	mapper      *protocol.ColumnMapper
322	err         error
323	added       bool
324	diagnostics []protocol.Diagnostic
325}
326
327func newConnection(app *Application) *connection {
328	return &connection{
329		Client: &cmdClient{
330			app:   app,
331			fset:  token.NewFileSet(),
332			files: make(map[span.URI]*cmdFile),
333		},
334	}
335}
336
337// fileURI converts a DocumentURI to a file:// span.URI, panicking if it's not a file.
338func fileURI(uri protocol.DocumentURI) span.URI {
339	sURI := uri.SpanURI()
340	if !sURI.IsFile() {
341		panic(fmt.Sprintf("%q is not a file URI", uri))
342	}
343	return sURI
344}
345
346func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { return nil }
347
348func (c *cmdClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) {
349	return nil, nil
350}
351
352func (c *cmdClient) LogMessage(ctx context.Context, p *protocol.LogMessageParams) error {
353	switch p.Type {
354	case protocol.Error:
355		log.Print("Error:", p.Message)
356	case protocol.Warning:
357		log.Print("Warning:", p.Message)
358	case protocol.Info:
359		if c.app.verbose() {
360			log.Print("Info:", p.Message)
361		}
362	case protocol.Log:
363		if c.app.verbose() {
364			log.Print("Log:", p.Message)
365		}
366	default:
367		if c.app.verbose() {
368			log.Print(p.Message)
369		}
370	}
371	return nil
372}
373
374func (c *cmdClient) Event(ctx context.Context, t *interface{}) error { return nil }
375
376func (c *cmdClient) RegisterCapability(ctx context.Context, p *protocol.RegistrationParams) error {
377	return nil
378}
379
380func (c *cmdClient) UnregisterCapability(ctx context.Context, p *protocol.UnregistrationParams) error {
381	return nil
382}
383
384func (c *cmdClient) WorkspaceFolders(ctx context.Context) ([]protocol.WorkspaceFolder, error) {
385	return nil, nil
386}
387
388func (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" {
392			continue
393		}
394		env := map[string]interface{}{}
395		for _, value := range c.app.env {
396			l := strings.SplitN(value, "=", 2)
397			if len(l) != 2 {
398				continue
399			}
400			env[l[0]] = l[1]
401		}
402		m := map[string]interface{}{
403			"env": env,
404			"analyses": map[string]bool{
405				"fillreturns":    true,
406				"nonewvars":      true,
407				"noresultvalues": true,
408				"undeclaredname": true,
409			},
410		}
411		if c.app.VeryVerbose {
412			m["verboseOutput"] = true
413		}
414		results[i] = m
415	}
416	return results, nil
417}
418
419func (c *cmdClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResponse, error) {
420	return &protocol.ApplyWorkspaceEditResponse{Applied: false, FailureReason: "not implemented"}, nil
421}
422
423func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error {
424	if p.URI == "gopls://diagnostics-done" {
425		close(c.diagnosticsDone)
426	}
427	// Don't worry about diagnostics without versions.
428	if p.Version == 0 {
429		return nil
430	}
431
432	c.filesMu.Lock()
433	defer c.filesMu.Unlock()
434
435	file := c.getFile(ctx, fileURI(p.URI))
436	file.diagnostics = p.Diagnostics
437	return nil
438}
439
440func (c *cmdClient) Progress(context.Context, *protocol.ProgressParams) error {
441	return nil
442}
443
444func (c *cmdClient) WorkDoneProgressCreate(context.Context, *protocol.WorkDoneProgressCreateParams) error {
445	return nil
446}
447
448func (c *cmdClient) getFile(ctx context.Context, uri span.URI) *cmdFile {
449	file, found := c.files[uri]
450	if !found || file.err != nil {
451		file = &cmdFile{
452			uri: uri,
453		}
454		c.files[uri] = file
455	}
456	if file.mapper == nil {
457		fname := uri.Filename()
458		content, err := ioutil.ReadFile(fname)
459		if err != nil {
460			file.err = errors.Errorf("getFile: %v: %v", uri, err)
461			return file
462		}
463		f := c.fset.AddFile(fname, -1, len(content))
464		f.SetLinesForContent(content)
465		converter := span.NewContentConverter(fname, content)
466		file.mapper = &protocol.ColumnMapper{
467			URI:       uri,
468			Converter: converter,
469			Content:   content,
470		}
471	}
472	return file
473}
474
475func (c *connection) AddFile(ctx context.Context, uri span.URI) *cmdFile {
476	c.Client.filesMu.Lock()
477	defer c.Client.filesMu.Unlock()
478
479	file := c.Client.getFile(ctx, uri)
480	// This should never happen.
481	if file == nil {
482		return &cmdFile{
483			uri: uri,
484			err: fmt.Errorf("no file found for %s", uri),
485		}
486	}
487	if file.err != nil || file.added {
488		return file
489	}
490	file.added = true
491	p := &protocol.DidOpenTextDocumentParams{
492		TextDocument: protocol.TextDocumentItem{
493			URI:        protocol.URIFromSpanURI(uri),
494			LanguageID: source.DetectLanguage("", file.uri.Filename()).String(),
495			Version:    1,
496			Text:       string(file.mapper.Content),
497		},
498	}
499	if err := c.Server.DidOpen(ctx, p); err != nil {
500		file.err = errors.Errorf("%v: %v", uri, err)
501	}
502	return file
503}
504
505func (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),
509		},
510	}
511	resp, err := c.Server.SemanticTokensFull(ctx, p)
512	if err != nil {
513		return nil, err
514	}
515	return resp, nil
516}
517
518func (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))
522	}
523	c.Client.diagnosticsMu.Lock()
524	defer c.Client.diagnosticsMu.Unlock()
525
526	c.Client.diagnosticsDone = make(chan struct{})
527	_, err := c.Server.NonstandardRequest(ctx, "gopls/diagnoseFiles", map[string]interface{}{"files": untypedFiles})
528	<-c.Client.diagnosticsDone
529	return err
530}
531
532func (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
535		return
536	}
537	//TODO: do we need to handle errors on these calls?
538	c.Shutdown(ctx)
539	//TODO: right now calling exit terminates the process, we should rethink that
540	//server.Exit(ctx)
541}
542
543// Implement io.Closer.
544func (c *cmdClient) Close() error {
545	return nil
546}
547