1// Copyright 2020 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 lsprpc implements a jsonrpc2.StreamServer that may be used to
6// serve the LSP on a jsonrpc2 channel.
7package lsprpc
8
9import (
10	"context"
11	"encoding/json"
12	"fmt"
13	"log"
14	"net"
15	"os"
16	"strconv"
17	"strings"
18	"sync"
19	"sync/atomic"
20	"time"
21
22	"golang.org/x/tools/internal/event"
23	"golang.org/x/tools/internal/gocommand"
24	"golang.org/x/tools/internal/jsonrpc2"
25	jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2"
26	"golang.org/x/tools/internal/lsp"
27	"golang.org/x/tools/internal/lsp/cache"
28	"golang.org/x/tools/internal/lsp/command"
29	"golang.org/x/tools/internal/lsp/debug"
30	"golang.org/x/tools/internal/lsp/debug/tag"
31	"golang.org/x/tools/internal/lsp/protocol"
32	errors "golang.org/x/xerrors"
33)
34
35// AutoNetwork is the pseudo network type used to signal that gopls should use
36// automatic discovery to resolve a remote address.
37const AutoNetwork = "auto"
38
39// Unique identifiers for client/server.
40var serverIndex int64
41
42// The StreamServer type is a jsonrpc2.StreamServer that handles incoming
43// streams as a new LSP session, using a shared cache.
44type StreamServer struct {
45	cache *cache.Cache
46	// daemon controls whether or not to log new connections.
47	daemon bool
48
49	// serverForTest may be set to a test fake for testing.
50	serverForTest protocol.Server
51}
52
53// NewStreamServer creates a StreamServer using the shared cache. If
54// withTelemetry is true, each session is instrumented with telemetry that
55// records RPC statistics.
56func NewStreamServer(cache *cache.Cache, daemon bool) *StreamServer {
57	return &StreamServer{cache: cache, daemon: daemon}
58}
59
60func (s *StreamServer) Binder() *ServerBinder {
61	newServer := func(ctx context.Context, client protocol.ClientCloser) protocol.Server {
62		session := s.cache.NewSession(ctx)
63		server := s.serverForTest
64		if server == nil {
65			server = lsp.NewServer(session, client)
66			debug.GetInstance(ctx).AddService(server, session)
67		}
68		return server
69	}
70	return NewServerBinder(newServer)
71}
72
73// ServeStream implements the jsonrpc2.StreamServer interface, by handling
74// incoming streams using a new lsp server.
75func (s *StreamServer) ServeStream(ctx context.Context, conn jsonrpc2.Conn) error {
76	client := protocol.ClientDispatcher(conn)
77	session := s.cache.NewSession(ctx)
78	server := s.serverForTest
79	if server == nil {
80		server = lsp.NewServer(session, client)
81		debug.GetInstance(ctx).AddService(server, session)
82	}
83	// Clients may or may not send a shutdown message. Make sure the server is
84	// shut down.
85	// TODO(rFindley): this shutdown should perhaps be on a disconnected context.
86	defer func() {
87		if err := server.Shutdown(ctx); err != nil {
88			event.Error(ctx, "error shutting down", err)
89		}
90	}()
91	executable, err := os.Executable()
92	if err != nil {
93		log.Printf("error getting gopls path: %v", err)
94		executable = ""
95	}
96	ctx = protocol.WithClient(ctx, client)
97	conn.Go(ctx,
98		protocol.Handlers(
99			handshaker(session, executable, s.daemon,
100				protocol.ServerHandler(server,
101					jsonrpc2.MethodNotFound))))
102	if s.daemon {
103		log.Printf("Session %s: connected", session.ID())
104		defer log.Printf("Session %s: exited", session.ID())
105	}
106	<-conn.Done()
107	return conn.Err()
108}
109
110// A Forwarder is a jsonrpc2.StreamServer that handles an LSP stream by
111// forwarding it to a remote. This is used when the gopls process started by
112// the editor is in the `-remote` mode, which means it finds and connects to a
113// separate gopls daemon. In these cases, we still want the forwarder gopls to
114// be instrumented with telemetry, and want to be able to in some cases hijack
115// the jsonrpc2 connection with the daemon.
116type Forwarder struct {
117	network, addr string
118
119	// goplsPath is the path to the current executing gopls binary.
120	goplsPath string
121
122	// configuration for the auto-started gopls remote.
123	remoteConfig remoteConfig
124
125	mu sync.Mutex
126	// Hold on to the server connection so that we can redo the handshake if any
127	// information changes.
128	serverConn jsonrpc2.Conn
129	serverID   string
130}
131
132type remoteConfig struct {
133	debug         string
134	listenTimeout time.Duration
135	logfile       string
136}
137
138// A RemoteOption configures the behavior of the auto-started remote.
139type RemoteOption interface {
140	set(*remoteConfig)
141}
142
143// RemoteDebugAddress configures the address used by the auto-started Gopls daemon
144// for serving debug information.
145type RemoteDebugAddress string
146
147func (d RemoteDebugAddress) set(cfg *remoteConfig) {
148	cfg.debug = string(d)
149}
150
151// RemoteListenTimeout configures the amount of time the auto-started gopls
152// daemon will wait with no client connections before shutting down.
153type RemoteListenTimeout time.Duration
154
155func (d RemoteListenTimeout) set(cfg *remoteConfig) {
156	cfg.listenTimeout = time.Duration(d)
157}
158
159// RemoteLogfile configures the logfile location for the auto-started gopls
160// daemon.
161type RemoteLogfile string
162
163func (l RemoteLogfile) set(cfg *remoteConfig) {
164	cfg.logfile = string(l)
165}
166
167func defaultRemoteConfig() remoteConfig {
168	return remoteConfig{
169		listenTimeout: 1 * time.Minute,
170	}
171}
172
173// NewForwarder creates a new Forwarder, ready to forward connections to the
174// remote server specified by network and addr.
175func NewForwarder(network, addr string, opts ...RemoteOption) *Forwarder {
176	gp, err := os.Executable()
177	if err != nil {
178		log.Printf("error getting gopls path for forwarder: %v", err)
179		gp = ""
180	}
181
182	rcfg := defaultRemoteConfig()
183	for _, opt := range opts {
184		opt.set(&rcfg)
185	}
186
187	fwd := &Forwarder{
188		network:      network,
189		addr:         addr,
190		goplsPath:    gp,
191		remoteConfig: rcfg,
192	}
193	return fwd
194}
195
196// QueryServerState queries the server state of the current server.
197func QueryServerState(ctx context.Context, addr string) (*ServerState, error) {
198	serverConn, err := dialRemote(ctx, addr)
199	if err != nil {
200		return nil, err
201	}
202	var state ServerState
203	if err := protocol.Call(ctx, serverConn, sessionsMethod, nil, &state); err != nil {
204		return nil, errors.Errorf("querying server state: %w", err)
205	}
206	return &state, nil
207}
208
209// dialRemote is used for making calls into the gopls daemon. addr should be a
210// URL, possibly on the synthetic 'auto' network (e.g. tcp://..., unix://...,
211// or auto://...).
212func dialRemote(ctx context.Context, addr string) (jsonrpc2.Conn, error) {
213	network, address := ParseAddr(addr)
214	if network == AutoNetwork {
215		gp, err := os.Executable()
216		if err != nil {
217			return nil, errors.Errorf("getting gopls path: %w", err)
218		}
219		network, address = autoNetworkAddress(gp, address)
220	}
221	netConn, err := net.DialTimeout(network, address, 5*time.Second)
222	if err != nil {
223		return nil, errors.Errorf("dialing remote: %w", err)
224	}
225	serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn))
226	serverConn.Go(ctx, jsonrpc2.MethodNotFound)
227	return serverConn, nil
228}
229
230func ExecuteCommand(ctx context.Context, addr string, id string, request, result interface{}) error {
231	serverConn, err := dialRemote(ctx, addr)
232	if err != nil {
233		return err
234	}
235	args, err := command.MarshalArgs(request)
236	if err != nil {
237		return err
238	}
239	params := protocol.ExecuteCommandParams{
240		Command:   id,
241		Arguments: args,
242	}
243	return protocol.Call(ctx, serverConn, "workspace/executeCommand", params, result)
244}
245
246// ServeStream dials the forwarder remote and binds the remote to serve the LSP
247// on the incoming stream.
248func (f *Forwarder) ServeStream(ctx context.Context, clientConn jsonrpc2.Conn) error {
249	client := protocol.ClientDispatcher(clientConn)
250
251	netConn, err := f.connectToRemote(ctx)
252	if err != nil {
253		return errors.Errorf("forwarder: connecting to remote: %w", err)
254	}
255	serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn))
256	server := protocol.ServerDispatcher(serverConn)
257
258	// Forward between connections.
259	serverConn.Go(ctx,
260		protocol.Handlers(
261			protocol.ClientHandler(client,
262				jsonrpc2.MethodNotFound)))
263
264	// Don't run the clientConn yet, so that we can complete the handshake before
265	// processing any client messages.
266
267	// Do a handshake with the server instance to exchange debug information.
268	index := atomic.AddInt64(&serverIndex, 1)
269	f.mu.Lock()
270	f.serverConn = serverConn
271	f.serverID = strconv.FormatInt(index, 10)
272	f.mu.Unlock()
273	f.handshake(ctx)
274	clientConn.Go(ctx,
275		protocol.Handlers(
276			f.handler(
277				protocol.ServerHandler(server,
278					jsonrpc2.MethodNotFound))))
279
280	select {
281	case <-serverConn.Done():
282		clientConn.Close()
283	case <-clientConn.Done():
284		serverConn.Close()
285	}
286
287	err = nil
288	if serverConn.Err() != nil {
289		err = errors.Errorf("remote disconnected: %v", err)
290	} else if clientConn.Err() != nil {
291		err = errors.Errorf("client disconnected: %v", err)
292	}
293	event.Log(ctx, fmt.Sprintf("forwarder: exited with error: %v", err))
294	return err
295}
296
297func (f *Forwarder) Binder() *ForwardBinder {
298	network, address := realNetworkAddress(f.network, f.addr, f.goplsPath)
299	dialer := jsonrpc2_v2.NetDialer(network, address, net.Dialer{
300		Timeout: 5 * time.Second,
301	})
302	return NewForwardBinder(dialer)
303}
304
305func (f *Forwarder) handshake(ctx context.Context) {
306	var (
307		hreq = handshakeRequest{
308			ServerID:  f.serverID,
309			GoplsPath: f.goplsPath,
310		}
311		hresp handshakeResponse
312	)
313	if di := debug.GetInstance(ctx); di != nil {
314		hreq.Logfile = di.Logfile
315		hreq.DebugAddr = di.ListenedDebugAddress()
316	}
317	if err := protocol.Call(ctx, f.serverConn, handshakeMethod, hreq, &hresp); err != nil {
318		// TODO(rfindley): at some point in the future we should return an error
319		// here.  Handshakes have become functional in nature.
320		event.Error(ctx, "forwarder: gopls handshake failed", err)
321	}
322	if hresp.GoplsPath != f.goplsPath {
323		event.Error(ctx, "", fmt.Errorf("forwarder: gopls path mismatch: forwarder is %q, remote is %q", f.goplsPath, hresp.GoplsPath))
324	}
325	event.Log(ctx, "New server",
326		tag.NewServer.Of(f.serverID),
327		tag.Logfile.Of(hresp.Logfile),
328		tag.DebugAddress.Of(hresp.DebugAddr),
329		tag.GoplsPath.Of(hresp.GoplsPath),
330		tag.ClientID.Of(hresp.SessionID),
331	)
332}
333
334func (f *Forwarder) connectToRemote(ctx context.Context) (net.Conn, error) {
335	return connectToRemote(ctx, f.network, f.addr, f.goplsPath, f.remoteConfig)
336}
337
338func ConnectToRemote(ctx context.Context, addr string, opts ...RemoteOption) (net.Conn, error) {
339	rcfg := defaultRemoteConfig()
340	for _, opt := range opts {
341		opt.set(&rcfg)
342	}
343	// This is not strictly necessary, as it won't be used if not connecting to
344	// the 'auto' remote.
345	goplsPath, err := os.Executable()
346	if err != nil {
347		return nil, fmt.Errorf("unable to resolve gopls path: %v", err)
348	}
349	network, address := ParseAddr(addr)
350	return connectToRemote(ctx, network, address, goplsPath, rcfg)
351}
352
353func realNetworkAddress(inNetwork, inAddr, goplsPath string) (network, address string) {
354	if inNetwork != AutoNetwork {
355		return inNetwork, inAddr
356	}
357	// The "auto" network is a fake network used for service discovery. It
358	// resolves a known address based on gopls binary path.
359	return autoNetworkAddress(goplsPath, inAddr)
360}
361
362func connectToRemote(ctx context.Context, inNetwork, inAddr, goplsPath string, rcfg remoteConfig) (net.Conn, error) {
363	var (
364		netConn          net.Conn
365		err              error
366		network, address = realNetworkAddress(inNetwork, inAddr, goplsPath)
367	)
368	// Attempt to verify that we own the remote. This is imperfect, but if we can
369	// determine that the remote is owned by a different user, we should fail.
370	ok, err := verifyRemoteOwnership(network, address)
371	if err != nil {
372		// If the ownership check itself failed, we fail open but log an error to
373		// the user.
374		event.Error(ctx, "unable to check daemon socket owner, failing open", err)
375	} else if !ok {
376		// We successfully checked that the socket is not owned by us, we fail
377		// closed.
378		return nil, fmt.Errorf("socket %q is owned by a different user", address)
379	}
380	const dialTimeout = 1 * time.Second
381	// Try dialing our remote once, in case it is already running.
382	netConn, err = net.DialTimeout(network, address, dialTimeout)
383	if err == nil {
384		return netConn, nil
385	}
386	// If our remote is on the 'auto' network, start it if it doesn't exist.
387	if inNetwork == AutoNetwork {
388		if goplsPath == "" {
389			return nil, fmt.Errorf("cannot auto-start remote: gopls path is unknown")
390		}
391		if network == "unix" {
392			// Sometimes the socketfile isn't properly cleaned up when gopls shuts
393			// down. Since we have already tried and failed to dial this address, it
394			// should *usually* be safe to remove the socket before binding to the
395			// address.
396			// TODO(rfindley): there is probably a race here if multiple gopls
397			// instances are simultaneously starting up.
398			if _, err := os.Stat(address); err == nil {
399				if err := os.Remove(address); err != nil {
400					return nil, errors.Errorf("removing remote socket file: %w", err)
401				}
402			}
403		}
404		args := []string{"serve",
405			"-listen", fmt.Sprintf(`%s;%s`, network, address),
406			"-listen.timeout", rcfg.listenTimeout.String(),
407		}
408		if rcfg.logfile != "" {
409			args = append(args, "-logfile", rcfg.logfile)
410		}
411		if rcfg.debug != "" {
412			args = append(args, "-debug", rcfg.debug)
413		}
414		if err := startRemote(goplsPath, args...); err != nil {
415			return nil, errors.Errorf("startRemote(%q, %v): %w", goplsPath, args, err)
416		}
417	}
418
419	const retries = 5
420	// It can take some time for the newly started server to bind to our address,
421	// so we retry for a bit.
422	for retry := 0; retry < retries; retry++ {
423		startDial := time.Now()
424		netConn, err = net.DialTimeout(network, address, dialTimeout)
425		if err == nil {
426			return netConn, nil
427		}
428		event.Log(ctx, fmt.Sprintf("failed attempt #%d to connect to remote: %v\n", retry+2, err))
429		// In case our failure was a fast-failure, ensure we wait at least
430		// f.dialTimeout before trying again.
431		if retry != retries-1 {
432			time.Sleep(dialTimeout - time.Since(startDial))
433		}
434	}
435	return nil, errors.Errorf("dialing remote: %w", err)
436}
437
438// handler intercepts messages to the daemon to enrich them with local
439// information.
440func (f *Forwarder) handler(handler jsonrpc2.Handler) jsonrpc2.Handler {
441	return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
442		// Intercept certain messages to add special handling.
443		switch r.Method() {
444		case "initialize":
445			if newr, err := addGoEnvToInitializeRequest(ctx, r); err == nil {
446				r = newr
447			} else {
448				log.Printf("unable to add local env to initialize request: %v", err)
449			}
450		case "workspace/executeCommand":
451			var params protocol.ExecuteCommandParams
452			if err := json.Unmarshal(r.Params(), &params); err == nil {
453				if params.Command == command.StartDebugging.ID() {
454					var args command.DebuggingArgs
455					if err := command.UnmarshalArgs(params.Arguments, &args); err == nil {
456						reply = f.replyWithDebugAddress(ctx, reply, args)
457					} else {
458						event.Error(ctx, "unmarshaling debugging args", err)
459					}
460				}
461			} else {
462				event.Error(ctx, "intercepting executeCommand request", err)
463			}
464		}
465		// The gopls workspace environment defaults to the process environment in
466		// which gopls daemon was started. To avoid discrepancies in Go environment
467		// between the editor and daemon, inject any unset variables in `go env`
468		// into the options sent by initialize.
469		//
470		// See also golang.org/issue/37830.
471		return handler(ctx, reply, r)
472	}
473}
474
475// addGoEnvToInitializeRequest builds a new initialize request in which we set
476// any environment variables output by `go env` and not already present in the
477// request.
478//
479// It returns an error if r is not an initialize requst, or is otherwise
480// malformed.
481func addGoEnvToInitializeRequest(ctx context.Context, r jsonrpc2.Request) (jsonrpc2.Request, error) {
482	var params protocol.ParamInitialize
483	if err := json.Unmarshal(r.Params(), &params); err != nil {
484		return nil, err
485	}
486	var opts map[string]interface{}
487	switch v := params.InitializationOptions.(type) {
488	case nil:
489		opts = make(map[string]interface{})
490	case map[string]interface{}:
491		opts = v
492	default:
493		return nil, fmt.Errorf("unexpected type for InitializationOptions: %T", v)
494	}
495	envOpt, ok := opts["env"]
496	if !ok {
497		envOpt = make(map[string]interface{})
498	}
499	env, ok := envOpt.(map[string]interface{})
500	if !ok {
501		return nil, fmt.Errorf(`env option is %T, expected a map`, envOpt)
502	}
503	goenv, err := getGoEnv(ctx, env)
504	if err != nil {
505		return nil, err
506	}
507	for govar, value := range goenv {
508		env[govar] = value
509	}
510	opts["env"] = env
511	params.InitializationOptions = opts
512	call, ok := r.(*jsonrpc2.Call)
513	if !ok {
514		return nil, fmt.Errorf("%T is not a *jsonrpc2.Call", r)
515	}
516	return jsonrpc2.NewCall(call.ID(), "initialize", params)
517}
518
519func getGoEnv(ctx context.Context, env map[string]interface{}) (map[string]string, error) {
520	var runEnv []string
521	for k, v := range env {
522		runEnv = append(runEnv, fmt.Sprintf("%s=%s", k, v))
523	}
524	runner := gocommand.Runner{}
525	output, err := runner.Run(ctx, gocommand.Invocation{
526		Verb: "env",
527		Args: []string{"-json"},
528		Env:  runEnv,
529	})
530	if err != nil {
531		return nil, err
532	}
533	envmap := make(map[string]string)
534	if err := json.Unmarshal(output.Bytes(), &envmap); err != nil {
535		return nil, err
536	}
537	return envmap, nil
538}
539
540func (f *Forwarder) replyWithDebugAddress(outerCtx context.Context, r jsonrpc2.Replier, args command.DebuggingArgs) jsonrpc2.Replier {
541	di := debug.GetInstance(outerCtx)
542	if di == nil {
543		event.Log(outerCtx, "no debug instance to start")
544		return r
545	}
546	return func(ctx context.Context, result interface{}, outerErr error) error {
547		if outerErr != nil {
548			return r(ctx, result, outerErr)
549		}
550		// Enrich the result with our own debugging information. Since we're an
551		// intermediary, the jsonrpc2 package has deserialized the result into
552		// maps, by default. Re-do the unmarshalling.
553		raw, err := json.Marshal(result)
554		if err != nil {
555			event.Error(outerCtx, "marshaling intermediate command result", err)
556			return r(ctx, result, err)
557		}
558		var modified command.DebuggingResult
559		if err := json.Unmarshal(raw, &modified); err != nil {
560			event.Error(outerCtx, "unmarshaling intermediate command result", err)
561			return r(ctx, result, err)
562		}
563		addr := args.Addr
564		if addr == "" {
565			addr = "localhost:0"
566		}
567		addr, err = di.Serve(outerCtx, addr)
568		if err != nil {
569			event.Error(outerCtx, "starting debug server", err)
570			return r(ctx, result, outerErr)
571		}
572		urls := []string{"http://" + addr}
573		modified.URLs = append(urls, modified.URLs...)
574		go f.handshake(ctx)
575		return r(ctx, modified, nil)
576	}
577}
578
579// A handshakeRequest identifies a client to the LSP server.
580type handshakeRequest struct {
581	// ServerID is the ID of the server on the client. This should usually be 0.
582	ServerID string `json:"serverID"`
583	// Logfile is the location of the clients log file.
584	Logfile string `json:"logfile"`
585	// DebugAddr is the client debug address.
586	DebugAddr string `json:"debugAddr"`
587	// GoplsPath is the path to the Gopls binary running the current client
588	// process.
589	GoplsPath string `json:"goplsPath"`
590}
591
592// A handshakeResponse is returned by the LSP server to tell the LSP client
593// information about its session.
594type handshakeResponse struct {
595	// SessionID is the server session associated with the client.
596	SessionID string `json:"sessionID"`
597	// Logfile is the location of the server logs.
598	Logfile string `json:"logfile"`
599	// DebugAddr is the server debug address.
600	DebugAddr string `json:"debugAddr"`
601	// GoplsPath is the path to the Gopls binary running the current server
602	// process.
603	GoplsPath string `json:"goplsPath"`
604}
605
606// ClientSession identifies a current client LSP session on the server. Note
607// that it looks similar to handshakeResposne, but in fact 'Logfile' and
608// 'DebugAddr' now refer to the client.
609type ClientSession struct {
610	SessionID string `json:"sessionID"`
611	Logfile   string `json:"logfile"`
612	DebugAddr string `json:"debugAddr"`
613}
614
615// ServerState holds information about the gopls daemon process, including its
616// debug information and debug information of all of its current connected
617// clients.
618type ServerState struct {
619	Logfile         string          `json:"logfile"`
620	DebugAddr       string          `json:"debugAddr"`
621	GoplsPath       string          `json:"goplsPath"`
622	CurrentClientID string          `json:"currentClientID"`
623	Clients         []ClientSession `json:"clients"`
624}
625
626const (
627	handshakeMethod = "gopls/handshake"
628	sessionsMethod  = "gopls/sessions"
629)
630
631func handshaker(session *cache.Session, goplsPath string, logHandshakes bool, handler jsonrpc2.Handler) jsonrpc2.Handler {
632	return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
633		switch r.Method() {
634		case handshakeMethod:
635			// We log.Printf in this handler, rather than event.Log when we want logs
636			// to go to the daemon log rather than being reflected back to the
637			// client.
638			var req handshakeRequest
639			if err := json.Unmarshal(r.Params(), &req); err != nil {
640				if logHandshakes {
641					log.Printf("Error processing handshake for session %s: %v", session.ID(), err)
642				}
643				sendError(ctx, reply, err)
644				return nil
645			}
646			if logHandshakes {
647				log.Printf("Session %s: got handshake. Logfile: %q, Debug addr: %q", session.ID(), req.Logfile, req.DebugAddr)
648			}
649			event.Log(ctx, "Handshake session update",
650				cache.KeyUpdateSession.Of(session),
651				tag.DebugAddress.Of(req.DebugAddr),
652				tag.Logfile.Of(req.Logfile),
653				tag.ServerID.Of(req.ServerID),
654				tag.GoplsPath.Of(req.GoplsPath),
655			)
656			resp := handshakeResponse{
657				SessionID: session.ID(),
658				GoplsPath: goplsPath,
659			}
660			if di := debug.GetInstance(ctx); di != nil {
661				resp.Logfile = di.Logfile
662				resp.DebugAddr = di.ListenedDebugAddress()
663			}
664			return reply(ctx, resp, nil)
665
666		case sessionsMethod:
667			resp := ServerState{
668				GoplsPath:       goplsPath,
669				CurrentClientID: session.ID(),
670			}
671			if di := debug.GetInstance(ctx); di != nil {
672				resp.Logfile = di.Logfile
673				resp.DebugAddr = di.ListenedDebugAddress()
674				for _, c := range di.State.Clients() {
675					resp.Clients = append(resp.Clients, ClientSession{
676						SessionID: c.Session.ID(),
677						Logfile:   c.Logfile,
678						DebugAddr: c.DebugAddress,
679					})
680				}
681			}
682			return reply(ctx, resp, nil)
683		}
684		return handler(ctx, reply, r)
685	}
686}
687
688func sendError(ctx context.Context, reply jsonrpc2.Replier, err error) {
689	err = errors.Errorf("%v: %w", err, jsonrpc2.ErrParse)
690	if err := reply(ctx, nil, err); err != nil {
691		event.Error(ctx, "", err)
692	}
693}
694
695// ParseAddr parses the address of a gopls remote.
696// TODO(rFindley): further document this syntax, and allow URI-style remote
697// addresses such as "auto://...".
698func ParseAddr(listen string) (network string, address string) {
699	// Allow passing just -remote=auto, as a shorthand for using automatic remote
700	// resolution.
701	if listen == AutoNetwork {
702		return AutoNetwork, ""
703	}
704	if parts := strings.SplitN(listen, ";", 2); len(parts) == 2 {
705		return parts[0], parts[1]
706	}
707	return "tcp", listen
708}
709