use std::collections::HashMap;
use std::process::Stdio;
use std::sync::Arc;
use tempfile::TempDir;
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
use tokio::process::{Child, ChildStdin, ChildStdout, Command};
use super::Translator;
use super::encoding_ctx::EncodingCtx;
use crate::bridge::encoding::PositionEncoding;
use crate::bridge::state::ResourceLimits;
use crate::bridge::{DiagnosticInfo, DocumentTracker};
use crate::config::{LspServerConfig, ServerId, ToolRouter};
use crate::lsp::{LspClient, LspServer, LspTransport};
type JsonValue = serde_json::Value;
pub(super) fn test_ctx() -> EncodingCtx {
test_ctx_with(PositionEncoding::Utf16)
}
pub(super) fn test_ctx_with(encoding: PositionEncoding) -> EncodingCtx {
EncodingCtx {
encoding,
tracker: Arc::new(DocumentTracker::new(
ResourceLimits::default(),
HashMap::new(),
)),
}
}
pub(super) fn test_uri() -> lsp_types::Uri {
"file:///test.rs".parse().unwrap()
}
pub(super) fn test_tracker() -> Arc<DocumentTracker> {
Arc::new(DocumentTracker::new(
ResourceLimits::default(),
HashMap::new(),
))
}
pub(super) fn lsp_diag(
line: u32,
end_character: u32,
severity: lsp_types::DiagnosticSeverity,
message: &str,
code: Option<&str>,
) -> lsp_types::Diagnostic {
lsp_types::Diagnostic {
range: lsp_types::Range {
start: lsp_types::Position { line, character: 0 },
end: lsp_types::Position {
line,
character: end_character,
},
},
severity: Some(severity),
message: message.to_string(),
code: code.map(|c| lsp_types::NumberOrString::String(c.to_string())),
source: None,
code_description: None,
related_information: None,
tags: None,
data: None,
}
}
pub(super) fn diag_info(diagnostics: Vec<lsp_types::Diagnostic>) -> DiagnosticInfo {
DiagnosticInfo {
uri: "file:///test.rs".parse().unwrap(),
version: Some(1),
diagnostics,
}
}
pub(super) struct FakeServer {
_write_half: Child,
_read_half: Child,
pub(super) read_half_stdin: ChildStdin,
pub(super) write_stdout: ChildStdout,
}
pub(super) fn fake_lsp_client() -> (LspClient, FakeServer) {
let mut write_half = Command::new("cat")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.kill_on_drop(true)
.spawn()
.unwrap();
let write_stdin = write_half.stdin.take().unwrap();
let write_stdout = write_half.stdout.take().unwrap();
let mut read_half = Command::new("cat")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.kill_on_drop(true)
.spawn()
.unwrap();
let read_stdout = read_half.stdout.take().unwrap();
let read_stdin = read_half.stdin.take().unwrap();
let transport = LspTransport::new(write_stdin, read_stdout);
let client = LspClient::from_transport(LspServerConfig::rust_analyzer(), transport);
(
client,
FakeServer {
_write_half: write_half,
_read_half: read_half,
read_half_stdin: read_stdin,
write_stdout,
},
)
}
pub(super) async fn read_framed_message(reader: &mut BufReader<&mut ChildStdout>) -> JsonValue {
let mut content_length = None;
let mut line = String::new();
loop {
line.clear();
reader.read_line(&mut line).await.unwrap();
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(super) async fn write_response(stdin: &mut ChildStdin, id: &JsonValue, result: JsonValue) {
let message = serde_json::json!({
"jsonrpc": "2.0",
"id": id,
"result": result,
});
let content = serde_json::to_string(&message).unwrap();
let header = format!("Content-Length: {}\r\n\r\n", content.len());
stdin.write_all(header.as_bytes()).await.unwrap();
stdin.write_all(content.as_bytes()).await.unwrap();
stdin.flush().await.unwrap();
}
pub(super) async fn write_error_response(
stdin: &mut ChildStdin,
id: &JsonValue,
code: i64,
message: &str,
) {
let response = serde_json::json!({
"jsonrpc": "2.0",
"id": id,
"error": {
"code": code,
"message": message,
},
});
let content = serde_json::to_string(&response).unwrap();
let header = format!("Content-Length: {}\r\n\r\n", content.len());
stdin.write_all(header.as_bytes()).await.unwrap();
stdin.write_all(content.as_bytes()).await.unwrap();
stdin.flush().await.unwrap();
}
pub(super) fn translator_with_capabilities(
dir: &TempDir,
server_id: &ServerId,
capabilities: lsp_types::ServerCapabilities,
) -> (Translator, FakeServer) {
let mut extensions = HashMap::new();
extensions.insert("rs".to_string(), "rust".to_string());
let mut translator =
Translator::new()
.with_extensions(extensions)
.with_router(ToolRouter::catch_all([(
server_id.clone(),
"rust".to_string(),
)]));
translator.set_workspace_roots(vec![dir.path().to_path_buf()]);
let (client, server) = fake_lsp_client();
translator.register_client(server_id.clone(), client);
translator.register_server(server_id.clone(), LspServer::new_for_test(capabilities));
(translator, server)
}
pub(super) fn translator_with_capabilities_and_encoding(
dir: &TempDir,
server_id: &ServerId,
capabilities: lsp_types::ServerCapabilities,
position_encoding: lsp_types::PositionEncodingKind,
) -> (Translator, FakeServer) {
let mut extensions = HashMap::new();
extensions.insert("rs".to_string(), "rust".to_string());
let mut translator =
Translator::new()
.with_extensions(extensions)
.with_router(ToolRouter::catch_all([(
server_id.clone(),
"rust".to_string(),
)]));
translator.set_workspace_roots(vec![dir.path().to_path_buf()]);
let (client, server) = fake_lsp_client();
translator.register_client(server_id.clone(), client);
translator.register_server(
server_id.clone(),
LspServer::new_for_test_with_encoding(capabilities, position_encoding),
);
(translator, server)
}