Improved rust analyzer client
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user