ollie/tools/lsp/client.go

104 lines
2.0 KiB
Go

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
}