Rust analyzer init

This commit is contained in:
2026-05-23 14:55:44 +02:00
parent 763473ef57
commit ff58a92b3f
4 changed files with 135 additions and 1 deletions
Generated
+17 -1
View File
@@ -423,6 +423,19 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "lsp-types"
version = "0.95.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e34d33a8e9b006cd3fc4fe69a921affa097bae4bb65f76271f4644f9a334365"
dependencies = [
"bitflags 1.3.2",
"serde",
"serde_json",
"serde_repr",
"url",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.0" version = "2.8.0"
@@ -446,8 +459,11 @@ version = "0.0.2"
dependencies = [ dependencies = [
"cargo_metadata", "cargo_metadata",
"dashmap 6.1.0", "dashmap 6.1.0",
"lsp-types 0.95.1",
"mist-codegen", "mist-codegen",
"mist-parser", "mist-parser",
"serde",
"serde_json",
"tokio", "tokio",
"tower-lsp", "tower-lsp",
] ]
@@ -865,7 +881,7 @@ dependencies = [
"dashmap 5.5.3", "dashmap 5.5.3",
"futures", "futures",
"httparse", "httparse",
"lsp-types", "lsp-types 0.94.1",
"memchr", "memchr",
"serde", "serde",
"serde_json", "serde_json",
+4
View File
@@ -16,3 +16,7 @@ dashmap = "6.1.0"
tokio = { version = "1.52.3", features = ["full", "macros", "rt", "rt-multi-thread"] } tokio = { version = "1.52.3", features = ["full", "macros", "rt", "rt-multi-thread"] }
tower-lsp = "0.20.0" tower-lsp = "0.20.0"
cargo_metadata = "0.23.1" cargo_metadata = "0.23.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
lsp-types = "0.95"
+4
View File
@@ -1,4 +1,5 @@
pub mod builder; pub mod builder;
pub mod rust_analyzer;
pub mod transpiler; pub mod transpiler;
use std::collections::HashMap; use std::collections::HashMap;
@@ -72,6 +73,9 @@ impl LanguageServer for Backend {
tokio::spawn(async move { tokio::spawn(async move {
if let Some(root) = &*workspace_folder.lock().await { if let Some(root) = &*workspace_folder.lock().await {
transpiler::build(root); transpiler::build(root);
rust_analyzer::initialize(root)
.await
.expect("Failed to initialize rust analyzer");
} }
}); });
+110
View File
@@ -0,0 +1,110 @@
use std::{path::PathBuf, process::Stdio};
use lsp_types::{ClientCapabilities, InitializeParams, InitializeResult, Url, WorkspaceFolder};
use serde_json::json;
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
async fn send_lsp_message<W: AsyncWriteExt + Unpin>(
writer: &mut W,
value: &serde_json::Value,
) -> std::io::Result<()> {
let payload = serde_json::to_string(value)?;
let frame = format!("Content-Length: {}\r\n\r\n{}", payload.len(), payload);
writer.write_all(frame.as_bytes()).await?;
writer.flush().await?;
Ok(())
}
async fn read_lsp_message<R: AsyncBufReadExt + Unpin>(
reader: &mut R,
) -> Result<String, Box<dyn std::error::Error>> {
let mut line = String::new();
let mut content_length = 0;
// Read headers until we hit the empty separator line (\r\n)
loop {
line.clear();
reader.read_line(&mut line).await?;
if line == "\r\n" || line.is_empty() {
break;
}
if line.to_lowercase().starts_with("content-length:") {
content_length = line["content-length:".len()..].trim().parse::<usize>()?;
}
}
if content_length == 0 {
return Err("Missing or invalid Content-Length header".into());
}
// Read the exact byte buffer payload
let mut buffer = vec![0u8; content_length];
reader.read_exact(&mut buffer).await?;
Ok(String::from_utf8(buffer)?)
}
pub async fn initialize(root: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
// Step 1: Spawn rust-analyzer
let mut child = tokio::process::Command::new("rust-analyzer")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null()) // Ignore logs for simplicity
.spawn()?;
let mut stdin = child.stdin.take().unwrap();
let mut stdout = BufReader::new(child.stdout.take().unwrap());
let project_uri = Url::from_directory_path(root)
.map_err(|_| "Failed to convert path to valid file:// URL")?;
#[allow(deprecated)]
let init_params = InitializeParams {
process_id: Some(std::process::id()), // Highly recommended so RA knows if your client dies
root_uri: Some(project_uri.clone()), // Provide root_uri as a fallback alongside folders
workspace_folders: Some(vec![WorkspaceFolder {
uri: project_uri,
name: "workspace".to_string(),
}]),
capabilities: ClientCapabilities {
workspace: Some(lsp_types::WorkspaceClientCapabilities {
workspace_folders: Some(true),
..Default::default()
}),
text_document: Some(lsp_types::TextDocumentClientCapabilities {
..Default::default()
}),
..Default::default()
},
..Default::default()
};
let init_request = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": init_params
});
send_lsp_message(&mut stdin, &init_request).await?;
eprintln!("-> Sent: initialize");
// Step 3: Read 'initialize' response
let response_str = read_lsp_message(&mut stdout).await?;
// DEBUG: If it fails again, this print will show you exactly what rust-analyzer sent back!
let json_parsed: serde_json::Value = serde_json::from_str(&response_str)?;
if let Some(error) = json_parsed.get("error") {
panic!(
"Rust-analyzer explicitly rejected our request! Error: {:#?}",
error
);
}
// If no explicit error, try parsing into the strong type
let _response: InitializeResult =
serde_json::from_value(json_parsed.get("result").cloned().unwrap_or(json_parsed))?;
eprintln!("<- Received: initialize response");
Ok(())
}