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/debug"
26	"golang.org/x/tools/internal/lsp/protocol"
27	"golang.org/x/tools/internal/lsp/source"
28	"golang.org/x/tools/internal/span"
29	"golang.org/x/tools/internal/tool"
30	"golang.org/x/tools/internal/xcontext"
31	errors "golang.org/x/xerrors"
32)
33
34// Application is the main application as passed to tool.Main
35// It handles the main command line parsing and dispatch to the sub commands.
36type Application struct {
37	// Core application flags
38
39	// Embed the basic profiling flags supported by the tool package
40	tool.Profile
41
42	// We include the server configuration directly for now, so the flags work
43	// even without the verb.
44	// TODO: Remove this when we stop allowing the serve verb by default.
45	Serve Serve
46
47	// the options configuring function to invoke when building a server
48	options func(*source.Options)
49
50	// The name of the binary, used in help and telemetry.
51	name string
52
53	// The working directory to run commands in.
54	wd string
55
56	// The environment variables to use.
57	env []string
58
59	// Support for remote lsp server
60	Remote string `flag:"remote" help:"*EXPERIMENTAL* - forward all commands to a remote lsp specified by this flag. 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. Otherwise, TCP is used."`
61
62	// Enable verbose logging
63	Verbose bool `flag:"v" help:"verbose output"`
64
65	// Control ocagent export of telemetry
66	OCAgent string `flag:"ocagent" help:"the address of the ocagent, or off"`
67
68	// PrepareOptions is called to update the options when a new view is built.
69	// It is primarily to allow the behavior of gopls to be modified by hooks.
70	PrepareOptions func(*source.Options)
71
72	debug *debug.Instance
73}
74
75// New returns a new Application ready to run.
76func New(name, wd string, env []string, options func(*source.Options)) *Application {
77	if wd == "" {
78		wd, _ = os.Getwd()
79	}
80	app := &Application{
81		options: options,
82		name:    name,
83		wd:      wd,
84		env:     env,
85		OCAgent: "off", //TODO: Remove this line to default the exporter to on
86	}
87	return app
88}
89
90// Name implements tool.Application returning the binary name.
91func (app *Application) Name() string { return app.name }
92
93// Usage implements tool.Application returning empty extra argument usage.
94func (app *Application) Usage() string { return "<command> [command-flags] [command-args]" }
95
96// ShortHelp implements tool.Application returning the main binary help.
97func (app *Application) ShortHelp() string {
98	return "The Go Language source tools."
99}
100
101// DetailedHelp implements tool.Application returning the main binary help.
102// This includes the short help for all the sub commands.
103func (app *Application) DetailedHelp(f *flag.FlagSet) {
104	fmt.Fprint(f.Output(), `
105gopls is a Go language server. It is typically used with an editor to provide
106language features. When no command is specified, gopls will default to the 'serve'
107command. The language features can also be accessed via the gopls command-line interface.
108
109Available commands are:
110`)
111	fmt.Fprint(f.Output(), `
112main:
113`)
114	for _, c := range app.mainCommands() {
115		fmt.Fprintf(f.Output(), "  %s : %v\n", c.Name(), c.ShortHelp())
116	}
117	fmt.Fprint(f.Output(), `
118features:
119`)
120	for _, c := range app.featureCommands() {
121		fmt.Fprintf(f.Output(), "  %s : %v\n", c.Name(), c.ShortHelp())
122	}
123	fmt.Fprint(f.Output(), `
124gopls flags are:
125`)
126	f.PrintDefaults()
127}
128
129// Run takes the args after top level flag processing, and invokes the correct
130// sub command as specified by the first argument.
131// If no arguments are passed it will invoke the server sub command, as a
132// temporary measure for compatibility.
133func (app *Application) Run(ctx context.Context, args ...string) error {
134	app.debug = debug.NewInstance(app.wd, app.OCAgent)
135	app.Serve.app = app
136	if len(args) == 0 {
137		return tool.Run(ctx, &app.Serve, args)
138	}
139	command, args := args[0], args[1:]
140	for _, c := range app.commands() {
141		if c.Name() == command {
142			return tool.Run(ctx, c, args)
143		}
144	}
145	return tool.CommandLineErrorf("Unknown command %v", command)
146}
147
148// commands returns the set of commands supported by the gopls tool on the
149// command line.
150// The command is specified by the first non flag argument.
151func (app *Application) commands() []tool.Application {
152	var commands []tool.Application
153	commands = append(commands, app.mainCommands()...)
154	commands = append(commands, app.featureCommands()...)
155	return commands
156}
157
158func (app *Application) mainCommands() []tool.Application {
159	return []tool.Application{
160		&app.Serve,
161		&version{app: app},
162		&bug{},
163	}
164}
165
166func (app *Application) featureCommands() []tool.Application {
167	return []tool.Application{
168		&check{app: app},
169		&foldingRanges{app: app},
170		&format{app: app},
171		&highlight{app: app},
172		&implementation{app: app},
173		&imports{app: app},
174		&links{app: app},
175		&prepareRename{app: app},
176		&query{app: app},
177		&references{app: app},
178		&rename{app: app},
179		&signature{app: app},
180		&suggestedfix{app: app},
181		&symbols{app: app},
182	}
183}
184
185var (
186	internalMu          sync.Mutex
187	internalConnections = make(map[string]*connection)
188)
189
190func (app *Application) connect(ctx context.Context) (*connection, error) {
191	switch {
192	case app.Remote == "":
193		connection := newConnection(app)
194		connection.Server = lsp.NewServer(cache.New(app.options, nil).NewSession(), connection.Client)
195		ctx = protocol.WithClient(ctx, connection.Client)
196		return connection, connection.initialize(ctx, app.options)
197	case strings.HasPrefix(app.Remote, "internal@"):
198		internalMu.Lock()
199		defer internalMu.Unlock()
200		if c := internalConnections[app.wd]; c != nil {
201			return c, nil
202		}
203		remote := app.Remote[len("internal@"):]
204		ctx := xcontext.Detach(ctx) //TODO:a way of shutting down the internal server
205		connection, err := app.connectRemote(ctx, remote)
206		if err != nil {
207			return nil, err
208		}
209		internalConnections[app.wd] = connection
210		return connection, nil
211	default:
212		return app.connectRemote(ctx, app.Remote)
213	}
214}
215
216func (app *Application) connectRemote(ctx context.Context, remote string) (*connection, error) {
217	connection := newConnection(app)
218	conn, err := net.Dial("tcp", remote)
219	if err != nil {
220		return nil, err
221	}
222	stream := jsonrpc2.NewHeaderStream(conn, conn)
223	cc := jsonrpc2.NewConn(stream)
224	connection.Server = protocol.ServerDispatcher(cc)
225	cc.AddHandler(protocol.ClientHandler(connection.Client))
226	cc.AddHandler(protocol.Canceller{})
227	ctx = protocol.WithClient(ctx, connection.Client)
228	go cc.Run(ctx)
229	return connection, connection.initialize(ctx, app.options)
230}
231
232func (c *connection) initialize(ctx context.Context, options func(*source.Options)) error {
233	params := &protocol.ParamInitialize{}
234	params.RootURI = protocol.URIFromPath(c.Client.app.wd)
235	params.Capabilities.Workspace.Configuration = true
236
237	// Make sure to respect configured options when sending initialize request.
238	opts := source.DefaultOptions()
239	if options != nil {
240		options(&opts)
241	}
242	params.Capabilities.TextDocument.Hover = protocol.HoverClientCapabilities{
243		ContentFormat: []protocol.MarkupKind{opts.PreferredContentFormat},
244	}
245
246	if _, err := c.Server.Initialize(ctx, params); err != nil {
247		return err
248	}
249	if err := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
250		return err
251	}
252	return nil
253}
254
255type connection struct {
256	protocol.Server
257	Client *cmdClient
258}
259
260type cmdClient struct {
261	protocol.Server
262	app  *Application
263	fset *token.FileSet
264
265	diagnosticsMu   sync.Mutex
266	diagnosticsDone chan struct{}
267
268	filesMu sync.Mutex
269	files   map[span.URI]*cmdFile
270}
271
272type cmdFile struct {
273	uri         span.URI
274	mapper      *protocol.ColumnMapper
275	err         error
276	added       bool
277	diagnostics []protocol.Diagnostic
278}
279
280func newConnection(app *Application) *connection {
281	return &connection{
282		Client: &cmdClient{
283			app:   app,
284			fset:  token.NewFileSet(),
285			files: make(map[span.URI]*cmdFile),
286		},
287	}
288}
289
290// fileURI converts a DocumentURI to a file:// span.URI, panicking if it's not a file.
291func fileURI(uri protocol.DocumentURI) span.URI {
292	sURI := uri.SpanURI()
293	if !sURI.IsFile() {
294		panic(fmt.Sprintf("%q is not a file URI", uri))
295	}
296	return sURI
297}
298
299func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { return nil }
300
301func (c *cmdClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) {
302	return nil, nil
303}
304
305func (c *cmdClient) LogMessage(ctx context.Context, p *protocol.LogMessageParams) error {
306	switch p.Type {
307	case protocol.Error:
308		log.Print("Error:", p.Message)
309	case protocol.Warning:
310		log.Print("Warning:", p.Message)
311	case protocol.Info:
312		if c.app.Verbose {
313			log.Print("Info:", p.Message)
314		}
315	case protocol.Log:
316		if c.app.Verbose {
317			log.Print("Log:", p.Message)
318		}
319	default:
320		if c.app.Verbose {
321			log.Print(p.Message)
322		}
323	}
324	return nil
325}
326
327func (c *cmdClient) Event(ctx context.Context, t *interface{}) error { return nil }
328
329func (c *cmdClient) RegisterCapability(ctx context.Context, p *protocol.RegistrationParams) error {
330	return nil
331}
332
333func (c *cmdClient) UnregisterCapability(ctx context.Context, p *protocol.UnregistrationParams) error {
334	return nil
335}
336
337func (c *cmdClient) WorkspaceFolders(ctx context.Context) ([]protocol.WorkspaceFolder, error) {
338	return nil, nil
339}
340
341func (c *cmdClient) Configuration(ctx context.Context, p *protocol.ParamConfiguration) ([]interface{}, error) {
342	results := make([]interface{}, len(p.Items))
343	for i, item := range p.Items {
344		if item.Section != "gopls" {
345			continue
346		}
347		env := map[string]interface{}{}
348		for _, value := range c.app.env {
349			l := strings.SplitN(value, "=", 2)
350			if len(l) != 2 {
351				continue
352			}
353			env[l[0]] = l[1]
354		}
355		results[i] = map[string]interface{}{
356			"env":     env,
357			"go-diff": true,
358		}
359	}
360	return results, nil
361}
362
363func (c *cmdClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResponse, error) {
364	return &protocol.ApplyWorkspaceEditResponse{Applied: false, FailureReason: "not implemented"}, nil
365}
366
367func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error {
368	if p.URI == "gopls://diagnostics-done" {
369		close(c.diagnosticsDone)
370	}
371	// Don't worry about diagnostics without versions.
372	if p.Version == 0 {
373		return nil
374	}
375
376	c.filesMu.Lock()
377	defer c.filesMu.Unlock()
378
379	file := c.getFile(ctx, fileURI(p.URI))
380	file.diagnostics = p.Diagnostics
381	return nil
382}
383
384func (c *cmdClient) getFile(ctx context.Context, uri span.URI) *cmdFile {
385	file, found := c.files[uri]
386	if !found || file.err != nil {
387		file = &cmdFile{
388			uri: uri,
389		}
390		c.files[uri] = file
391	}
392	if file.mapper == nil {
393		fname := uri.Filename()
394		content, err := ioutil.ReadFile(fname)
395		if err != nil {
396			file.err = errors.Errorf("getFile: %v: %v", uri, err)
397			return file
398		}
399		f := c.fset.AddFile(fname, -1, len(content))
400		f.SetLinesForContent(content)
401		converter := span.NewContentConverter(fname, content)
402		file.mapper = &protocol.ColumnMapper{
403			URI:       uri,
404			Converter: converter,
405			Content:   content,
406		}
407	}
408	return file
409}
410
411func (c *connection) AddFile(ctx context.Context, uri span.URI) *cmdFile {
412	c.Client.filesMu.Lock()
413	defer c.Client.filesMu.Unlock()
414
415	file := c.Client.getFile(ctx, uri)
416	// This should never happen.
417	if file == nil {
418		return &cmdFile{
419			uri: uri,
420			err: fmt.Errorf("no file found for %s", uri),
421		}
422	}
423	if file.err != nil || file.added {
424		return file
425	}
426	file.added = true
427	p := &protocol.DidOpenTextDocumentParams{
428		TextDocument: protocol.TextDocumentItem{
429			URI:        protocol.URIFromSpanURI(uri),
430			LanguageID: source.DetectLanguage("", file.uri.Filename()).String(),
431			Version:    1,
432			Text:       string(file.mapper.Content),
433		},
434	}
435	if err := c.Server.DidOpen(ctx, p); err != nil {
436		file.err = errors.Errorf("%v: %v", uri, err)
437	}
438	return file
439}
440
441func (c *connection) diagnoseFiles(ctx context.Context, files []span.URI) error {
442	var untypedFiles []interface{}
443	for _, file := range files {
444		untypedFiles = append(untypedFiles, string(file))
445	}
446	c.Client.diagnosticsMu.Lock()
447	defer c.Client.diagnosticsMu.Unlock()
448
449	c.Client.diagnosticsDone = make(chan struct{})
450	_, err := c.Server.NonstandardRequest(ctx, "gopls/diagnoseFiles", map[string]interface{}{"files": untypedFiles})
451	<-c.Client.diagnosticsDone
452	return err
453}
454
455func (c *connection) terminate(ctx context.Context) {
456	if strings.HasPrefix(c.Client.app.Remote, "internal@") {
457		// internal connections need to be left alive for the next test
458		return
459	}
460	//TODO: do we need to handle errors on these calls?
461	c.Shutdown(ctx)
462	//TODO: right now calling exit terminates the process, we should rethink that
463	//server.Exit(ctx)
464}
465