package lsp import ( "encoding/json" "fmt" "net" "os" "os/exec" "strconv" "syscall" "time" ) // ClientRequest sends a request to the bridge daemon, starting it if needed. // Returns the raw JSON result. func ClientRequest(req *BridgeRequest) (json.RawMessage, error) { if !bridgeRunning() { if err := startBridge(); err != nil { return nil, fmt.Errorf("start bridge: %w", err) } } resp, err := sendRequest(req) if err != nil { // Stale socket — restart os.Remove(SockPath()) if err := startBridge(); err != nil { return nil, fmt.Errorf("restart bridge: %w", err) } resp, err = sendRequest(req) if err != nil { return nil, err } } if resp.Error != "" { return nil, fmt.Errorf("%s", resp.Error) } return resp.Result, nil } func bridgeRunning() bool { data, err := os.ReadFile(PidPath()) if err != nil { return false } pid, err := strconv.Atoi(string(data)) if err != nil { return false } // Check if process exists proc, err := os.FindProcess(pid) if err != nil { return false } return proc.Signal(syscall.Signal(0)) == nil } func startBridge() error { exe, err := os.Executable() if err != nil { return err } cmd := exec.Command(exe, "--bridge") cmd.Stdin = nil cmd.Stdout = nil cmd.Stderr = nil cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} if err := cmd.Start(); err != nil { return err } cmd.Process.Release() // Wait for socket sock := SockPath() for i := 0; i < 50; i++ { if _, err := os.Stat(sock); err == nil { return nil } time.Sleep(100 * time.Millisecond) } return fmt.Errorf("bridge did not start within 5s") } func sendRequest(req *BridgeRequest) (*BridgeResponse, error) { conn, err := net.DialTimeout("unix", SockPath(), 5*time.Second) if err != nil { return nil, err } defer conn.Close() conn.SetDeadline(time.Now().Add(60 * time.Second)) if err := json.NewEncoder(conn).Encode(req); err != nil { return nil, err } var resp BridgeResponse if err := json.NewDecoder(conn).Decode(&resp); err != nil { return nil, err } return &resp, nil }