manen

Fancy Lua REPL
Log | Files | Refs | README | LICENSE

commit f3b29c1b0f85b44ab148dcd044784f31ddb9ed03
parent 844350bac1840dd4a7eb575ced064e8eec2ee55b
Author: Sylvia Ivory <git@sivory.net>
Date:   Tue, 24 Jun 2025 00:42:27 -0700

Finish local resolver

Diffstat:
Aqueries/locals.scm | 35+++++++++++++++++++++++++++++++++++
Msrc/completion.rs | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
2 files changed, 126 insertions(+), 34 deletions(-)

diff --git a/queries/locals.scm b/queries/locals.scm @@ -0,0 +1,35 @@ +; Scopes + +[ + (chunk) + (do_statement) + (while_statement) + (repeat_statement) + (if_statement) + (for_statement) + (function_definition) + (function_declaration) +] @local.scope + +; Definitions + +(assignment_statement + (variable_list + (identifier) @local.definition)) + +; technically a "local function name() ... end" is +; local name +; name = function() ... end +; so we need to ensure that the function name goes into the parent scope +; to do what we treat it as a special case and hoist it in the code +(function_declaration + name: (identifier) @local.fn_name) + +(for_generic_clause + (variable_list + (identifier) @local.definition)) + +(for_numeric_clause + name: (identifier) @local.definition) + +(parameters (identifier) @local.definition) diff --git a/src/completion.rs b/src/completion.rs @@ -1,13 +1,16 @@ -use std::collections::HashSet; - use mlua::prelude::*; -use tree_sitter::{Parser, Point, Query, QueryCursor, StreamingIterator, Tree}; +use tree_sitter::{Parser, Point, Query, QueryCursor, Range, StreamingIterator, Tree}; + +#[derive(Debug)] +struct Variable { + range: Range, + name: String, +} #[derive(Debug)] struct Scope { - start: Point, - end: Point, - variables: HashSet<String>, + range: Range, + variables: Vec<Variable>, } pub struct LuaCompleter { @@ -16,6 +19,7 @@ pub struct LuaCompleter { tree: Tree, locals_query: Query, + scopes: Vec<Scope>, text: String, } @@ -31,7 +35,7 @@ impl LuaCompleter { let locals_query = Query::new( &tree_sitter_lua::LANGUAGE.into(), - tree_sitter_lua::LOCALS_QUERY, + include_str!("../queries/locals.scm"), ) .unwrap(); @@ -40,6 +44,7 @@ impl LuaCompleter { parser, tree, locals_query, + scopes: Vec::new(), text: String::new(), } } @@ -47,6 +52,7 @@ impl LuaCompleter { fn refresh_tree(&mut self, text: &str) { self.tree = self.parser.parse(text, None).unwrap(); self.text = text.to_string(); + self.scopes = self.resolve_scopes(); } fn globals(&self) -> Vec<LuaValue> { @@ -58,7 +64,7 @@ impl LuaCompleter { .collect() } - fn locals(&self, point: Point) -> Vec<String> { + fn resolve_scopes(&self) -> Vec<Scope> { let mut cursor = QueryCursor::new(); let matches = cursor.matches( @@ -68,48 +74,75 @@ impl LuaCompleter { ); let names = self.locals_query.capture_names(); - let mut scopes = Vec::new(); - let mut variables = Vec::new(); + let mut scopes: Vec<Scope> = Vec::new(); + let mut scope_hierarchy: Vec<usize> = Vec::new(); matches.for_each(|m| { for capture in m.captures { let name = names[capture.index as usize]; + let text = capture + .node + .utf8_text(self.text.as_bytes()) + .unwrap() + .to_string(); + let range = capture.node.range(); match name { "local.definition" => { - variables.push(( - capture.node.start_position(), - capture.node.utf8_text(self.text.as_bytes()).unwrap(), - )); + let last = scope_hierarchy.last().unwrap(); + let last_scope = &mut scopes[*last]; + + last_scope.variables.push(Variable { range, name: text }); + } + "local.fn_name" => { + let len = scope_hierarchy.len(); + let parent = scope_hierarchy + .get(len - 2) + .unwrap_or_else(|| scope_hierarchy.last().unwrap()); + let scope = &mut scopes[*parent]; + + scope.variables.push(Variable { range, name: text }); } "local.scope" => { - scopes.push((capture.node.start_position(), capture.node.end_position())); + let scope = Scope { + range: capture.node.range(), + variables: Vec::new(), + }; + + if let Some(last) = scope_hierarchy.last() { + // outside + let last_scope = &scopes[*last]; + + if scope.range.end_byte > last_scope.range.end_byte { + scope_hierarchy.pop(); + } + } + + scope_hierarchy.push(scopes.len()); + scopes.push(scope); } _ => {} } } }); - let new_scopes = scopes - .into_iter() - .map(|(start, end)| { - let variables = variables - .iter() - .filter(|v| v.0 >= start && v.0 <= end) - .map(|v| v.1.to_string()) - .collect::<HashSet<_>>(); - - Scope { - start, - end, - variables, - } - }) - .collect::<Vec<_>>(); + scopes + } + + fn locals(&self, point: Point) -> Vec<String> { + let mut variables = Vec::new(); - dbg!(&new_scopes); + for scope in self.scopes.iter() { + if point > scope.range.start_point && point < scope.range.end_point { + for var in scope.variables.iter() { + if point > var.range.end_point { + variables.push(var.name.clone()); + } + } + } + } - Vec::new() + variables } } @@ -124,15 +157,39 @@ mod tests { completer.refresh_tree( r#" local function foo(a, b) + -- 2: foo, a, b print(a, b) end + -- 6: foo + local function bar(c) + -- 9: foo, bar, c print(c) end + + -- 13: foo, bar "#, ); - completer.locals(Point { row: 1, column: 0 }); + assert_eq!( + &["foo", "a", "b"].as_slice(), + &completer.locals(Point { row: 2, column: 0 }), + ); + + assert_eq!( + &["foo"].as_slice(), + &completer.locals(Point { row: 6, column: 0 }), + ); + + assert_eq!( + &["foo", "bar", "c"].as_slice(), + &completer.locals(Point { row: 9, column: 0 }), + ); + + assert_eq!( + &["foo", "bar"].as_slice(), + &completer.locals(Point { row: 13, column: 0 }), + ); } }