manen

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

commit 844350bac1840dd4a7eb575ced064e8eec2ee55b
parent 540845230bff53d867b8dce329c67b95c36af83f
Author: Sylvia Ivory <git@sivory.net>
Date:   Mon, 23 Jun 2025 23:48:54 -0700

Begin work on autocompletion

Diffstat:
MCargo.lock | 1+
MCargo.toml | 1+
Asrc/completion.rs | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.rs | 4+++-
4 files changed, 143 insertions(+), 1 deletion(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -600,6 +600,7 @@ dependencies = [ "mlua", "nu-ansi-term", "reedline", + "tree-sitter", "tree-sitter-highlight", "tree-sitter-lua", ] diff --git a/Cargo.toml b/Cargo.toml @@ -27,5 +27,6 @@ lazy_static = "1.5.0" mlua = { version = "0.10.5", features = ["anyhow", "send", "async"] } nu-ansi-term = "0.50.1" reedline = "0.40.0" +tree-sitter = "0.25.6" tree-sitter-highlight = "0.25.6" tree-sitter-lua = "0.2.0" diff --git a/src/completion.rs b/src/completion.rs @@ -0,0 +1,138 @@ +use std::collections::HashSet; + +use mlua::prelude::*; +use tree_sitter::{Parser, Point, Query, QueryCursor, StreamingIterator, Tree}; + +#[derive(Debug)] +struct Scope { + start: Point, + end: Point, + variables: HashSet<String>, +} + +pub struct LuaCompleter { + lua: Lua, + parser: Parser, + tree: Tree, + + locals_query: Query, + text: String, +} + +impl LuaCompleter { + pub fn new(lua: Lua) -> Self { + let mut parser = Parser::new(); + + parser + .set_language(&tree_sitter_lua::LANGUAGE.into()) + .unwrap(); + + let tree = parser.parse("", None).unwrap(); + + let locals_query = Query::new( + &tree_sitter_lua::LANGUAGE.into(), + tree_sitter_lua::LOCALS_QUERY, + ) + .unwrap(); + + Self { + lua, + parser, + tree, + locals_query, + text: String::new(), + } + } + + fn refresh_tree(&mut self, text: &str) { + self.tree = self.parser.parse(text, None).unwrap(); + self.text = text.to_string(); + } + + fn globals(&self) -> Vec<LuaValue> { + self.lua + .globals() + .pairs() + .flatten() + .map(|(k, _): (LuaValue, LuaValue)| k) + .collect() + } + + fn locals(&self, point: Point) -> Vec<String> { + let mut cursor = QueryCursor::new(); + + let matches = cursor.matches( + &self.locals_query, + self.tree.root_node(), + self.text.as_bytes(), + ); + let names = self.locals_query.capture_names(); + + let mut scopes = Vec::new(); + let mut variables = Vec::new(); + + matches.for_each(|m| { + for capture in m.captures { + let name = names[capture.index as usize]; + + match name { + "local.definition" => { + variables.push(( + capture.node.start_position(), + capture.node.utf8_text(self.text.as_bytes()).unwrap(), + )); + } + "local.scope" => { + scopes.push((capture.node.start_position(), capture.node.end_position())); + } + _ => {} + } + } + }); + + 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<_>>(); + + dbg!(&new_scopes); + + Vec::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn locals() { + let mut completer = LuaCompleter::new(Lua::new()); + + completer.refresh_tree( + r#" + local function foo(a, b) + print(a, b) + end + + local function bar(c) + print(c) + end + "#, + ); + + completer.locals(Point { row: 1, column: 0 }); + } +} diff --git a/src/main.rs b/src/main.rs @@ -11,8 +11,10 @@ use highlight::LuaHighlighter; use mlua::prelude::*; use reedline::Highlighter; -use crate::{format::comfy_table, inspect::inspect}; +use format::comfy_table; +use inspect::inspect; +mod completion; mod editor; mod format; mod highlight;