Improved rust analyzer client

This commit is contained in:
2026-05-23 15:26:15 +02:00
parent ff58a92b3f
commit c405a2059e
2 changed files with 111 additions and 67 deletions
+13 -1
View File
@@ -13,12 +13,14 @@ use tower_lsp::lsp_types::*;
use tower_lsp::{Client, LanguageServer, LspService, Server}; use tower_lsp::{Client, LanguageServer, LspService, Server};
use crate::builder::MistDiagnostic; use crate::builder::MistDiagnostic;
use crate::rust_analyzer::RustAnalyzer;
#[derive(Debug)] #[derive(Debug)]
struct Backend { struct Backend {
client: Client, client: Client,
workspace_folder: Arc<Mutex<Option<PathBuf>>>, workspace_folder: Arc<Mutex<Option<PathBuf>>>,
previous_diagnostics: Arc<Mutex<HashMap<Url, Vec<Diagnostic>>>>, previous_diagnostics: Arc<Mutex<HashMap<Url, Vec<Diagnostic>>>>,
rust_analyzer: Arc<Mutex<RustAnalyzer>>,
} }
/// Helper function to force percent-encoding on Windows drive colons /// 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 workspace_folder = self.workspace_folder.clone();
let analyzer = self.rust_analyzer.clone();
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)
analyzer
.lock()
.await
.init(root)
.await .await
.expect("Failed to initialize rust analyzer"); .expect("Failed to initialize rust analyzer");
} }
@@ -231,6 +240,9 @@ pub async fn start() {
client, client,
workspace_folder: Arc::new(Mutex::new(None)), workspace_folder: Arc::new(Mutex::new(None)),
previous_diagnostics: Arc::new(Mutex::new(HashMap::new())), 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; Server::new(stdin, stdout, socket).serve(service).await;
} }
+98 -66
View File
@@ -1,9 +1,106 @@
use std::{path::PathBuf, process::Stdio}; 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 serde_json::json;
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
#[derive(Debug)]
pub struct RustAnalyzer {
stdin: tokio::process::ChildStdin,
stdout: BufReader<tokio::process::ChildStdout>,
id: usize,
}
impl RustAnalyzer {
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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::<request::Initialize>(init_params);
eprintln!("<- Received: initialize response");
Ok(())
}
pub async fn request<R: Request>(
&mut self,
params: R::Params,
) -> Result<R::Result, Box<dyn std::error::Error>> {
self.send(R::METHOD, params).await?;
self.read().await
}
pub async fn send<T: Serialize>(&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<T>(&mut self) -> Result<T, Box<dyn std::error::Error>>
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<W: AsyncWriteExt + Unpin>( async fn send_lsp_message<W: AsyncWriteExt + Unpin>(
writer: &mut W, writer: &mut W,
value: &serde_json::Value, value: &serde_json::Value,
@@ -43,68 +140,3 @@ async fn read_lsp_message<R: AsyncBufReadExt + Unpin>(
Ok(String::from_utf8(buffer)?) 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(())
}