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