diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index eca138e..69ca1e4 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -13,12 +13,14 @@ use tower_lsp::lsp_types::*; use tower_lsp::{Client, LanguageServer, LspService, Server}; use crate::builder::MistDiagnostic; +use crate::rust_analyzer::RustAnalyzer; #[derive(Debug)] struct Backend { client: Client, workspace_folder: Arc>>, previous_diagnostics: Arc>>>, + rust_analyzer: Arc>, } /// Helper function to force percent-encoding on Windows drive colons @@ -70,10 +72,17 @@ impl LanguageServer for Backend { } let workspace_folder = self.workspace_folder.clone(); + + let analyzer = self.rust_analyzer.clone(); + tokio::spawn(async move { if let Some(root) = &*workspace_folder.lock().await { transpiler::build(root); - rust_analyzer::initialize(root) + + analyzer + .lock() + .await + .init(root) .await .expect("Failed to initialize rust analyzer"); } @@ -231,6 +240,9 @@ pub async fn start() { client, workspace_folder: Arc::new(Mutex::new(None)), previous_diagnostics: Arc::new(Mutex::new(HashMap::new())), + rust_analyzer: Arc::new(Mutex::new( + RustAnalyzer::new().expect("Failed to create rust analyzer"), + )), }); Server::new(stdin, stdout, socket).serve(service).await; } diff --git a/crates/analyzer/src/rust_analyzer.rs b/crates/analyzer/src/rust_analyzer.rs index d0ec471..5ceef93 100644 --- a/crates/analyzer/src/rust_analyzer.rs +++ b/crates/analyzer/src/rust_analyzer.rs @@ -1,9 +1,106 @@ use std::{path::PathBuf, process::Stdio}; -use lsp_types::{ClientCapabilities, InitializeParams, InitializeResult, Url, WorkspaceFolder}; +use lsp_types::{ + ClientCapabilities, InitializeParams, Url, WorkspaceFolder, + request::{self, Request}, +}; +use serde::Serialize; use serde_json::json; use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; +#[derive(Debug)] +pub struct RustAnalyzer { + stdin: tokio::process::ChildStdin, + stdout: BufReader, + id: usize, +} + +impl RustAnalyzer { + pub fn new() -> Result> { + let mut child = tokio::process::Command::new("rust-analyzer") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) // Ignore logs for simplicity + .spawn()?; + + let stdin = child.stdin.take().unwrap(); + let stdout = BufReader::new(child.stdout.take().unwrap()); + + Ok(Self { + stdin, + stdout, + id: 0, + }) + } + + pub async fn init(&mut self, root: &PathBuf) -> Result<(), Box> { + 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()), + root_uri: Some(project_uri.clone()), + 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 _response = self.request::(init_params); + + eprintln!("<- Received: initialize response"); + + Ok(()) + } + + pub async fn request( + &mut self, + params: R::Params, + ) -> Result> { + self.send(R::METHOD, params).await?; + self.read().await + } + + pub async fn send(&mut self, method: &str, req: T) -> std::io::Result<()> { + let id = { + self.id += 1; + self.id + }; + + send_lsp_message( + &mut self.stdin, + &json!({ + "jsonrpc": "2.0", + "id": id, + "method": method, + "params": req, + }), + ) + .await + } + + pub async fn read(&mut self) -> Result> + where + T: for<'de> serde::Deserialize<'de>, + { + read_lsp_message(&mut self.stdout) + .await + .map(|v| serde_json::from_str(&v).unwrap()) + } +} + async fn send_lsp_message( writer: &mut W, value: &serde_json::Value, @@ -43,68 +140,3 @@ async fn read_lsp_message( 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(()) -}