From ac94882e5a2a0f16fbe03c90cd062f22c8a39aa1 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 12 Jul 2026 05:15:00 -0400 Subject: [PATCH 1/5] Fix mapping system #115 --- crates/analyzer/src/lib.rs | 10 +++--- crates/codegen/src/lib.rs | 4 +-- crates/mist-api/src/builder.mist | 60 ++++++++++++++++++-------------- crates/parser/src/rev_mapper.rs | 4 +-- 4 files changed, 43 insertions(+), 35 deletions(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index 7602f4c..0fa9f39 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -93,12 +93,12 @@ fn clean_lsp_url(path: &std::path::Path) -> Option { } fn lsp_pos_to_rust_map(pos: &Position) -> RustMap { - RustMap(pos.line as usize + 1, pos.character as usize) + RustMap(pos.line as usize, pos.character as usize) } fn mist_map_to_lsp_pos(map: &MistMap) -> Position { Position { - line: map.0.saturating_sub(1) as u32, + line: map.0 as u32, character: map.1 as u32, } } @@ -391,7 +391,7 @@ impl Backend { // Fallback: the source has a syntax error that prevents transpilation. // Use the last known working Rust content + mapping to inject the // marker at the nearest valid Rust position. - let mist_target = MistMap(line as usize + 1, character as usize); + let mist_target = MistMap(line as usize, character as usize); let mut rust_path = crate::from_mist_to_rust(mist_path.to_path_buf()); if mist_path.file_name().and_then(|n| n.to_str()) == Some("package.mist") { rust_path.set_file_name("mod.rs"); @@ -417,7 +417,7 @@ impl Backend { .map(|(rust, _)| *rust); let (modified_rust, rust_pos) = if let Some(next_rust) = rust_next { - let lsp_line = (next_rust.0 - 1) as u32; + let lsp_line = next_rust.0 as u32; let lsp_col = next_rust.1 as u32; let modified = inject_marker_at(&last_content, lsp_line, lsp_col, &marker)?; let pos = find_marker_position(&modified, &marker)?; @@ -1869,7 +1869,7 @@ async fn handle_ra_notifications( character: if mist_end_pos.line == mist_start_pos.line { mist_end_pos.character.max(mist_start_pos.character + 1) } else { - mist_end_pos.character.max(1) + mist_end_pos.character.max(0) }, }; diff --git a/crates/codegen/src/lib.rs b/crates/codegen/src/lib.rs index d2d3a3b..dc7e142 100644 --- a/crates/codegen/src/lib.rs +++ b/crates/codegen/src/lib.rs @@ -46,7 +46,7 @@ impl RustCodegen { indent: 0, crates: HashMap::new(), mapping: Mapping::new(mist_path), - position: RustMap(1, 0), + position: RustMap(0, 0), } } @@ -186,7 +186,7 @@ impl GenSpanTranslation for Spanned { fn gen_span(&self, cg: &mut RustCodegen) { cg.mapping .map - .insert((cg.position, MistMap(self.line, self.column))); + .insert((cg.position, MistMap(self.line, self.column - 1))); } } diff --git a/crates/mist-api/src/builder.mist b/crates/mist-api/src/builder.mist index 3445058..dae8dab 100644 --- a/crates/mist-api/src/builder.mist +++ b/crates/mist-api/src/builder.mist @@ -58,28 +58,36 @@ pub bool build(Vec mut args, PathBuf root) serde_json::from_str(&map_str).expect("Failed to parse map file") }); let mist_file = map.mist_path.strip_prefix(&root); - let (_, rev_mapper::MistMap(line, column)) = map.find(&rev_mapper::RustMap(span.line_end, span.column_start)).unwrap_or_default(); - let mist_msg = MistDiagnosticMessage { - message: format!("{}: {}", - msg.message.message, - span.label.clone().unwrap_or_default()), - file_name: if let Ok(mist_file) = mist_file { - mist_file.to_string_lossy().to_string() - } else { - map.mist_path.to_string_lossy().to_string() - }, - file_path: map.mist_path.clone(), - line: line, - column: column, - }; - match msg.message.level { - cargo_metadata::diagnostic::DiagnosticLevel::Error => { - diagnostics.push(MistDiagnostic::Error(mist_msg)) + let rust_lookup = rev_mapper::RustMap(span.line_end - 1, span.column_start - 1); + match map.find(&rust_lookup) { + Some((_, rev_mapper::MistMap(line, column))) => { + let mist_msg = MistDiagnosticMessage { + message: format!("{}: {}", + msg.message.message, + span.label.clone().unwrap_or_default()), + file_name: if let Ok(mist_file) = mist_file { + mist_file.to_string_lossy().to_string() + } else { + map.mist_path.to_string_lossy().to_string() + }, + file_path: map.mist_path.clone(), + line: line, + column: column, + }; + match msg.message.level { + cargo_metadata::diagnostic::DiagnosticLevel::Error => { + diagnostics.push(MistDiagnostic::Error(mist_msg)) + } + cargo_metadata::diagnostic::DiagnosticLevel::Warning => { + diagnostics.push(MistDiagnostic::Warning(mist_msg)) + } + _ => { + } + } } - cargo_metadata::diagnostic::DiagnosticLevel::Warning => { - diagnostics.push(MistDiagnostic::Warning(mist_msg)) - } - _ => { + None => { + diagnostics.push(MistDiagnostic::Rust(msg)); + break; } } } @@ -110,8 +118,8 @@ pub void print_diagnostics(Vec& diagnostics) let line = get_line(&mut files, &msg); println!("\n{}:{}:{}\n \x1b[31mError\x1b[0m: {}\n\t{}", msg.file_name, - msg.line, - msg.column, + msg.line + 1, + msg.column + 1, msg.message, line.unwrap_or_default(),) } @@ -119,8 +127,8 @@ pub void print_diagnostics(Vec& diagnostics) let line = get_line(&mut files, &msg); println!("\n{}:{}:{}\n \x1b[33mWarning\x1b[0m: {}\n\t{}", msg.file_name, - msg.line, - msg.column, + msg.line + 1, + msg.column + 1, msg.message, line.unwrap_or_default(),) } @@ -135,5 +143,5 @@ pub Option get_line(HashMap> mut& files, MistDiagno let lines = files.entry(src_path.clone()).or_insert_with(() => { fs::read_to_string(src_path).expect("Unable to read mist file").lines().into_iter().map(String::from).collect() }); - lines.get(msg.line - 1).map((v) => v.trim().to_string()) + lines.get(msg.line).map((v) => v.trim().to_string()) } diff --git a/crates/parser/src/rev_mapper.rs b/crates/parser/src/rev_mapper.rs index 2b75e2f..b15f62a 100644 --- a/crates/parser/src/rev_mapper.rs +++ b/crates/parser/src/rev_mapper.rs @@ -3,12 +3,12 @@ use std::{collections::HashSet, path::PathBuf}; use serde::{Deserialize, Serialize}; #[derive( - Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, + Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, )] pub struct MistMap(pub usize, pub usize); #[derive( - Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, + Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, )] pub struct RustMap(pub usize, pub usize); From 01b7e816ee8c3ea02be7edd34ce7b810768f2716 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 12 Jul 2026 05:26:33 -0400 Subject: [PATCH 2/5] Fix include manifest path #106 --- crates/mist-api/src/transpiler.mist | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/mist-api/src/transpiler.mist b/crates/mist-api/src/transpiler.mist index 6c494b4..ece08cf 100644 --- a/crates/mist-api/src/transpiler.mist +++ b/crates/mist-api/src/transpiler.mist @@ -77,6 +77,7 @@ pub void transpile(Path& root) let elapsed = start.elapsed(); println!("\x1b[32m\nTranspile successful\x1b[0m in \x1b[34m{:.2?}\x1b[0m", elapsed); for incl in &config.include { + let incl = if incl.is_relative() { root.join(incl) } else { incl.clone() }; if ends_with_star(&incl) { let sub_crates = incl.parent().expect("Failed to get parent"); let entries = match fs::read_dir(sub_crates) { @@ -95,7 +96,7 @@ pub void transpile(Path& root) } } } else { - transpile(incl); + transpile(&incl); } } } From 0100abfa53a740c56a3776a58a2a83fccaecdb48 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 12 Jul 2026 05:36:57 -0400 Subject: [PATCH 3/5] Fix analyzer bugs --- crates/analyzer/src/lib.rs | 59 +++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index 0fa9f39..7895bd5 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -604,6 +604,59 @@ impl Backend { None => return, }; + // Prune documents whose source files no longer exist on disk. + // Without this, deleted .mist files linger in the in-memory map + // and compute_mod_decls keeps generating stale pub mod declarations. + // Also clean up stale synthetic package.mist entries and generated + // mod.rs output files for directories with no remaining source children. + { + let mut docs = self.documents.lock().await; + let stale: Vec = docs + .keys() + .filter(|p| !p.exists()) + .cloned() + .collect(); + for path in &stale { + docs.remove(path); + if path.extension().and_then(|e| e.to_str()) == Some("mist") { + let rust_path = mist_to_rust_path(path); + let _ = std::fs::remove_file(&rust_path); + let map_path = rust_path.with_extension("map.json"); + let _ = std::fs::remove_file(&map_path); + } + } + + // Also remove synthetic package.mist entries whose directories + // no longer contain any real .mist source files. + let synthetic: Vec = docs + .keys() + .filter(|p| { + p.file_name().and_then(|n| n.to_str()) == Some("package.mist") + && !p.exists() + }) + .cloned() + .collect(); + for path in &synthetic { + docs.remove(path); + } + + drop(docs); + + // Clean up auxiliary state for deleted files + let mut versions = self.doc_versions.lock().await; + let mut contents = self.last_rust_contents.lock().await; + let mut diags = self.previous_diagnostics.lock().await; + let all_stale: Vec = stale.into_iter().chain(synthetic).collect(); + let stale_set: std::collections::HashSet<_> = all_stale.iter().collect(); + versions.retain(|k, _| !stale_set.contains(k)); + contents.retain(|k, _| !stale_set.contains(k)); + for path in &all_stale { + if let Some(uri) = clean_lsp_url(path) { + diags.remove(&uri); + } + } + } + self.ensure_implicit_packages().await; // Collect sources first to avoid holding locks during transpile @@ -619,7 +672,11 @@ impl Backend { let package_mist = read_mist_package(crate_root); let mod_decls = compute_mod_decls(&docs, &src_root, &package_mist); for (path, decl) in mod_decls { - if !decl.is_empty() { + // Include files that have mod declarations OR that are + // already known package/directory entries in the doc map. + // The latter case covers parents whose children were + // deleted — they need a regenerated (empty) mod.rs. + if !decl.is_empty() || docs.contains_key(&path) { if let Some(source) = docs.get(&path) { result.push((path.clone(), source.to_string(), decl)); } From 72f7b8430dad81293f2ebb88929e45812ff144a5 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 12 Jul 2026 05:43:41 -0400 Subject: [PATCH 4/5] Fix publish command --- src/main.mist | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main.mist b/src/main.mist index 6d17160..ad10b72 100644 --- a/src/main.mist +++ b/src/main.mist @@ -16,10 +16,11 @@ void main() process::exit(1); } match args[1].as_str() { - "run" | "build" | "check" | "test" | "publish" | "bench" | "doc" | "fix" | "clippy" | "r" | "b" | "c" => { + "run" | "build" | "check" | "test" | "bench" | "doc" | "fix" | "clippy" | "r" | "b" | "c" => { let mut root = env::current_dir().expect("Unable to find project root"); modify_args(&mut args, &mut root); transpiler::transpile(&root); + println!(""); if builder::build(args, root) { println!("\x1b[32m\nBuild successful\x1b[0m"); @@ -27,6 +28,22 @@ void main() println!("\x1b[31m\nBuild failed\x1b[0m"); } } + "publish" => { + let mut root = env::current_dir().expect("Unable to find project root"); + modify_args(&mut args, &mut root); + transpiler::transpile(&root); + + let status = std::process::Command::new("cargo") + .args(args) + .status() + .expect("Failed to run cargo"); + + if status.success() { + println!("\x1b[32m\nPublished successfully\x1b[0m"); + } else { + println!("\x1b[31m\nFailed to publish\x1b[0m"); + } + } "transpile" | "t" => { let mut root = env::current_dir().expect("Unable to find project root"); modify_args(&mut args, &mut root); From e1b851911df2d6ed313f6d01a4c01f3a3788d06d Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 12 Jul 2026 06:00:16 -0400 Subject: [PATCH 5/5] Fix completion polution --- crates/analyzer/src/lib.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index 7895bd5..74f90fb 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -1218,6 +1218,11 @@ impl LanguageServer for Backend { return Ok(None); }; + let trigger_char: Option = params + .context + .as_ref() + .and_then(|c| c.trigger_character.clone()); + let comp_params = CompletionParams { text_document_position: TextDocumentPositionParams { text_document: TextDocumentIdentifier { uri: rust_uri }, @@ -1245,13 +1250,13 @@ impl LanguageServer for Backend { match completion_result { Ok(Some(CompletionResponse::Array(mut items))) => { - mist_ify_completions(&source, &mist_pos, &mut items); + mist_ify_completions(&source, &mist_pos, &mut items, trigger_char.as_deref()); Ok(Some(CompletionResponse::Array(items))) } Ok(Some(CompletionResponse::List(mut list))) => { - mist_ify_completions(&source, &mist_pos, &mut list.items); + mist_ify_completions(&source, &mist_pos, &mut list.items, trigger_char.as_deref()); Ok(Some(CompletionResponse::List(list))) } @@ -1546,10 +1551,8 @@ impl LanguageServer for Backend { } } -fn mist_ify_completions(source: &str, pos: &Position, items: &mut Vec) { - let existing: HashSet = items.iter().map(|item| item.label.clone()).collect(); - - items.extend(keyword_completion_items().filter(|item| !existing.contains(&item.label))); +fn mist_ify_completions(source: &str, pos: &Position, items: &mut Vec, trigger_char: Option<&str>) { + let is_scoping_trigger = matches!(trigger_char, Some("." | ":")); for item in items.iter_mut() { item.text_edit = None; @@ -1561,6 +1564,14 @@ fn mist_ify_completions(source: &str, pos: &Position, items: &mut Vec = items.iter().map(|item| item.label.clone()).collect(); + + items.extend(keyword_completion_items().filter(|item| !existing.contains(&item.label))); + let curr_scope = current_scope(&source, pos.line, pos.character); // =========================