From a63199a41d6818553c54bef92595c547fbc0b10c Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Thu, 25 Jun 2026 22:24:25 +0200 Subject: [PATCH 1/7] Removed fn from keywords --- crates/analyzer/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index 028125a..4936726 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -20,9 +20,9 @@ use crate::transpiler::{TranspileError, transpile_mist, transpile_mist_no_sem}; static MARKER_COUNTER: AtomicU64 = AtomicU64::new(0); -const KEYWORDS: [&'static str; 25] = [ +const KEYWORDS: [&'static str; 24] = [ "if", "else", "for", "while", "match", "return", "break", "continue", "struct", "enum", - "class", "trait", "impl", "pub", "mut", "let", "true", "false", "dyn", "loop", "fn", "unsafe", + "class", "trait", "impl", "pub", "mut", "let", "true", "false", "dyn", "loop", "unsafe", "override", "module", "void", ]; From a1a3ce1a2928eb8be4d9bf1ce799136fa093db81 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Thu, 25 Jun 2026 22:44:00 +0200 Subject: [PATCH 2/7] Removed analyzer debug file --- .gitignore | 3 +++ analyzer.bat | 9 ------- crates/analyzer/src/lib.rs | 52 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 9 deletions(-) delete mode 100644 analyzer.bat diff --git a/.gitignore b/.gitignore index 6351270..fc3d3f3 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ target **/build .mist/ + +analyzer.bat +analyzer.sh diff --git a/analyzer.bat b/analyzer.bat deleted file mode 100644 index 446d793..0000000 --- a/analyzer.bat +++ /dev/null @@ -1,9 +0,0 @@ -@echo off - -cd D:\projects\mist\mist - -mist build >nul 2>&1 - -cd mist-test - -D:\projects\mist\mist\target\debug\mist-analyzer.exe diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index 4936726..27a0050 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -1004,6 +1004,17 @@ impl LanguageServer for Backend { keyword_completion_items().filter(|item| !existing.contains(&item.label)), ); + if is_at_module_scope(&source, mist_pos.line, mist_pos.character) { + cleaned.push(CompletionItem { + label: "declaration".to_string(), + kind: Some(CompletionItemKind::SNIPPET), + insert_text: Some("$1 $2($3)\n{\n$4\n}".to_string()), + insert_text_format: Some(InsertTextFormat::SNIPPET), + sort_text: Some("1".to_string()), + ..Default::default() + }); + } + for item in &mut cleaned { if item.label.starts_with("__") { item.sort_text = Some("zzzz".to_string()); @@ -1024,6 +1035,17 @@ impl LanguageServer for Backend { keyword_completion_items().filter(|item| !existing.contains(&item.label)), ); + if is_at_module_scope(&source, mist_pos.line, mist_pos.character) { + cleaned.push(CompletionItem { + label: "declaration".to_string(), + kind: Some(CompletionItemKind::SNIPPET), + insert_text: Some("$1 $2($3)\n{\n$4\n}".to_string()), + insert_text_format: Some(InsertTextFormat::SNIPPET), + sort_text: Some("1".to_string()), + ..Default::default() + }); + } + for item in &mut cleaned { if item.label.starts_with("__") { item.sort_text = Some("zzzz".to_string()); @@ -1555,6 +1577,36 @@ fn ensure_implicit_packages_impl(docs: &mut HashMap, src_root: &P } } +/// Check if the cursor is at module scope (top-level, not inside a function +/// body, class body, or any other brace-delimited block). +fn is_at_module_scope(source: &str, line: u32, character: u32) -> bool { + let cursor_offset = match byte_offset_from_lsp(source, line, character) { + Some(offset) => offset, + None => return false, + }; + + let mut depth: i32 = 0; + let mut in_string = false; + + for c in source[..cursor_offset].chars() { + if in_string { + if c == '"' { + in_string = false; + } + continue; + } + + match c { + '{' => depth += 1, + '}' if depth > 0 => depth -= 1, + '"' => in_string = true, + _ => {} + } + } + + depth == 0 +} + fn clean_completion_item(mut item: CompletionItem) -> CompletionItem { item.text_edit = None; item.additional_text_edits = None; From 065dc575a58c70c2def62e88195b6159b8eceaf1 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Thu, 25 Jun 2026 23:00:13 +0200 Subject: [PATCH 3/7] Improved scope checking --- crates/analyzer/src/lib.rs | 79 +++++++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 13 deletions(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index 27a0050..3aec540 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -23,7 +23,7 @@ static MARKER_COUNTER: AtomicU64 = AtomicU64::new(0); const KEYWORDS: [&'static str; 24] = [ "if", "else", "for", "while", "match", "return", "break", "continue", "struct", "enum", "class", "trait", "impl", "pub", "mut", "let", "true", "false", "dyn", "loop", "unsafe", - "override", "module", "void", + "override", "module", "void ", ]; fn keyword_completion_items() -> impl Iterator { @@ -1004,9 +1004,12 @@ impl LanguageServer for Backend { keyword_completion_items().filter(|item| !existing.contains(&item.label)), ); - if is_at_module_scope(&source, mist_pos.line, mist_pos.character) { + if matches!( + current_scope(&source, mist_pos.line, mist_pos.character), + Scope::Module + ) { cleaned.push(CompletionItem { - label: "declaration".to_string(), + label: "function".to_string(), kind: Some(CompletionItemKind::SNIPPET), insert_text: Some("$1 $2($3)\n{\n$4\n}".to_string()), insert_text_format: Some(InsertTextFormat::SNIPPET), @@ -1035,9 +1038,12 @@ impl LanguageServer for Backend { keyword_completion_items().filter(|item| !existing.contains(&item.label)), ); - if is_at_module_scope(&source, mist_pos.line, mist_pos.character) { + if matches!( + current_scope(&source, mist_pos.line, mist_pos.character), + Scope::Module + ) { cleaned.push(CompletionItem { - label: "declaration".to_string(), + label: "function".to_string(), kind: Some(CompletionItemKind::SNIPPET), insert_text: Some("$1 $2($3)\n{\n$4\n}".to_string()), insert_text_format: Some(InsertTextFormat::SNIPPET), @@ -1577,18 +1583,43 @@ fn ensure_implicit_packages_impl(docs: &mut HashMap, src_root: &P } } -/// Check if the cursor is at module scope (top-level, not inside a function -/// body, class body, or any other brace-delimited block). -fn is_at_module_scope(source: &str, line: u32, character: u32) -> bool { +#[derive(Debug, Clone, PartialEq, Eq)] +enum Scope { + Module, + Struct, + Enum, + Class, + Trait, + Impl, + Block, + Unknown, +} + +fn scope_for_line(line: &str) -> Scope { + for token in line.split_whitespace() { + match token { + "struct" => return Scope::Struct, + "enum" => return Scope::Enum, + "class" => return Scope::Class, + "trait" => return Scope::Trait, + "impl" => return Scope::Impl, + _ => {} + } + } + Scope::Block +} + +fn current_scope(source: &str, line: u32, character: u32) -> Scope { let cursor_offset = match byte_offset_from_lsp(source, line, character) { Some(offset) => offset, - None => return false, + None => return Scope::Unknown, }; let mut depth: i32 = 0; let mut in_string = false; + let mut brace_starts: Vec = Vec::new(); - for c in source[..cursor_offset].chars() { + for (i, c) in source[..cursor_offset].char_indices() { if in_string { if c == '"' { in_string = false; @@ -1597,14 +1628,36 @@ fn is_at_module_scope(source: &str, line: u32, character: u32) -> bool { } match c { - '{' => depth += 1, - '}' if depth > 0 => depth -= 1, + '{' => { + depth += 1; + brace_starts.push(i); + } + '}' if depth > 0 => { + depth -= 1; + brace_starts.pop(); + } '"' => in_string = true, _ => {} } } - depth == 0 + if depth == 0 { + return Scope::Module; + } + + let Some(&brace_pos) = brace_starts.last() else { + return Scope::Block; + }; + + let before = &source[..brace_pos]; + let before_trimmed = before.trim_end(); + + let line_start = before_trimmed[..before_trimmed.len()] + .rfind('\n') + .map(|pos| pos + 1) + .unwrap_or(0); + + scope_for_line(&before_trimmed[line_start..]) } fn clean_completion_item(mut item: CompletionItem) -> CompletionItem { From c6c33bf83b00b50028405c1e18bb0e013bc08e4a Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Thu, 25 Jun 2026 23:28:11 +0200 Subject: [PATCH 4/7] Improved completions --- crates/analyzer/src/lib.rs | 137 ++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 77 deletions(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index 3aec540..c4eaedb 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -993,72 +993,14 @@ impl LanguageServer for Backend { } match completion_result { - Ok(Some(CompletionResponse::Array(items))) => { - let mut cleaned: Vec = - items.into_iter().map(clean_completion_item).collect(); + Ok(Some(CompletionResponse::Array(mut items))) => { + mist_ify_completions(&source, &mist_pos, &mut items); - let existing: HashSet = - cleaned.iter().map(|item| item.label.clone()).collect(); - - cleaned.extend( - keyword_completion_items().filter(|item| !existing.contains(&item.label)), - ); - - if matches!( - current_scope(&source, mist_pos.line, mist_pos.character), - Scope::Module - ) { - cleaned.push(CompletionItem { - label: "function".to_string(), - kind: Some(CompletionItemKind::SNIPPET), - insert_text: Some("$1 $2($3)\n{\n$4\n}".to_string()), - insert_text_format: Some(InsertTextFormat::SNIPPET), - sort_text: Some("1".to_string()), - ..Default::default() - }); - } - - for item in &mut cleaned { - if item.label.starts_with("__") { - item.sort_text = Some("zzzz".to_string()); - } - } - - Ok(Some(CompletionResponse::Array(cleaned))) + Ok(Some(CompletionResponse::Array(items))) } Ok(Some(CompletionResponse::List(mut list))) => { - let mut cleaned: Vec = - list.items.into_iter().map(clean_completion_item).collect(); - - let existing: HashSet = - cleaned.iter().map(|item| item.label.clone()).collect(); - - cleaned.extend( - keyword_completion_items().filter(|item| !existing.contains(&item.label)), - ); - - if matches!( - current_scope(&source, mist_pos.line, mist_pos.character), - Scope::Module - ) { - cleaned.push(CompletionItem { - label: "function".to_string(), - kind: Some(CompletionItemKind::SNIPPET), - insert_text: Some("$1 $2($3)\n{\n$4\n}".to_string()), - insert_text_format: Some(InsertTextFormat::SNIPPET), - sort_text: Some("1".to_string()), - ..Default::default() - }); - } - - for item in &mut cleaned { - if item.label.starts_with("__") { - item.sort_text = Some("zzzz".to_string()); - } - } - - list.items = cleaned; + mist_ify_completions(&source, &mist_pos, &mut list.items); Ok(Some(CompletionResponse::List(list))) } @@ -1315,6 +1257,36 @@ 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))); + + for item in items.iter_mut() { + item.text_edit = None; + item.additional_text_edits = None; + item.command = None; + + if item.label.starts_with("__") { + item.sort_text = Some("zzzz".to_string()); + } + } + + if matches!( + current_scope(&source, pos.line, pos.character), + Scope::Module | Scope::Class | Scope::Impl | Scope::Trait + ) { + items.push(CompletionItem { + label: "function".to_string(), + kind: Some(CompletionItemKind::SNIPPET), + insert_text: Some("$1 $2($3)\n{\n$4\n}".to_string()), + insert_text_format: Some(InsertTextFormat::SNIPPET), + sort_text: Some("1".to_string()), + ..Default::default() + }); + } +} + async fn handle_ra_notifications( mut rx: tokio::sync::mpsc::UnboundedReceiver, client: Client, @@ -1596,16 +1568,34 @@ enum Scope { } fn scope_for_line(line: &str) -> Scope { - for token in line.split_whitespace() { - match token { - "struct" => return Scope::Struct, - "enum" => return Scope::Enum, - "class" => return Scope::Class, - "trait" => return Scope::Trait, - "impl" => return Scope::Impl, - _ => {} + let mut rest = line.trim_start(); + + loop { + for (kw, scope) in [ + ("struct", Scope::Struct), + ("enum", Scope::Enum), + ("class", Scope::Class), + ("trait", Scope::Trait), + ("impl", Scope::Impl), + ] { + if let Some(after) = rest.strip_prefix(kw) { + if after.is_empty() || !after.starts_with(|c: char| c.is_alphanumeric() || c == '_') + { + return scope; + } + } + } + + let Some(idx) = rest.find(char::is_whitespace) else { + break; + }; + + rest = rest[idx..].trim_start(); + if rest.is_empty() { + break; } } + Scope::Block } @@ -1660,13 +1650,6 @@ fn current_scope(source: &str, line: u32, character: u32) -> Scope { scope_for_line(&before_trimmed[line_start..]) } -fn clean_completion_item(mut item: CompletionItem) -> CompletionItem { - item.text_edit = None; - item.additional_text_edits = None; - item.command = None; - item -} - fn collect_mist_files(dir: &PathBuf, out: &mut Vec) { let Ok(entries) = std::fs::read_dir(dir) else { return; From c47beae67d4f3dcd951f058ac4030a3c86964482 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Thu, 25 Jun 2026 23:49:05 +0200 Subject: [PATCH 5/7] Contextual snippets (massive DX improvement) --- crates/analyzer/src/lib.rs | 282 ++++++++++++++++++++++++++++++++++++- 1 file changed, 278 insertions(+), 4 deletions(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index c4eaedb..4a1a10e 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -31,6 +31,7 @@ fn keyword_completion_items() -> impl Iterator { label: kw.to_string(), kind: Some(CompletionItemKind::KEYWORD), insert_text: Some(kw.to_string()), + sort_text: Some("2".to_string()+kw), ..Default::default() }) } @@ -1272,18 +1273,291 @@ fn mist_ify_completions(source: &str, pos: &Position, items: &mut Vec Date: Fri, 26 Jun 2026 00:05:25 +0200 Subject: [PATCH 6/7] Fix a few snippets --- crates/analyzer/src/lib.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index 4a1a10e..c6a0c4d 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -31,7 +31,7 @@ fn keyword_completion_items() -> impl Iterator { label: kw.to_string(), kind: Some(CompletionItemKind::KEYWORD), insert_text: Some(kw.to_string()), - sort_text: Some("2".to_string()+kw), + sort_text: Some("2".to_string() + kw), ..Default::default() }) } @@ -1356,9 +1356,19 @@ fn mist_ify_completions(source: &str, pos: &Position, items: &mut Vec Date: Fri, 26 Jun 2026 00:07:09 +0200 Subject: [PATCH 7/7] Improved keywords --- crates/analyzer/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/analyzer/src/lib.rs b/crates/analyzer/src/lib.rs index c6a0c4d..4e95b1f 100644 --- a/crates/analyzer/src/lib.rs +++ b/crates/analyzer/src/lib.rs @@ -20,10 +20,10 @@ use crate::transpiler::{TranspileError, transpile_mist, transpile_mist_no_sem}; static MARKER_COUNTER: AtomicU64 = AtomicU64::new(0); -const KEYWORDS: [&'static str; 24] = [ - "if", "else", "for", "while", "match", "return", "break", "continue", "struct", "enum", - "class", "trait", "impl", "pub", "mut", "let", "true", "false", "dyn", "loop", "unsafe", - "override", "module", "void ", +const KEYWORDS: [&'static str; 22] = [ + "if", "else", "for", "while", "match", "return", "break", "continue ", "struct ", "enum ", + "class ", "trait ", "impl ", "pub ", "mut ", "let ", "dyn ", "loop", "unsafe", + "override", "module ", "void ", ]; fn keyword_completion_items() -> impl Iterator {