From ff58a92b3f03f40473faf129ddf1b465f99fab6b Mon Sep 17 00:00:00 2001 From: Selimaj Dev Date: Sat, 23 May 2026 14:55:44 +0200 Subject: [PATCH 01/13] 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(()) +} From c405a2059e90994db222ab12c4a49f313ce4196a Mon Sep 17 00:00:00 2001 From: Selimaj Dev Date: Sat, 23 May 2026 15:26:15 +0200 Subject: [PATCH 02/13] Improved rust analyzer client --- crates/analyzer/src/lib.rs | 14 ++- crates/analyzer/src/rust_analyzer.rs | 164 ++++++++++++++++----------- 2 files changed, 111 insertions(+), 67 deletions(-) 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(()) -} From b2f5f2fdc86200e4f204af1da58e6a8e06ae20f8 Mon Sep 17 00:00:00 2001 From: Selimaj Dev Date: Sat, 23 May 2026 15:40:47 +0200 Subject: [PATCH 03/13] Sending goto to rust --- crates/analyzer/src/lib.rs | 56 +++++++-- crates/analyzer/src/rust_analyzer.rs | 174 ++++++++++++++------------- 2 files changed, 135 insertions(+), 95 deletions(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index 69ca1e4..c25d4ec 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -197,15 +197,14 @@ impl LanguageServer for Backend { &self, params: GotoDefinitionParams, ) -> Result> { - let mut source = fs::read_to_string( - params - .text_document_position_params - .text_document - .uri - .to_file_path() - .unwrap(), - ) - .expect("Failed to read source"); + let file_path = params + .text_document_position_params + .text_document + .uri + .to_file_path() + .unwrap(); + + let mut source = fs::read_to_string(&file_path).expect("Failed to read source"); let inject = "__mist_23"; @@ -223,10 +222,32 @@ impl LanguageServer for Backend { .expect("Didn't find injection") .saturating_sub(1); + let (line, character) = find_row_col(&output, inject).unwrap(); + self.client .log_message(MessageType::INFO, format!("Found at {position}")) .await; + 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"), + }, + }, + partial_result_params: lsp_types::PartialResultParams::default(), + work_done_progress_params: lsp_types::WorkDoneProgressParams::default(), + }) + .await + .expect("Failed to send to rust"); + Ok(None) } } @@ -286,3 +307,20 @@ fn insert_at_position(s: &str, line: usize, col: usize, insert: &str) -> String lines.join("\n") } + +fn find_row_col(output: &str, inject: &str) -> Option<(usize, usize)> { + // 1. Find the flat byte index just like your original code + let byte_idx = output.find(inject)?; + + // 2. Slice the string up to the match point + let prefix = &output[..byte_idx]; + + // 3. Row = number of newlines found before the match + 1 (1-indexed) + let row = prefix.lines().count(); + + // 4. Column = character count of the remaining text on the current line + 1 + // (Using .chars().count() ensures it works with multi-byte UTF-8 symbols) + let col = prefix.lines().last().unwrap_or("").chars().count() + 1; + + Some((row, col)) +} diff --git a/crates/analyzer/src/rust_analyzer.rs b/crates/analyzer/src/rust_analyzer.rs index 5ceef93..c168ecb 100644 --- a/crates/analyzer/src/rust_analyzer.rs +++ b/crates/analyzer/src/rust_analyzer.rs @@ -15,92 +15,6 @@ pub struct RustAnalyzer { 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, @@ -140,3 +54,91 @@ async fn read_lsp_message( Ok(String::from_utf8(buffer)?) } + +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 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()) + } +} + +impl RustAnalyzer { + 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(()) + } +} From 9b1acb8cc3662153e957d7a60aaa622313ec9942 Mon Sep 17 00:00:00 2001 From: Selimaj Dev Date: Sun, 24 May 2026 06:29:58 +0200 Subject: [PATCH 04/13] Working req res --- crates/analyzer/src/lib.rs | 48 ++++++++++++++------ crates/analyzer/src/rust_analyzer.rs | 65 ++++++++++++++++++++++++---- 2 files changed, 92 insertions(+), 21 deletions(-) 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(()) } From 831fa544fcea4d615f5bb7911ce0a66322225c0b Mon Sep 17 00:00:00 2001 From: Selimaj Dev Date: Sun, 24 May 2026 07:01:53 +0200 Subject: [PATCH 05/13] Pending --- crates/analyzer/src/lib.rs | 41 ++++++++------------ crates/analyzer/src/rust_analyzer.rs | 57 +++++++++++++++++++++------- 2 files changed, 59 insertions(+), 39 deletions(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index e7457f8..8fa5bcb 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -7,10 +7,9 @@ use std::fs; use std::path::{Component, PathBuf}; use std::sync::Arc; -use serde_json::Value; +use lsp_types::request::GotoDefinition; 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}; @@ -237,37 +236,29 @@ impl LanguageServer for Backend { .log_message(MessageType::INFO, format!("Found at {position}")) .await; - self.rust_analyzer + let res = self + .rust_analyzer .lock() .await - .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"), - }, + .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"), }, - 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 - ), - ) + .log_message(MessageType::INFO, format!("{:?}", res)) .await; Ok(None) diff --git a/crates/analyzer/src/rust_analyzer.rs b/crates/analyzer/src/rust_analyzer.rs index b26e789..323de0e 100644 --- a/crates/analyzer/src/rust_analyzer.rs +++ b/crates/analyzer/src/rust_analyzer.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, process::Stdio}; +use std::{collections::HashMap, path::PathBuf, process::Stdio, sync::Arc}; use lsp_types::{ ClientCapabilities, InitializeParams, InitializedParams, Url, WorkspaceFolder, @@ -6,15 +6,16 @@ use lsp_types::{ request::{self, Request}, }; use serde::{Deserialize, Serialize}; -use serde_json::json; -use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; +use serde_json::{Value, json}; +use tokio::{ + io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}, + sync::{Mutex, mpsc::Sender}, +}; #[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 id: Option, pub result: Option, pub error: Option, } @@ -29,7 +30,8 @@ pub struct JsonRpcError { #[derive(Debug)] pub struct RustAnalyzer { stdin: tokio::process::ChildStdin, - stdout: BufReader, + stdout: Arc>>, + pending: Arc>>>, id: usize, } @@ -82,11 +84,12 @@ impl RustAnalyzer { .spawn()?; let stdin = child.stdin.take().unwrap(); - let stdout = BufReader::new(child.stdout.take().unwrap()); + let stdout = Arc::new(Mutex::new(BufReader::new(child.stdout.take().unwrap()))); Ok(Self { stdin, stdout, + pending: Arc::new(Mutex::new(HashMap::new())), id: 0, }) } @@ -95,11 +98,11 @@ impl RustAnalyzer { &mut self, params: R::Params, ) -> Result> { - self.send(R::METHOD, params).await?; - self.read().await + let id = self.send(R::METHOD, params).await?; + self.read(id).await } - pub async fn send(&mut self, method: &str, req: T) -> std::io::Result<()> { + pub async fn send(&mut self, method: &str, req: T) -> std::io::Result { let id = { self.id += 1; self.id @@ -114,14 +117,40 @@ impl RustAnalyzer { "params": req, }), ) - .await + .await?; + + Ok(id) } - pub async fn read(&mut self) -> Result> + pub async fn read(&mut self, id: usize) -> Result> where T: for<'de> serde::Deserialize<'de>, { - let raw_string = read_lsp_message(&mut self.stdout).await?; + let (tx, mut rx) = tokio::sync::mpsc::channel(16); + + self.pending.lock().await.insert(id, tx); + + if self.pending.lock().await.len() == 1 { + let stdout = self.stdout.clone(); + let pending = self.pending.clone(); + + tokio::spawn(async move { + let raw_string = read_lsp_message(&mut *stdout.lock().await).await.unwrap(); + + let envelope: JsonRpcResponse = serde_json::from_str(&raw_string).unwrap(); + + pending + .lock() + .await + .get(&envelope.id.unwrap()) + .unwrap() + .send(raw_string) + .await + .unwrap(); + }); + } + + let raw_string = rx.recv().await.expect("Failed to receive"); let envelope: JsonRpcResponse = serde_json::from_str(&raw_string)?; From 64c612fe3c85e7a7757620ecb8a29e70b960e5be Mon Sep 17 00:00:00 2001 From: Selimaj Dev Date: Sun, 24 May 2026 07:03:46 +0200 Subject: [PATCH 06/13] Removed unnecessary package --- Cargo.lock | 16 +--------------- crates/analyzer/Cargo.toml | 1 - crates/analyzer/src/lib.rs | 5 ++--- crates/analyzer/src/rust_analyzer.rs | 10 +++++----- 4 files changed, 8 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52a6d0e..df035ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -423,19 +423,6 @@ 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" @@ -459,7 +446,6 @@ version = "0.0.2" dependencies = [ "cargo_metadata", "dashmap 6.1.0", - "lsp-types 0.95.1", "mist-codegen", "mist-parser", "serde", @@ -881,7 +867,7 @@ dependencies = [ "dashmap 5.5.3", "futures", "httparse", - "lsp-types 0.94.1", + "lsp-types", "memchr", "serde", "serde_json", diff --git a/crates/analyzer/Cargo.toml b/crates/analyzer/Cargo.toml index 1cc9a3f..6274f4e 100644 --- a/crates/analyzer/Cargo.toml +++ b/crates/analyzer/Cargo.toml @@ -19,4 +19,3 @@ 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 8fa5bcb..853ee1e 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -7,10 +7,9 @@ use std::fs; use std::path::{Component, PathBuf}; use std::sync::Arc; -use lsp_types::request::GotoDefinition; use tokio::sync::Mutex; use tower_lsp::jsonrpc::Result; -use tower_lsp::lsp_types::*; +use tower_lsp::lsp_types::{self, *}; use tower_lsp::{Client, LanguageServer, LspService, Server}; use crate::builder::MistDiagnostic; @@ -240,7 +239,7 @@ impl LanguageServer for Backend { .rust_analyzer .lock() .await - .request::(lsp_types::GotoDefinitionParams { + .request::(lsp_types::GotoDefinitionParams { text_document_position_params: lsp_types::TextDocumentPositionParams { position: lsp_types::Position { line: line as u32, diff --git a/crates/analyzer/src/rust_analyzer.rs b/crates/analyzer/src/rust_analyzer.rs index 323de0e..d84d634 100644 --- a/crates/analyzer/src/rust_analyzer.rs +++ b/crates/analyzer/src/rust_analyzer.rs @@ -1,16 +1,16 @@ use std::{collections::HashMap, path::PathBuf, process::Stdio, sync::Arc}; -use lsp_types::{ - ClientCapabilities, InitializeParams, InitializedParams, Url, WorkspaceFolder, - notification::{Initialized, Notification}, - request::{self, Request}, -}; use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; use tokio::{ io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}, sync::{Mutex, mpsc::Sender}, }; +use tower_lsp::lsp_types::{ + self, ClientCapabilities, InitializeParams, InitializedParams, Url, WorkspaceFolder, + notification::{Initialized, Notification}, + request::{self, Request}, +}; #[derive(Deserialize)] pub struct JsonRpcResponse { From 8b0f2ac1b90f96222829705eaa8790a90714f585 Mon Sep 17 00:00:00 2001 From: Selimaj Dev Date: Sun, 24 May 2026 07:08:23 +0200 Subject: [PATCH 07/13] Working req res --- crates/analyzer/src/rust_analyzer.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/crates/analyzer/src/rust_analyzer.rs b/crates/analyzer/src/rust_analyzer.rs index d84d634..b868349 100644 --- a/crates/analyzer/src/rust_analyzer.rs +++ b/crates/analyzer/src/rust_analyzer.rs @@ -135,18 +135,25 @@ impl RustAnalyzer { let pending = self.pending.clone(); tokio::spawn(async move { - let raw_string = read_lsp_message(&mut *stdout.lock().await).await.unwrap(); + loop { + let raw_string = read_lsp_message(&mut *stdout.lock().await).await.unwrap(); - let envelope: JsonRpcResponse = serde_json::from_str(&raw_string).unwrap(); + let envelope: JsonRpcResponse = + serde_json::from_str(&raw_string).unwrap(); - pending - .lock() - .await - .get(&envelope.id.unwrap()) - .unwrap() - .send(raw_string) - .await - .unwrap(); + pending + .lock() + .await + .remove(&envelope.id.unwrap()) + .unwrap() + .send(raw_string) + .await + .unwrap(); + + if pending.lock().await.len() == 0 { + break; + } + } }); } From f181806f650738780811b4be6b09261384772f23 Mon Sep 17 00:00:00 2001 From: Selimaj Dev Date: Sun, 24 May 2026 07:34:04 +0200 Subject: [PATCH 08/13] Working request --- crates/analyzer/src/lib.rs | 27 +++++- crates/analyzer/src/rust_analyzer.rs | 138 ++++++++++++++------------- 2 files changed, 95 insertions(+), 70 deletions(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index 853ee1e..d86707b 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -235,6 +235,26 @@ impl LanguageServer for Backend { .log_message(MessageType::INFO, format!("Found at {position}")) .await; + let uri = + Url::from_file_path(from_mist_to_rust(file_path)).expect("failed to generate rs url"); + + self.rust_analyzer + .lock() + .await + .notify( + "textDocument/didOpen", + DidOpenTextDocumentParams { + text_document: TextDocumentItem { + uri: uri.clone(), + language_id: "rust".into(), + version: 1, + text: output, + }, + }, + ) + .await + .expect("Failed to init req"); + let res = self .rust_analyzer .lock() @@ -245,10 +265,7 @@ impl LanguageServer for Backend { 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"), - }, + text_document: lsp_types::TextDocumentIdentifier { uri }, }, partial_result_params: lsp_types::PartialResultParams::default(), work_done_progress_params: lsp_types::WorkDoneProgressParams::default(), @@ -257,7 +274,7 @@ impl LanguageServer for Backend { .expect("Failed to send to rust"); self.client - .log_message(MessageType::INFO, format!("{:?}", res)) + .log_message(MessageType::INFO, format!("OUTPUT: {:?}", res)) .await; Ok(None) diff --git a/crates/analyzer/src/rust_analyzer.rs b/crates/analyzer/src/rust_analyzer.rs index b868349..26de8c7 100644 --- a/crates/analyzer/src/rust_analyzer.rs +++ b/crates/analyzer/src/rust_analyzer.rs @@ -1,10 +1,10 @@ use std::{collections::HashMap, path::PathBuf, process::Stdio, sync::Arc}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde_json::{Value, json}; use tokio::{ io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}, - sync::{Mutex, mpsc::Sender}, + sync::{Mutex, oneshot}, }; use tower_lsp::lsp_types::{ self, ClientCapabilities, InitializeParams, InitializedParams, Url, WorkspaceFolder, @@ -27,11 +27,12 @@ pub struct JsonRpcError { pub data: Option, } +type PendingMap = Arc>>>; + #[derive(Debug)] pub struct RustAnalyzer { stdin: tokio::process::ChildStdin, - stdout: Arc>>, - pending: Arc>>>, + pending: PendingMap, id: usize, } @@ -84,12 +85,63 @@ impl RustAnalyzer { .spawn()?; let stdin = child.stdin.take().unwrap(); - let stdout = Arc::new(Mutex::new(BufReader::new(child.stdout.take().unwrap()))); + let stdout = child.stdout.take().unwrap(); + + let pending: PendingMap = Arc::new(Mutex::new(HashMap::new())); + let pending_clone = pending.clone(); + + tokio::spawn(async move { + let mut stdout = BufReader::new(stdout); + + loop { + let raw = match read_lsp_message(&mut stdout).await { + Ok(v) => v, + Err(err) => { + eprintln!("LSP read error: {err}"); + break; + } + }; + + let value: Value = match serde_json::from_str(&raw) { + Ok(v) => v, + Err(err) => { + eprintln!("Invalid JSON from rust-analyzer: {err}"); + continue; + } + }; + + if value.get("method").is_some() { + continue; + } + + let id = value.get("id").and_then(|v| v.as_u64()).map(|v| v as usize); + + match id { + Some(id) => { + let tx = pending_clone.lock().await.remove(&id); + + match tx { + Some(tx) => { + let _ = tx.send(value); + } + None => { + eprintln!("({:?}) {}", pending_clone, value); + eprintln!("Received response for unknown request id {id}"); + } + } + } + + None => { + // notification or server request + eprintln!("Received server notification/request: {raw}"); + } + } + } + }); Ok(Self { stdin, - stdout, - pending: Arc::new(Mutex::new(HashMap::new())), + pending, id: 0, }) } @@ -97,81 +149,39 @@ impl RustAnalyzer { pub async fn request( &mut self, params: R::Params, - ) -> Result> { - let id = self.send(R::METHOD, params).await?; - self.read(id).await - } - - pub async fn send(&mut self, method: &str, req: T) -> std::io::Result { + ) -> Result> + where + R::Result: DeserializeOwned, + { let id = { self.id += 1; self.id }; + let (tx, rx) = oneshot::channel(); + + self.pending.lock().await.insert(id, tx); + send_lsp_message( &mut self.stdin, &json!({ "jsonrpc": "2.0", "id": id, - "method": method, - "params": req, + "method": R::METHOD, + "params": params, }), ) .await?; - Ok(id) - } + let value = rx.await?; - pub async fn read(&mut self, id: usize) -> Result> - where - T: for<'de> serde::Deserialize<'de>, - { - let (tx, mut rx) = tokio::sync::mpsc::channel(16); - - self.pending.lock().await.insert(id, tx); - - if self.pending.lock().await.len() == 1 { - let stdout = self.stdout.clone(); - let pending = self.pending.clone(); - - tokio::spawn(async move { - loop { - let raw_string = read_lsp_message(&mut *stdout.lock().await).await.unwrap(); - - let envelope: JsonRpcResponse = - serde_json::from_str(&raw_string).unwrap(); - - pending - .lock() - .await - .remove(&envelope.id.unwrap()) - .unwrap() - .send(raw_string) - .await - .unwrap(); - - if pending.lock().await.len() == 0 { - break; - } - } - }); - } - - let raw_string = rx.recv().await.expect("Failed to receive"); - - let envelope: JsonRpcResponse = serde_json::from_str(&raw_string)?; + let envelope: JsonRpcResponse = serde_json::from_value(value)?; 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() - }) + envelope.result.ok_or_else(|| "missing result".into()) } pub async fn notify(&mut self, method: &str, req: T) -> std::io::Result<()> { @@ -213,9 +223,7 @@ impl RustAnalyzer { ..Default::default() }; - let response = self.request::(init_params).await; - - eprintln!("<- Received: initialize response {response:?}"); + self.request::(init_params).await?; Ok(()) } From c36d07f0dd31b9c0f14eef58116dceffd9113280 Mon Sep 17 00:00:00 2001 From: Selimaj Dev Date: Sun, 24 May 2026 07:56:31 +0200 Subject: [PATCH 09/13] Working goto request --- crates/analyzer/src/lib.rs | 36 ++++------------------------ crates/analyzer/src/rust_analyzer.rs | 2 +- 2 files changed, 5 insertions(+), 33 deletions(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index d86707b..b76d158 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -217,52 +217,26 @@ impl LanguageServer for Backend { source = insert_at_position( &source, - params.text_document_position_params.position.line as usize, + params.text_document_position_params.position.line as usize + 1, params.text_document_position_params.position.character as usize, &inject, ); let output = transpiler::transpile_text(&source).expect("Failed to transpile"); - let position = output - .find(inject) - .expect("Didn't find injection") - .saturating_sub(1); - let (line, character) = find_row_col(&output, inject).unwrap(); - self.client - .log_message(MessageType::INFO, format!("Found at {position}")) - .await; - let uri = Url::from_file_path(from_mist_to_rust(file_path)).expect("failed to generate rs url"); - self.rust_analyzer - .lock() - .await - .notify( - "textDocument/didOpen", - DidOpenTextDocumentParams { - text_document: TextDocumentItem { - uri: uri.clone(), - language_id: "rust".into(), - version: 1, - text: output, - }, - }, - ) - .await - .expect("Failed to init req"); - - let res = self + let rs_res = self .rust_analyzer .lock() .await .request::(lsp_types::GotoDefinitionParams { text_document_position_params: lsp_types::TextDocumentPositionParams { position: lsp_types::Position { - line: line as u32, + line: line as u32 - 1, character: character as u32, }, text_document: lsp_types::TextDocumentIdentifier { uri }, @@ -273,9 +247,7 @@ impl LanguageServer for Backend { .await .expect("Failed to send to rust"); - self.client - .log_message(MessageType::INFO, format!("OUTPUT: {:?}", res)) - .await; + eprintln!("{:?}", rs_res); Ok(None) } diff --git a/crates/analyzer/src/rust_analyzer.rs b/crates/analyzer/src/rust_analyzer.rs index 26de8c7..e2cc377 100644 --- a/crates/analyzer/src/rust_analyzer.rs +++ b/crates/analyzer/src/rust_analyzer.rs @@ -12,7 +12,7 @@ use tower_lsp::lsp_types::{ request::{self, Request}, }; -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] pub struct JsonRpcResponse { pub jsonrpc: String, pub id: Option, From f05849ee453245e12f7a69a1b6c1034d8b76f492 Mon Sep 17 00:00:00 2001 From: Selimaj Dev Date: Sun, 24 May 2026 09:34:02 +0200 Subject: [PATCH 10/13] Working did open and goto --- crates/analyzer/src/lib.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index b76d158..0c2f8b9 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -9,6 +9,7 @@ use std::sync::Arc; use tokio::sync::Mutex; use tower_lsp::jsonrpc::Result; +use tower_lsp::lsp_types::notification::Notification; use tower_lsp::lsp_types::{self, *}; use tower_lsp::{Client, LanguageServer, LspService, Server}; @@ -200,6 +201,27 @@ impl LanguageServer for Backend { *self.previous_diagnostics.lock().await = diagnostics; } + async fn did_open(&self, mut params: DidOpenTextDocumentParams) { + if params.text_document.language_id == "mist" { + params.text_document.language_id = "rust".to_string(); + + params.text_document.text = + transpiler::transpile_text(¶ms.text_document.text).unwrap(); + + params.text_document.uri = Url::from_file_path(from_mist_to_rust( + params.text_document.uri.to_file_path().unwrap(), + )) + .unwrap(); + } + + self.rust_analyzer + .lock() + .await + .notify(notification::DidOpenTextDocument::METHOD, params) + .await + .expect("Failed to send open document"); + } + async fn goto_definition( &self, params: GotoDefinitionParams, @@ -249,7 +271,7 @@ impl LanguageServer for Backend { eprintln!("{:?}", rs_res); - Ok(None) + Ok(rs_res) } } From bad6034ba5927316b43d3eb57a2a1749c655f218 Mon Sep 17 00:00:00 2001 From: Selimaj Dev Date: Sun, 24 May 2026 10:11:06 +0200 Subject: [PATCH 11/13] Working goto with mist --- crates/analyzer/src/lib.rs | 92 +++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 6 deletions(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index 0c2f8b9..b42ddd2 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -2,11 +2,12 @@ pub mod builder; pub mod rust_analyzer; pub mod transpiler; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fs; use std::path::{Component, PathBuf}; use std::sync::Arc; +use mist_parser::rev_mapper; use tokio::sync::Mutex; use tower_lsp::jsonrpc::Result; use tower_lsp::lsp_types::notification::Notification; @@ -22,6 +23,7 @@ struct Backend { workspace_folder: Arc>>, previous_diagnostics: Arc>>>, rust_analyzer: Arc>, + mapping: Arc>>>, } /// Helper function to force percent-encoding on Windows drive colons @@ -45,6 +47,40 @@ fn clean_lsp_url(path: &std::path::Path) -> Option { Url::parse(&url_str).ok() } +impl Backend { + async fn get_mist_location(&self, rs_loc: Location) -> Location { + let rs_path = rs_loc.uri.to_file_path().unwrap(); + + match self.mapping.lock().await.get(&rs_path) { + Some(mapping) => { + let (_, rev_mapper::MistMap(line, character)) = rev_mapper::find_mapping( + mapping, + &rev_mapper::RustMap( + rs_loc.range.start.line as usize, + rs_loc.range.start.character as usize, + ), + ) + .expect("Failed to map"); + + Location { + uri: Url::from_file_path(from_rust_to_mist(rs_path)).unwrap(), + range: Range { + start: Position { + line: line as u32 - 1, + character: character as u32, + }, + end: Position { + line: line as u32 - 1, + character: character as u32 + 1, + }, + }, + } + } + None => rs_loc, + } + } +} + #[tower_lsp::async_trait] impl LanguageServer for Backend { async fn initialize(&self, params: InitializeParams) -> Result { @@ -208,10 +244,14 @@ impl LanguageServer for Backend { params.text_document.text = transpiler::transpile_text(¶ms.text_document.text).unwrap(); - params.text_document.uri = Url::from_file_path(from_mist_to_rust( - params.text_document.uri.to_file_path().unwrap(), - )) - .unwrap(); + let rust_path = from_mist_to_rust(params.text_document.uri.to_file_path().unwrap()); + + params.text_document.uri = Url::from_file_path(&rust_path).unwrap(); + + self.mapping.lock().await.insert( + rust_path, + rev_mapper::get_mapping(¶ms.text_document.text), + ); } self.rust_analyzer @@ -271,7 +311,18 @@ impl LanguageServer for Backend { eprintln!("{:?}", rs_res); - Ok(rs_res) + Ok(match rs_res { + Some(GotoDefinitionResponse::Array(mut arr)) => { + for rs_loc in &mut arr { + let a = self.get_mist_location(rs_loc.clone()).await; + + *rs_loc = a; + } + + Some(GotoDefinitionResponse::Array(arr)) + } + _ => rs_res, + }) } } @@ -284,6 +335,7 @@ pub async fn start() { client, workspace_folder: Arc::new(Mutex::new(None)), previous_diagnostics: Arc::new(Mutex::new(HashMap::new())), + mapping: Arc::new(Mutex::new(HashMap::new())), rust_analyzer: Arc::new(Mutex::new( RustAnalyzer::new().expect("Failed to create rust analyzer"), )), @@ -305,6 +357,34 @@ pub fn from_mist_to_rust(mut path: PathBuf) -> PathBuf { } } +pub fn from_rust_to_mist(mut path: PathBuf) -> PathBuf { + // reverse extension + path.set_extension("mist"); + + let comps: Vec = path.components().collect(); + + // pattern we originally inserted: ".mist/lsp" + let pattern: Vec = std::path::Path::new(".mist/lsp").components().collect(); + + // find the last occurrence of the pattern + if let Some(pos) = comps + .windows(pattern.len()) + .rposition(|window| window == pattern.as_slice()) + { + let mut new_comps = comps.clone(); + + // replace the matched range with "src" + new_comps.splice( + pos..pos + pattern.len(), + std::iter::once(Component::Normal(std::ffi::OsStr::new("src"))), + ); + + new_comps.iter().collect() + } else { + path + } +} + fn insert_at_position(s: &str, line: usize, col: usize, insert: &str) -> String { let mut lines: Vec = s.lines().map(|l| l.to_string()).collect(); From 5bfccb0fc4dbcfe60afcfa7a5d05afe25b421aa1 Mon Sep 17 00:00:00 2001 From: Selimaj Dev Date: Sun, 24 May 2026 10:41:28 +0200 Subject: [PATCH 12/13] Improved error handling --- crates/analyzer/src/lib.rs | 167 +++++++++++++++++++++++++++---------- 1 file changed, 122 insertions(+), 45 deletions(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index b42ddd2..28e548d 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -49,31 +49,35 @@ fn clean_lsp_url(path: &std::path::Path) -> Option { impl Backend { async fn get_mist_location(&self, rs_loc: Location) -> Location { - let rs_path = rs_loc.uri.to_file_path().unwrap(); + let rs_path = match rs_loc.uri.to_file_path() { + Ok(path) => path, + Err(_) => return rs_loc, + }; match self.mapping.lock().await.get(&rs_path) { Some(mapping) => { - let (_, rev_mapper::MistMap(line, character)) = rev_mapper::find_mapping( + // FIX: Match the find_mapping operation instead of calling .expect("Failed to map") + match rev_mapper::find_mapping( mapping, &rev_mapper::RustMap( rs_loc.range.start.line as usize, rs_loc.range.start.character as usize, ), - ) - .expect("Failed to map"); - - Location { - uri: Url::from_file_path(from_rust_to_mist(rs_path)).unwrap(), - range: Range { - start: Position { - line: line as u32 - 1, - character: character as u32, - }, - end: Position { - line: line as u32 - 1, - character: character as u32 + 1, + ) { + Some((_, rev_mapper::MistMap(line, character))) => Location { + uri: Url::from_file_path(from_rust_to_mist(rs_path)).unwrap(), + range: Range { + start: Position { + line: line as u32 - 1, + character: character as u32, + }, + end: Position { + line: line as u32 - 1, + character: character as u32 + 1, + }, }, }, + None => rs_loc, } } None => rs_loc, @@ -96,6 +100,12 @@ impl LanguageServer for Backend { }, )); + res.capabilities.completion_provider = Some(CompletionOptions { + resolve_provider: None, + trigger_characters: Some(vec![".".to_string()]), + ..Default::default() + }); + res.capabilities.definition_provider = Some(OneOf::Left(true)); let folder_path = params @@ -241,25 +251,32 @@ impl LanguageServer for Backend { if params.text_document.language_id == "mist" { params.text_document.language_id = "rust".to_string(); - params.text_document.text = - transpiler::transpile_text(¶ms.text_document.text).unwrap(); + match transpiler::transpile_text(¶ms.text_document.text) { + Ok(transpiled_text) => { + params.text_document.text = transpiled_text; + let rust_path = + from_mist_to_rust(params.text_document.uri.to_file_path().unwrap()); + params.text_document.uri = Url::from_file_path(&rust_path).unwrap(); - let rust_path = from_mist_to_rust(params.text_document.uri.to_file_path().unwrap()); - - params.text_document.uri = Url::from_file_path(&rust_path).unwrap(); - - self.mapping.lock().await.insert( - rust_path, - rev_mapper::get_mapping(¶ms.text_document.text), - ); + self.mapping.lock().await.insert( + rust_path, + rev_mapper::get_mapping(¶ms.text_document.text), + ); + } + Err(e) => { + self.client + .log_message(MessageType::WARNING, format!("MIST-LSP: Syntax invalid during open/change. Parsing stopped: {:?}", e)) + .await; + return; + } + } } - self.rust_analyzer - .lock() - .await - .notify(notification::DidOpenTextDocument::METHOD, params) - .await - .expect("Failed to send open document"); + if let Ok(mut ra) = self.rust_analyzer.try_lock() { + let _ = ra + .notify(notification::DidOpenTextDocument::METHOD, params) + .await; + } } async fn goto_definition( @@ -273,20 +290,28 @@ impl LanguageServer for Backend { .to_file_path() .unwrap(); - let mut source = fs::read_to_string(&file_path).expect("Failed to read source"); + let source = match fs::read_to_string(&file_path) { + Ok(src) => src, + Err(_) => return Ok(None), + }; let inject = "__mist_23"; - - source = insert_at_position( + let injected_source = insert_at_position( &source, params.text_document_position_params.position.line as usize + 1, params.text_document_position_params.position.character as usize, &inject, ); - let output = transpiler::transpile_text(&source).expect("Failed to transpile"); + let output = match transpiler::transpile_text(&injected_source) { + Ok(out) => out, + Err(_) => return Ok(None), + }; - let (line, character) = find_row_col(&output, inject).unwrap(); + let (line, character) = match find_row_col(&output, inject) { + Some(coords) => coords, + None => return Ok(None), + }; let uri = Url::from_file_path(from_mist_to_rust(file_path)).expect("failed to generate rs url"); @@ -309,21 +334,73 @@ impl LanguageServer for Backend { .await .expect("Failed to send to rust"); - eprintln!("{:?}", rs_res); - Ok(match rs_res { - Some(GotoDefinitionResponse::Array(mut arr)) => { - for rs_loc in &mut arr { - let a = self.get_mist_location(rs_loc.clone()).await; - - *rs_loc = a; + Some(GotoDefinitionResponse::Array(arr)) => { + let mut mapped_arr = Vec::new(); + for rs_loc in arr { + let mapped_loc = self.get_mist_location(rs_loc).await; + mapped_arr.push(mapped_loc); } - - Some(GotoDefinitionResponse::Array(arr)) + Some(GotoDefinitionResponse::Array(mapped_arr)) } _ => rs_res, }) } + + async fn completion(&self, _params: CompletionParams) -> Result> { + self.client + .log_message(MessageType::INFO, "getting completion") + .await; + + Ok(Some(CompletionResponse::Array(vec![ + CompletionItem::new_simple("new".to_string(), "The new keyword".to_string()), + ]))) + } + + // async fn completion(&self, mut params: CompletionParams) -> Result> { + // self.client + // .log_message(MessageType::INFO, "COMPLETEING!") + // .await; + + // let file_path = params + // .text_document_position + // .text_document + // .uri + // .to_file_path() + // .unwrap(); + + // let mut source = fs::read_to_string(&file_path).expect("Failed to read source"); + + // let inject = "__mist_23"; + + // source = insert_at_position( + // &source, + // params.text_document_position.position.line as usize + 1, + // params.text_document_position.position.character as usize, + // &inject, + // ); + + // let output = transpiler::transpile_text(&source).expect("Failed to transpile"); + + // let (line, character) = find_row_col(&output, inject).unwrap(); + + // let uri = + // Url::from_file_path(from_mist_to_rust(file_path)).expect("failed to generate rs url"); + + // params.text_document_position.text_document.uri = uri; + // params.text_document_position.position.line = line as u32; + // params.text_document_position.position.character = character as u32; + + // let rs_res = self + // .rust_analyzer + // .lock() + // .await + // .request::(params) + // .await + // .expect("Failed to send to rust"); + + // Ok(rs_res) + // } } #[tokio::main] From 9eec07ca2efd0f1881aa80773bcd7358d7d4dea9 Mon Sep 17 00:00:00 2001 From: Selimaj Dev Date: Sun, 24 May 2026 11:57:17 +0200 Subject: [PATCH 13/13] Improved compiler error message --- src/builder.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/builder.rs b/src/builder.rs index dfe510b..ec74fc6 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -79,7 +79,11 @@ pub fn build(mut args: Vec, root: PathBuf) -> bool { .expect("Unable to find mapping"); let mist_msg = MistDiagnosticMessage { - message: span.label.clone().unwrap_or(msg.message.message.clone()), + message: format!( + "{}: {}", + msg.message.message, + span.label.clone().unwrap_or_default() + ), file_name: if is_root { mist_file } else {