Improved scope checking

This commit is contained in:
2026-06-25 23:00:13 +02:00
parent a1a3ce1a29
commit 065dc575a5
+65 -12
View File
@@ -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<PathBuf, Rope>, 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<usize> = 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 {