//! LSP client — sends JSON-RPC requests to language servers. use anyhow::Result; use serde_json::Value; use std::io::{BufRead, BufReader, Read, Write}; use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio}; use std::sync::Mutex; use tracing::{debug, info}; /// Mutable inner state of an LSP client, protected by a mutex so that /// `send_request` and `shutdown` can be called via `&self` (required by /// [`LspManager`](super::manager::LspManager)). struct LspClientInner { process: Child, stdin: ChildStdin, stdout: BufReader, request_id: u64, } /// A minimal but functional LSP client. pub struct LspClient { inner: Mutex, } impl LspClient { /// Spawn a language server process. pub fn start(command: &str, args: &[String]) -> Result { let mut child = Command::new(command) .args(args) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()?; let stdin = child.stdin.take().unwrap(); let stdout = BufReader::new(child.stdout.take().unwrap()); info!("LSP client spawned: {command}"); Ok(LspClient { inner: Mutex::new(LspClientInner { process: child, stdin, stdout, request_id: 0, }), }) } /// Send a JSON-RPC request and read the response. pub fn send_request(&self, method: &str, params: &Value) -> Result { let mut inner = self.inner.lock().unwrap(); inner.request_id += 1; let request = serde_json::json!({ "jsonrpc": "2.0", "id": inner.request_id, "method": method, "params": params.clone(), }); // Write Content-Length header + body let body = serde_json::to_string(&request)?; let header = format!("Content-Length: {}\r\n\r\n", body.len()); inner.stdin.write_all(header.as_bytes())?; inner.stdin.write_all(body.as_bytes())?; inner.stdin.flush()?; debug!("LSP request: {method} (id={})", inner.request_id); // Read Content-Length header let mut content_length = 0usize; loop { let mut line = String::new(); inner.stdout.read_line(&mut line)?; let trimmed = line.trim(); if trimmed.is_empty() { break; // end of headers } if let Some(len_str) = trimmed.strip_prefix("Content-Length: ") { content_length = len_str.parse::()?; } } // Read the JSON body let mut buf = vec![0u8; content_length]; inner.stdout.read_exact(&mut buf)?; let response: Value = serde_json::from_slice(&buf)?; debug!("LSP response for {method}: response received"); Ok(response) } /// Gracefully shut down the server. pub fn shutdown(&self) -> Result<()> { let null = Value::Null; let _ = self.send_request("shutdown", &null); let _ = self.send_request("exit", &null); if let Ok(mut inner) = self.inner.lock() { let _ = inner.process.wait(); } info!("LSP client shut down"); Ok(()) } } impl Drop for LspClient { fn drop(&mut self) { if let Ok(mut inner) = self.inner.lock() { let _ = inner.process.kill(); let _ = inner.process.wait(); } } }