diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index c25d4ec..e7457f8 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -7,8 +7,10 @@ use std::fs; use std::path::{Component, PathBuf}; use std::sync::Arc; +use serde_json::Value; use tokio::sync::Mutex; use tower_lsp::jsonrpc::Result; +use tower_lsp::lsp_types::request::Request; use tower_lsp::lsp_types::*; use tower_lsp::{Client, LanguageServer, LspService, Server}; @@ -82,7 +84,7 @@ impl LanguageServer for Backend { analyzer .lock() .await - .init(root) + .initialize(root) .await .expect("Failed to initialize rust analyzer"); } @@ -95,6 +97,13 @@ impl LanguageServer for Backend { self.client .log_message(MessageType::INFO, "server initialized!") .await; + + self.rust_analyzer + .lock() + .await + .initialized() + .await + .expect("Failed to initialize rust analyzer"); } async fn shutdown(&self) -> Result<()> { @@ -231,23 +240,36 @@ impl LanguageServer for Backend { self.rust_analyzer .lock() .await - .request::(lsp_types::GotoDefinitionParams { - text_document_position_params: lsp_types::TextDocumentPositionParams { - position: lsp_types::Position { - line: line as u32, - character: character as u32, - }, - text_document: lsp_types::TextDocumentIdentifier { - uri: Url::from_file_path(from_mist_to_rust(file_path)) - .expect("failed to generate rs url"), + .send( + request::GotoDefinition::METHOD, + lsp_types::GotoDefinitionParams { + text_document_position_params: lsp_types::TextDocumentPositionParams { + position: lsp_types::Position { + line: line as u32, + character: character as u32, + }, + text_document: lsp_types::TextDocumentIdentifier { + uri: Url::from_file_path(from_mist_to_rust(file_path)) + .expect("failed to generate rs url"), + }, }, + partial_result_params: lsp_types::PartialResultParams::default(), + work_done_progress_params: lsp_types::WorkDoneProgressParams::default(), }, - partial_result_params: lsp_types::PartialResultParams::default(), - work_done_progress_params: lsp_types::WorkDoneProgressParams::default(), - }) + ) .await .expect("Failed to send to rust"); + self.client + .log_message( + MessageType::INFO, + format!( + "{:?}", + self.rust_analyzer.lock().await.read::().await + ), + ) + .await; + Ok(None) } } diff --git a/crates/analyzer/src/rust_analyzer.rs b/crates/analyzer/src/rust_analyzer.rs index c168ecb..b26e789 100644 --- a/crates/analyzer/src/rust_analyzer.rs +++ b/crates/analyzer/src/rust_analyzer.rs @@ -1,13 +1,31 @@ use std::{path::PathBuf, process::Stdio}; use lsp_types::{ - ClientCapabilities, InitializeParams, Url, WorkspaceFolder, + ClientCapabilities, InitializeParams, InitializedParams, Url, WorkspaceFolder, + notification::{Initialized, Notification}, request::{self, Request}, }; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; +#[derive(Deserialize)] +pub struct JsonRpcResponse { + pub jsonrpc: String, + // id can be a number or null, so we use serde_json::Value to be safe + pub id: serde_json::Value, + // We only unpack the result if it exists + pub result: Option, + pub error: Option, +} + +#[derive(Debug, Deserialize)] +pub struct JsonRpcError { + pub code: i64, + pub message: String, + pub data: Option, +} + #[derive(Debug)] pub struct RustAnalyzer { stdin: tokio::process::ChildStdin, @@ -103,14 +121,38 @@ impl RustAnalyzer { where T: for<'de> serde::Deserialize<'de>, { - read_lsp_message(&mut self.stdout) - .await - .map(|v| serde_json::from_str(&v).unwrap()) + let raw_string = read_lsp_message(&mut self.stdout).await?; + + let envelope: JsonRpcResponse = serde_json::from_str(&raw_string)?; + + if let Some(err) = envelope.error { + return Err(format!("LSP Error ({}): {}", err.code, err.message).into()); + } + + envelope.result.ok_or_else(|| { + format!( + "LSP response missing both result and error fields. Raw: {}", + raw_string + ) + .into() + }) + } + + pub async fn notify(&mut self, method: &str, req: T) -> std::io::Result<()> { + send_lsp_message( + &mut self.stdin, + &json!({ + "jsonrpc": "2.0", + "method": method, + "params": req, + }), + ) + .await } } impl RustAnalyzer { - pub async fn init(&mut self, root: &PathBuf) -> Result<(), Box> { + pub async fn initialize(&mut self, root: &PathBuf) -> Result<(), Box> { let project_uri = Url::from_directory_path(root) .map_err(|_| "Failed to convert path to valid file:// URL")?; @@ -135,9 +177,16 @@ impl RustAnalyzer { ..Default::default() }; - let _response = self.request::(init_params); + let response = self.request::(init_params).await; - eprintln!("<- Received: initialize response"); + eprintln!("<- Received: initialize response {response:?}"); + + Ok(()) + } + + pub async fn initialized(&mut self) -> Result<(), Box> { + self.notify(Initialized::METHOD, InitializedParams {}) + .await?; Ok(()) }