From ff58a92b3f03f40473faf129ddf1b465f99fab6b Mon Sep 17 00:00:00 2001 From: Selimaj Dev Date: Sat, 23 May 2026 14:55:44 +0200 Subject: [PATCH] Rust analyzer init --- Cargo.lock | 18 ++++- crates/analyzer/Cargo.toml | 4 + crates/analyzer/src/lib.rs | 4 + crates/analyzer/src/rust_analyzer.rs | 110 +++++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 crates/analyzer/src/rust_analyzer.rs diff --git a/Cargo.lock b/Cargo.lock index 17a2a50..52a6d0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -423,6 +423,19 @@ dependencies = [ "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]] name = "memchr" version = "2.8.0" @@ -446,8 +459,11 @@ version = "0.0.2" dependencies = [ "cargo_metadata", "dashmap 6.1.0", + "lsp-types 0.95.1", "mist-codegen", "mist-parser", + "serde", + "serde_json", "tokio", "tower-lsp", ] @@ -865,7 +881,7 @@ dependencies = [ "dashmap 5.5.3", "futures", "httparse", - "lsp-types", + "lsp-types 0.94.1", "memchr", "serde", "serde_json", diff --git a/crates/analyzer/Cargo.toml b/crates/analyzer/Cargo.toml index cc3c8c6..1cc9a3f 100644 --- a/crates/analyzer/Cargo.toml +++ b/crates/analyzer/Cargo.toml @@ -16,3 +16,7 @@ dashmap = "6.1.0" tokio = { version = "1.52.3", features = ["full", "macros", "rt", "rt-multi-thread"] } tower-lsp = "0.20.0" cargo_metadata = "0.23.1" + +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +lsp-types = "0.95" diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index fbbffe0..eca138e 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -1,4 +1,5 @@ pub mod builder; +pub mod rust_analyzer; pub mod transpiler; use std::collections::HashMap; @@ -72,6 +73,9 @@ impl LanguageServer for Backend { tokio::spawn(async move { if let Some(root) = &*workspace_folder.lock().await { transpiler::build(root); + rust_analyzer::initialize(root) + .await + .expect("Failed to initialize rust analyzer"); } }); diff --git a/crates/analyzer/src/rust_analyzer.rs b/crates/analyzer/src/rust_analyzer.rs new file mode 100644 index 0000000..d0ec471 --- /dev/null +++ b/crates/analyzer/src/rust_analyzer.rs @@ -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( + 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( + reader: &mut R, +) -> Result> { + 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::()?; + } + } + + 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> { + // 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(()) +}