use serde_json::Value;
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, DuplexStream};
use tokio::time::Duration;
use crate::config::LspServerConfig;
use crate::lsp::{LspClient, LspTransport};
const MOCK_PIPE_CAPACITY: usize = 256 * 1024;
const READ_FRAME_TIMEOUT: Duration = Duration::from_secs(30);
pub struct FakeServer {
pub read_half_stdin: DuplexStream,
pub write_stdout: DuplexStream,
}
pub fn fake_lsp_client() -> (LspClient, FakeServer) {
fake_lsp_client_with_config(LspServerConfig::rust_analyzer())
}
pub fn fake_lsp_client_with_config(config: LspServerConfig) -> (LspClient, FakeServer) {
let (client_stdin, write_stdout) = tokio::io::duplex(MOCK_PIPE_CAPACITY);
let (read_half_stdin, client_stdout) = tokio::io::duplex(MOCK_PIPE_CAPACITY);
let transport = LspTransport::new(client_stdin, client_stdout);
let client = LspClient::from_transport(config, transport);
(
client,
FakeServer {
read_half_stdin,
write_stdout,
},
)
}
#[must_use]
pub fn inert_transport() -> LspTransport {
let (stdin, _unused_read) = tokio::io::duplex(1);
let (_unused_write, stdout) = tokio::io::duplex(1);
LspTransport::new(stdin, stdout)
}
pub async fn read_framed_message(reader: &mut BufReader<&mut DuplexStream>) -> Value {
tokio::time::timeout(READ_FRAME_TIMEOUT, read_framed_message_inner(reader))
.await
.expect("timed out waiting for a complete framed JSON-RPC message")
}
async fn read_framed_message_inner(reader: &mut BufReader<&mut DuplexStream>) -> Value {
let mut content_length = None;
let mut line = String::new();
loop {
line.clear();
let bytes_read = reader.read_line(&mut line).await.unwrap();
assert!(bytes_read != 0, "EOF before a complete frame was read");
if line == "\r\n" || line == "\n" {
break;
}
if let Some((key, value)) = line.trim_end().split_once(':')
&& key.trim().eq_ignore_ascii_case("content-length")
{
content_length = Some(value.trim().parse::<usize>().unwrap());
}
}
let mut buf = vec![0u8; content_length.unwrap()];
reader.read_exact(&mut buf).await.unwrap();
serde_json::from_slice(&buf).unwrap()
}
pub async fn write_response(writer: &mut DuplexStream, id: &Value, result: Value) {
write_framed(
writer,
&serde_json::json!({
"jsonrpc": "2.0",
"id": id,
"result": result,
}),
)
.await;
}
pub async fn write_error_response(writer: &mut DuplexStream, id: &Value, code: i64, message: &str) {
write_framed(
writer,
&serde_json::json!({
"jsonrpc": "2.0",
"id": id,
"error": { "code": code, "message": message },
}),
)
.await;
}
async fn write_framed(writer: &mut DuplexStream, message: &Value) {
let content = serde_json::to_string(message).unwrap();
let header = format!("Content-Length: {}\r\n\r\n", content.len());
writer.write_all(header.as_bytes()).await.unwrap();
writer.write_all(content.as_bytes()).await.unwrap();
writer.flush().await.unwrap();
}
#[derive(Clone, Default)]
pub struct CapturedLogs(std::sync::Arc<std::sync::Mutex<Vec<(tracing::Level, String)>>>);
impl CapturedLogs {
pub fn entries(&self) -> Vec<(tracing::Level, String)> {
self.0.lock().unwrap().clone()
}
pub fn messages(&self) -> Vec<String> {
self.0
.lock()
.unwrap()
.iter()
.map(|(_, message)| message.clone())
.collect()
}
}
impl<S: tracing::Subscriber> tracing_subscriber::Layer<S> for CapturedLogs {
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
struct MessageVisitor(String);
impl tracing::field::Visit for MessageVisitor {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
if field.name() == "message" {
self.0 = format!("{value:?}");
}
}
}
let mut visitor = MessageVisitor(String::new());
event.record(&mut visitor);
self.0
.lock()
.unwrap()
.push((*event.metadata().level(), visitor.0));
}
}