use std::collections::HashMap;
use std::io::{BufRead, BufReader, Read, Write};
use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio};
use std::sync::{
Arc, Mutex,
atomic::{AtomicI64, Ordering},
};
use anyhow::{Context as _, Result};
use futures::channel::oneshot;
use lsp_types::{InitializeParams, InitializeResult, ServerCapabilities};
use serde_json::Value;
use super::types::LspClient;
use crate::{App, Task};
#[derive(Debug, Clone)]
pub struct ServerNotification {
pub method: String,
pub params: Value,
}
#[derive(Debug, Clone)]
pub struct ResponseError {
pub code: i64,
pub message: String,
}
impl std::fmt::Display for ResponseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "lsp error {}: {}", self.code, self.message)
}
}
impl std::error::Error for ResponseError {}
pub fn read_message(reader: &mut impl BufRead) -> std::io::Result<Option<Vec<u8>>> {
let mut content_length: Option<usize> = None;
let mut line = Vec::new();
loop {
line.clear();
let n = reader.read_until(b'\n', &mut line)?;
if n == 0 {
if content_length.is_some() {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"lsp 头部截断",
));
}
return Ok(None);
}
let text = String::from_utf8_lossy(&line);
let trimmed = text.trim();
if trimmed.is_empty() {
break;
}
if let Some(value) = trimmed
.strip_prefix("Content-Length:")
.or_else(|| trimmed.strip_prefix("content-length:"))
{
content_length = value.trim().parse::<usize>().ok();
}
}
let Some(len) = content_length else {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"lsp 缺少 Content-Length 头",
));
};
let mut body = vec![0u8; len];
reader.read_exact(&mut body)?;
Ok(Some(body))
}
pub fn encode_message(body: &[u8]) -> Vec<u8> {
let mut out = format!("Content-Length: {}\r\n\r\n", body.len()).into_bytes();
out.extend_from_slice(body);
out
}
struct Transport {
next_id: AtomicI64,
pending: Mutex<HashMap<i64, oneshot::Sender<Result<Value, ResponseError>>>>,
writer: Mutex<std::sync::mpsc::Sender<Vec<u8>>>,
notifications: Mutex<std::sync::mpsc::Receiver<ServerNotification>>,
}
impl Transport {
fn new(
reader: Box<dyn BufRead + Send>,
writer: Box<dyn Write + Send>,
stderr: Option<Box<dyn Read + Send>>,
program: &str,
) -> Arc<Self> {
let (bytes_tx, bytes_rx) = std::sync::mpsc::channel::<Vec<u8>>();
let (notify_tx, notify_rx) = std::sync::mpsc::channel::<ServerNotification>();
let this = Arc::new(Self {
next_id: AtomicI64::new(1),
pending: Mutex::new(HashMap::new()),
writer: Mutex::new(bytes_tx),
notifications: Mutex::new(notify_rx),
});
std::thread::Builder::new()
.name(format!("lsp-reader-{program}"))
.spawn({
let this = this.clone();
move || {
Self::read_loop(reader, &this, notify_tx);
this.fail_all_pending();
}
})
.ok();
std::thread::Builder::new()
.name(format!("lsp-writer-{program}"))
.spawn(move || {
Self::write_loop(writer, bytes_rx);
})
.ok();
if let Some(stderr) = stderr {
let program = program.to_string();
std::thread::Builder::new()
.name(format!("lsp-stderr-{program}"))
.spawn(move || {
Self::stderr_loop(stderr, &program);
})
.ok();
}
this
}
fn read_loop(
mut reader: Box<dyn BufRead + Send>,
this: &Arc<Self>,
notify_tx: std::sync::mpsc::Sender<ServerNotification>,
) {
loop {
let frame = match read_message(&mut reader) {
Ok(Some(body)) => body,
Ok(None) => break,
Err(error) => {
log::warn!("lsp 读帧失败: {error}");
break;
}
};
let value: Value = match serde_json::from_slice(&frame) {
Ok(value) => value,
Err(error) => {
log::warn!("lsp 非 JSON 帧丢弃: {error}");
continue;
}
};
if let Some(id) = value.get("id").and_then(Value::as_i64) {
let result = if let Some(error) = value.get("error") {
Err(ResponseError {
code: error.get("code").and_then(Value::as_i64).unwrap_or(-1),
message: error
.get("message")
.and_then(Value::as_str)
.unwrap_or("未知错误")
.to_string(),
})
} else {
Ok(value.get("result").cloned().unwrap_or(Value::Null))
};
if let Some(tx) = this.pending.lock().unwrap().remove(&id) {
let _ = tx.send(result);
}
} else if let Some(method) = value.get("method").and_then(Value::as_str) {
let notification = ServerNotification {
method: method.to_string(),
params: value.get("params").cloned().unwrap_or(Value::Null),
};
if notify_tx.send(notification).is_err() {
break;
}
} else {
log::warn!("lsp 无 id 无 method 帧丢弃");
}
}
}
fn write_loop(mut writer: Box<dyn Write + Send>, rx: std::sync::mpsc::Receiver<Vec<u8>>) {
for bytes in rx {
if writer
.write_all(&bytes)
.and_then(|_| writer.flush())
.is_err()
{
break;
}
}
}
fn stderr_loop(reader: Box<dyn Read + Send>, program: &str) {
let mut reader = BufReader::new(reader);
let mut line = Vec::new();
loop {
line.clear();
match reader.read_until(b'\n', &mut line) {
Ok(0) => break,
Ok(_) => {
log::warn!(
"lsp[{program}] {}",
String::from_utf8_lossy(&line).trim_end()
);
}
Err(_) => break,
}
}
}
fn fail_all_pending(&self) {
let mut pending = self.pending.lock().unwrap();
for (_, tx) in pending.drain() {
let _ = tx.send(Err(ResponseError {
code: -1,
message: "lsp 传输已断开".to_string(),
}));
}
}
fn request(
self: &Arc<Self>,
method: &str,
params: Value,
) -> impl Future<Output = Result<Value>> + Send + 'static {
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
let body = serde_json::json!({
"jsonrpc": "2.0",
"id": id,
"method": method,
"params": params,
});
let (tx, rx) = oneshot::channel();
self.pending.lock().unwrap().insert(id, tx);
let queued = self
.writer
.lock()
.unwrap()
.send(encode_message(
&serde_json::to_vec(&body).unwrap_or_default(),
))
.is_ok();
if !queued {
self.pending.lock().unwrap().remove(&id);
}
async move {
if !queued {
anyhow::bail!("lsp 写队列已断开");
}
rx.await
.map_err(|_| anyhow::anyhow!("lsp 请求无响应(传输已断开)"))?
.map_err(anyhow::Error::from)
}
}
fn notify(&self, method: &str, params: Value) {
let body = serde_json::json!({
"jsonrpc": "2.0",
"method": method,
"params": params,
});
if self
.writer
.lock()
.unwrap()
.send(encode_message(
&serde_json::to_vec(&body).unwrap_or_default(),
))
.is_err()
{
log::warn!("lsp 通知发送失败(传输已断开): {method}");
}
}
fn try_recv_notification(&self) -> Option<ServerNotification> {
self.notifications.lock().unwrap().try_recv().ok()
}
}
pub struct StdioLspClient {
transport: Arc<Transport>,
capabilities: ServerCapabilities,
child: Mutex<Option<Child>>,
}
impl StdioLspClient {
pub fn spawn(command: &str, args: &[&str]) -> Result<Self> {
let mut child = Command::new(command)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.with_context(|| format!("lsp 启动失败: {command}"))?;
let stdin: ChildStdin = child.stdin.take().context("lsp stdin 管道缺失")?;
let stdout: ChildStdout = child.stdout.take().context("lsp stdout 管道缺失")?;
let stderr = child.stderr.take();
let transport = Transport::new(
Box::new(BufReader::new(stdout)),
Box::new(stdin),
stderr.map(|stderr| Box::new(stderr) as Box<dyn Read + Send>),
command,
);
Ok(Self {
transport,
capabilities: ServerCapabilities::default(),
child: Mutex::new(Some(child)),
})
}
pub fn set_capabilities(&mut self, capabilities: ServerCapabilities) {
self.capabilities = capabilities;
}
pub fn try_recv_notification(&self) -> Option<ServerNotification> {
self.transport.try_recv_notification()
}
fn request(&self, method: &str, params: Value, cx: &mut App) -> Task<Result<Value>> {
cx.background_executor()
.spawn(self.request_future(method, params))
}
fn request_future(
&self,
method: &str,
params: Value,
) -> impl Future<Output = Result<Value>> + Send + 'static {
self.transport.request(method, params)
}
}
impl Drop for StdioLspClient {
fn drop(&mut self) {
if let Some(mut child) = self.child.lock().unwrap().take() {
let _ = child.kill();
let _ = child.try_wait();
}
}
}
fn from_result<T: serde::de::DeserializeOwned + Default>(value: Value) -> Result<T> {
if value.is_null() {
return Ok(T::default());
}
Ok(serde_json::from_value(value)?)
}
impl LspClient for StdioLspClient {
fn server_capabilities(&self) -> &ServerCapabilities {
&self.capabilities
}
fn initialize(&self, params: InitializeParams, cx: &mut App) -> Task<Result<InitializeResult>> {
let task = self.request(
"initialize",
serde_json::to_value(params).unwrap_or(Value::Null),
cx,
);
cx.background_executor().spawn(async move {
let value = task.await?;
Ok(serde_json::from_value(value)?)
})
}
fn shutdown(&self, cx: &mut App) -> Task<Result<()>> {
let task = self.request("shutdown", Value::Null, cx);
let transport = self.transport.clone();
cx.background_executor().spawn(async move {
task.await?;
transport.notify("exit", Value::Null);
Ok(())
})
}
fn completions(
&self,
params: lsp_types::CompletionParams,
cx: &mut App,
) -> Task<Result<lsp_types::CompletionResponse>> {
let task = self.request(
"textDocument/completion",
serde_json::to_value(params).unwrap_or(Value::Null),
cx,
);
cx.background_executor().spawn(async move {
let value = task.await?;
if value.is_null() {
return Ok(lsp_types::CompletionResponse::Array(Vec::new()));
}
Ok(serde_json::from_value(value)?)
})
}
fn hover(
&self,
params: lsp_types::HoverParams,
cx: &mut App,
) -> Task<Result<Option<lsp_types::Hover>>> {
let task = self.request(
"textDocument/hover",
serde_json::to_value(params).unwrap_or(Value::Null),
cx,
);
cx.background_executor()
.spawn(async move { Ok(serde_json::from_value(task.await?)?) })
}
fn definition(
&self,
params: lsp_types::GotoDefinitionParams,
cx: &mut App,
) -> Task<Result<lsp_types::GotoDefinitionResponse>> {
let task = self.request(
"textDocument/definition",
serde_json::to_value(params).unwrap_or(Value::Null),
cx,
);
cx.background_executor().spawn(async move {
let value = task.await?;
if value.is_null() {
return Ok(lsp_types::GotoDefinitionResponse::Array(Vec::new()));
}
Ok(serde_json::from_value(value)?)
})
}
fn references(
&self,
params: lsp_types::ReferenceParams,
cx: &mut App,
) -> Task<Result<Vec<lsp_types::Location>>> {
let task = self.request(
"textDocument/references",
serde_json::to_value(params).unwrap_or(Value::Null),
cx,
);
cx.background_executor()
.spawn(async move { from_result(task.await?) })
}
fn diagnostics(
&self,
params: lsp_types::DocumentDiagnosticParams,
cx: &mut App,
) -> Task<Result<lsp_types::DocumentDiagnosticReport>> {
let task = self.request(
"textDocument/diagnostic",
serde_json::to_value(params).unwrap_or(Value::Null),
cx,
);
cx.background_executor()
.spawn(async move { Ok(serde_json::from_value(task.await?)?) })
}
fn semantic_tokens_full(
&self,
params: lsp_types::SemanticTokensParams,
cx: &mut App,
) -> Task<Result<Option<lsp_types::SemanticTokensResult>>> {
let task = self.request(
"textDocument/semanticTokens/full",
serde_json::to_value(params).unwrap_or(Value::Null),
cx,
);
cx.background_executor()
.spawn(async move { Ok(serde_json::from_value(task.await?)?) })
}
fn semantic_tokens_range(
&self,
params: lsp_types::SemanticTokensRangeParams,
cx: &mut App,
) -> Task<Result<Option<lsp_types::SemanticTokensRangeResult>>> {
let task = self.request(
"textDocument/semanticTokens/range",
serde_json::to_value(params).unwrap_or(Value::Null),
cx,
);
cx.background_executor()
.spawn(async move { Ok(serde_json::from_value(task.await?)?) })
}
fn code_actions(
&self,
params: lsp_types::CodeActionParams,
cx: &mut App,
) -> Task<Result<Vec<lsp_types::CodeActionOrCommand>>> {
let task = self.request(
"textDocument/codeAction",
serde_json::to_value(params).unwrap_or(Value::Null),
cx,
);
cx.background_executor()
.spawn(async move { from_result(task.await?) })
}
fn formatting(
&self,
params: lsp_types::DocumentFormattingParams,
cx: &mut App,
) -> Task<Result<Vec<lsp_types::TextEdit>>> {
let task = self.request(
"textDocument/formatting",
serde_json::to_value(params).unwrap_or(Value::Null),
cx,
);
cx.background_executor()
.spawn(async move { from_result(task.await?) })
}
fn rename(
&self,
params: lsp_types::RenameParams,
cx: &mut App,
) -> Task<Result<Option<lsp_types::WorkspaceEdit>>> {
let task = self.request(
"textDocument/rename",
serde_json::to_value(params).unwrap_or(Value::Null),
cx,
);
cx.background_executor()
.spawn(async move { Ok(serde_json::from_value(task.await?)?) })
}
fn document_highlights(
&self,
params: lsp_types::DocumentHighlightParams,
cx: &mut App,
) -> Task<Result<Vec<lsp_types::DocumentHighlight>>> {
let task = self.request(
"textDocument/documentHighlight",
serde_json::to_value(params).unwrap_or(Value::Null),
cx,
);
cx.background_executor()
.spawn(async move { from_result(task.await?) })
}
fn signature_help(
&self,
params: lsp_types::SignatureHelpParams,
cx: &mut App,
) -> Task<Result<Option<lsp_types::SignatureHelp>>> {
let task = self.request(
"textDocument/signatureHelp",
serde_json::to_value(params).unwrap_or(Value::Null),
cx,
);
cx.background_executor()
.spawn(async move { Ok(serde_json::from_value(task.await?)?) })
}
fn did_change(
&self,
identifier: lsp_types::TextDocumentIdentifier,
changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
_cx: &mut App,
) {
self.transport.notify(
"textDocument/didChange",
serde_json::json!({
"textDocument": identifier,
"contentChanges": changes,
}),
);
}
fn did_open(
&self,
identifier: lsp_types::TextDocumentIdentifier,
language_id: &str,
text: &str,
_cx: &mut App,
) {
self.transport.notify(
"textDocument/didOpen",
serde_json::json!({
"textDocument": {
"uri": identifier.uri,
"languageId": language_id,
"version": 0,
"text": text,
},
}),
);
}
fn did_close(&self, identifier: lsp_types::TextDocumentIdentifier, _cx: &mut App) {
self.transport.notify(
"textDocument/didClose",
serde_json::json!({ "textDocument": identifier }),
);
}
fn did_save(
&self,
identifier: lsp_types::TextDocumentIdentifier,
text: Option<String>,
_cx: &mut App,
) {
self.transport.notify(
"textDocument/didSave",
serde_json::json!({ "textDocument": identifier, "text": text }),
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
use std::net::{TcpListener, TcpStream};
#[test]
fn frame_roundtrip() {
let bodies: Vec<Vec<u8>> = vec![b"{}".to_vec(), b"{\"id\":1}".to_vec()];
let mut stream = Vec::new();
for body in &bodies {
stream.extend_from_slice(&encode_message(body));
}
let lower =
String::from_utf8_lossy(&stream).replacen("Content-Length", "content-length", 1);
let mut reader = Cursor::new(lower.into_bytes());
for body in &bodies {
assert_eq!(read_message(&mut reader).unwrap(), Some(body.clone()));
}
assert_eq!(read_message(&mut reader).unwrap(), None);
}
#[test]
fn frame_errors() {
let mut reader = Cursor::new(b"{}\r\n\r\n".to_vec());
assert!(read_message(&mut reader).is_err());
let mut reader = Cursor::new(b"Content-Length: 10\r\n\r\nabc".to_vec());
assert!(read_message(&mut reader).is_err());
}
#[test]
fn transport_request_response_notification() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let server = std::thread::spawn(move || {
let (stream, _) = listener.accept().unwrap();
let mut reader = std::io::BufReader::new(stream.try_clone().unwrap());
let mut writer = stream;
let frame = read_message(&mut reader).unwrap().unwrap();
let request: Value = serde_json::from_slice(&frame).unwrap();
assert_eq!(request["method"], "textDocument/completion");
let id = request["id"].clone();
let response = serde_json::json!({ "jsonrpc": "2.0", "id": id, "result": [] });
writer
.write_all(&encode_message(&serde_json::to_vec(&response).unwrap()))
.unwrap();
writer.flush().unwrap();
let notification =
serde_json::json!({ "jsonrpc": "2.0", "method": "m", "params": { "a": 1 } });
writer
.write_all(&encode_message(&serde_json::to_vec(¬ification).unwrap()))
.unwrap();
writer.flush().unwrap();
});
let stream = TcpStream::connect(addr).unwrap();
let transport = Transport::new(
Box::new(BufReader::new(stream.try_clone().unwrap())),
Box::new(stream),
None,
"test",
);
let value =
futures::executor::block_on(transport.request("textDocument/completion", Value::Null))
.unwrap();
assert_eq!(value, Value::Array(vec![]));
let mut notification = None;
for _ in 0..100 {
notification = transport.try_recv_notification();
if notification.is_some() {
break;
}
std::thread::sleep(std::time::Duration::from_millis(10));
}
let notification = notification.expect("服务端通知应到达");
assert_eq!(notification.method, "m");
server.join().unwrap();
}
#[test]
fn spawn_missing_program_errors() {
match StdioLspClient::spawn("rgpui-test-only-missing-program", &[]) {
Ok(_) => panic!("不存在的程序应启动失败"),
Err(error) => assert!(
error
.to_string()
.contains("rgpui-test-only-missing-program")
),
}
}
#[test]
fn client_completions_mapping() {
use std::time::Duration;
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
std::thread::spawn(move || {
let (stream, _) = listener.accept().unwrap();
let mut reader = std::io::BufReader::new(stream.try_clone().unwrap());
let mut writer = stream;
while let Ok(Some(frame)) = read_message(&mut reader) {
let request: Value = serde_json::from_slice(&frame).unwrap();
assert_eq!(
request["method"], "textDocument/completion",
"方法名映射必须正确",
);
let response = serde_json::json!({
"jsonrpc": "2.0",
"id": request["id"],
"result": [{ "label": "println" }],
});
writer
.write_all(&encode_message(&serde_json::to_vec(&response).unwrap()))
.unwrap();
writer.flush().unwrap();
}
});
let stream = std::net::TcpStream::connect(addr).unwrap();
stream
.set_read_timeout(Some(Duration::from_secs(10)))
.unwrap();
let transport = Transport::new(
Box::new(BufReader::new(stream.try_clone().unwrap())),
Box::new(stream),
None,
"test",
);
let client = StdioLspClient {
transport,
capabilities: ServerCapabilities::default(),
child: Mutex::new(None),
};
let params = serde_json::json!({
"textDocument": { "uri": "file:///a.rs" },
"position": { "line": 0, "character": 0 },
});
let value =
futures::executor::block_on(client.request_future("textDocument/completion", params))
.unwrap();
let response: lsp_types::CompletionResponse = serde_json::from_value(value).unwrap();
match response {
lsp_types::CompletionResponse::Array(items) => {
assert_eq!(items.len(), 1);
assert_eq!(items[0].label, "println");
}
other => panic!("应为数组响应: {other:?}"),
}
}
}