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/protocol"
26	"golang.org/x/tools/internal/lsp/source"
27	"golang.org/x/tools/internal/span"
28	"golang.org/x/tools/internal/telemetry/export"
29	"golang.org/x/tools/internal/telemetry/export/ocagent"
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:"*EXPERIMENTAL* - forward all commands to a remote lsp"`
62
63	// Enable verbose logging
64	Verbose bool `flag:"v" help:"Verbose output"`
65
66	// Control ocagent export of telemetry
67	OCAgent string `flag:"ocagent" help:"The address of the ocagent, or off"`
68
69	// PrepareOptions is called to update the options when a new view is built.
70	// It is primarily to allow the behavior of gopls to be modified by hooks.
71	PrepareOptions func(*source.Options)
72}
73
74// Returns a new Application ready to run.
75func New(name, wd string, env []string, options func(*source.Options)) *Application {
76	if wd == "" {
77		wd, _ = os.Getwd()
78	}
79	app := &Application{
80		options: options,
81		name:    name,
82		wd:      wd,
83		env:     env,
84		OCAgent: "off", //TODO: Remove this line to default the exporter to on
85	}
86	return app
87}
88
89// Name implements tool.Application returning the binary name.
90func (app *Application) Name() string { return app.name }
91
92// Usage implements tool.Application returning empty extra argument usage.
93func (app *Application) Usage() string { return "<command> [command-flags] [command-args]" }
94
95// ShortHelp implements tool.Application returning the main binary help.
96func (app *Application) ShortHelp() string {
97	return "The Go Language source tools."
98}
99
100// DetailedHelp implements tool.Application returning the main binary help.
101// This includes the short help for all the sub commands.
102func (app *Application) DetailedHelp(f *flag.FlagSet) {
103	fmt.Fprint(f.Output(), `
104Available commands are:
105`)
106	for _, c := range app.commands() {
107		fmt.Fprintf(f.Output(), "  %s : %v\n", c.Name(), c.ShortHelp())
108	}
109	fmt.Fprint(f.Output(), `
110gopls flags are:
111`)
112	f.PrintDefaults()
113}
114
115// Run takes the args after top level flag processing, and invokes the correct
116// sub command as specified by the first argument.
117// If no arguments are passed it will invoke the server sub command, as a
118// temporary measure for compatibility.
119func (app *Application) Run(ctx context.Context, args ...string) error {
120	ocConfig := ocagent.Discover()
121	//TODO: we should not need to adjust the discovered configuration
122	ocConfig.Address = app.OCAgent
123	export.AddExporters(ocagent.Connect(ocConfig))
124	app.Serve.app = app
125	if len(args) == 0 {
126		return tool.Run(ctx, &app.Serve, args)
127	}
128	command, args := args[0], args[1:]
129	for _, c := range app.commands() {
130		if c.Name() == command {
131			return tool.Run(ctx, c, args)
132		}
133	}
134	return tool.CommandLineErrorf("Unknown command %v", command)
135}
136
137// commands returns the set of commands supported by the gopls tool on the
138// command line.
139// The command is specified by the first non flag argument.
140func (app *Application) commands() []tool.Application {
141	return []tool.Application{
142		&app.Serve,
143		&bug{},
144		&check{app: app},
145		&foldingRanges{app: app},
146		&format{app: app},
147		&links{app: app},
148		&imports{app: app},
149		&query{app: app},
150		&references{app: app},
151		&rename{app: app},
152		&signature{app: app},
153		&suggestedfix{app: app},
154		&symbols{app: app},
155		&version{app: app},
156	}
157}
158
159var (
160	internalMu          sync.Mutex
161	internalConnections = make(map[string]*connection)
162)
163
164func (app *Application) connect(ctx context.Context) (*connection, error) {
165	switch app.Remote {
166	case "":
167		connection := newConnection(app)
168		ctx, connection.Server = lsp.NewClientServer(ctx, cache.New(app.options), connection.Client)
169		return connection, connection.initialize(ctx)
170	case "internal":
171		internalMu.Lock()
172		defer internalMu.Unlock()
173		if c := internalConnections[app.wd]; c != nil {
174			return c, nil
175		}
176		connection := newConnection(app)
177		ctx := xcontext.Detach(ctx) //TODO:a way of shutting down the internal server
178		cr, sw, _ := os.Pipe()
179		sr, cw, _ := os.Pipe()
180		var jc *jsonrpc2.Conn
181		ctx, jc, connection.Server = protocol.NewClient(ctx, jsonrpc2.NewHeaderStream(cr, cw), connection.Client)
182		go jc.Run(ctx)
183		go func() {
184			ctx, srv := lsp.NewServer(ctx, cache.New(app.options), jsonrpc2.NewHeaderStream(sr, sw))
185			srv.Run(ctx)
186		}()
187		if err := connection.initialize(ctx); err != nil {
188			return nil, err
189		}
190		internalConnections[app.wd] = connection
191		return connection, nil
192	default:
193		connection := newConnection(app)
194		conn, err := net.Dial("tcp", app.Remote)
195		if err != nil {
196			return nil, err
197		}
198		stream := jsonrpc2.NewHeaderStream(conn, conn)
199		var jc *jsonrpc2.Conn
200		ctx, jc, connection.Server = protocol.NewClient(ctx, stream, connection.Client)
201		go jc.Run(ctx)
202		return connection, connection.initialize(ctx)
203	}
204}
205
206func (c *connection) initialize(ctx context.Context) error {
207	params := &protocol.ParamInitialize{}
208	params.RootURI = string(span.FileURI(c.Client.app.wd))
209	params.Capabilities.Workspace.Configuration = true
210	params.Capabilities.TextDocument.Hover = protocol.HoverClientCapabilities{
211		ContentFormat: []protocol.MarkupKind{protocol.PlainText},
212	}
213	if _, err := c.Server.Initialize(ctx, params); err != nil {
214		return err
215	}
216	if err := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
217		return err
218	}
219	return nil
220}
221
222type connection struct {
223	protocol.Server
224	Client *cmdClient
225}
226
227type cmdClient struct {
228	protocol.Server
229	app  *Application
230	fset *token.FileSet
231
232	filesMu sync.Mutex
233	files   map[span.URI]*cmdFile
234}
235
236type cmdFile struct {
237	uri            span.URI
238	mapper         *protocol.ColumnMapper
239	err            error
240	added          bool
241	hasDiagnostics chan struct{}
242	diagnosticsMu  sync.Mutex
243	diagnostics    []protocol.Diagnostic
244}
245
246func newConnection(app *Application) *connection {
247	return &connection{
248		Client: &cmdClient{
249			app:   app,
250			fset:  token.NewFileSet(),
251			files: make(map[span.URI]*cmdFile),
252		},
253	}
254}
255
256func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { return nil }
257
258func (c *cmdClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) {
259	return nil, nil
260}
261
262func (c *cmdClient) LogMessage(ctx context.Context, p *protocol.LogMessageParams) error {
263	switch p.Type {
264	case protocol.Error:
265		log.Print("Error:", p.Message)
266	case protocol.Warning:
267		log.Print("Warning:", p.Message)
268	case protocol.Info:
269		if c.app.Verbose {
270			log.Print("Info:", p.Message)
271		}
272	case protocol.Log:
273		if c.app.Verbose {
274			log.Print("Log:", p.Message)
275		}
276	default:
277		if c.app.Verbose {
278			log.Print(p.Message)
279		}
280	}
281	return nil
282}
283
284func (c *cmdClient) Event(ctx context.Context, t *interface{}) error { return nil }
285
286func (c *cmdClient) RegisterCapability(ctx context.Context, p *protocol.RegistrationParams) error {
287	return nil
288}
289
290func (c *cmdClient) UnregisterCapability(ctx context.Context, p *protocol.UnregistrationParams) error {
291	return nil
292}
293
294func (c *cmdClient) WorkspaceFolders(ctx context.Context) ([]protocol.WorkspaceFolder, error) {
295	return nil, nil
296}
297
298func (c *cmdClient) Configuration(ctx context.Context, p *protocol.ParamConfiguration) ([]interface{}, error) {
299	results := make([]interface{}, len(p.Items))
300	for i, item := range p.Items {
301		if item.Section != "gopls" {
302			continue
303		}
304		env := map[string]interface{}{}
305		for _, value := range c.app.env {
306			l := strings.SplitN(value, "=", 2)
307			if len(l) != 2 {
308				continue
309			}
310			env[l[0]] = l[1]
311		}
312		results[i] = map[string]interface{}{
313			"env":     env,
314			"go-diff": true,
315		}
316	}
317	return results, nil
318}
319
320func (c *cmdClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResponse, error) {
321	return &protocol.ApplyWorkspaceEditResponse{Applied: false, FailureReason: "not implemented"}, nil
322}
323
324func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error {
325	c.filesMu.Lock()
326	defer c.filesMu.Unlock()
327	uri := span.URI(p.URI)
328	file := c.getFile(ctx, uri)
329	file.diagnosticsMu.Lock()
330	defer file.diagnosticsMu.Unlock()
331	hadDiagnostics := file.diagnostics != nil
332	file.diagnostics = p.Diagnostics
333	if file.diagnostics == nil {
334		file.diagnostics = []protocol.Diagnostic{}
335	}
336	if !hadDiagnostics {
337		close(file.hasDiagnostics)
338	}
339	return nil
340}
341
342func (c *cmdClient) getFile(ctx context.Context, uri span.URI) *cmdFile {
343	file, found := c.files[uri]
344	if !found || file.err != nil {
345		file = &cmdFile{
346			uri:            uri,
347			hasDiagnostics: make(chan struct{}),
348		}
349		c.files[uri] = file
350	}
351	if file.mapper == nil {
352		fname := uri.Filename()
353		content, err := ioutil.ReadFile(fname)
354		if err != nil {
355			file.err = errors.Errorf("getFile: %v: %v", uri, err)
356			return file
357		}
358		f := c.fset.AddFile(fname, -1, len(content))
359		f.SetLinesForContent(content)
360		converter := span.NewContentConverter(fname, content)
361		file.mapper = &protocol.ColumnMapper{
362			URI:       uri,
363			Converter: converter,
364			Content:   content,
365		}
366	}
367	return file
368}
369
370func (c *connection) AddFile(ctx context.Context, uri span.URI) *cmdFile {
371	c.Client.filesMu.Lock()
372	defer c.Client.filesMu.Unlock()
373
374	file := c.Client.getFile(ctx, uri)
375	// This should never happen.
376	if file == nil {
377		return &cmdFile{
378			uri: uri,
379			err: fmt.Errorf("no file found for %s", uri),
380		}
381	}
382	if file.err != nil || file.added {
383		return file
384	}
385	file.added = true
386	p := &protocol.DidOpenTextDocumentParams{}
387	p.TextDocument.URI = string(uri)
388	p.TextDocument.Text = string(file.mapper.Content)
389	p.TextDocument.LanguageID = source.DetectLanguage("", file.uri.Filename()).String()
390	if err := c.Server.DidOpen(ctx, p); err != nil {
391		file.err = errors.Errorf("%v: %v", uri, err)
392	}
393	return file
394}
395
396func (c *connection) terminate(ctx context.Context) {
397	if c.Client.app.Remote == "internal" {
398		// internal connections need to be left alive for the next test
399		return
400	}
401	//TODO: do we need to handle errors on these calls?
402	c.Shutdown(ctx)
403	//TODO: right now calling exit terminates the process, we should rethink that
404	//server.Exit(ctx)
405}
406