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(), ¶ms); 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(), ¶ms); 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