manen

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

commit 190519a21c84c0753cc1ad7913f70751d59ac4d1
parent 38afe4005ef57754ea4204a1d0833b51b8ce06cb
Author: Sylvia Ivory <git@sivory.net>
Date:   Sun, 29 Jun 2025 16:15:21 -0700

Implement LuaExecutor trait

Diffstat:
Msrc/completion.rs | 22+++++++++++++++-------
Msrc/editor.rs | 41++++++++++++++++-------------------------
Msrc/hinter.rs | 4+---
Msrc/inspect.rs | 4++--
Asrc/lua.rs | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.rs | 3++-
6 files changed, 87 insertions(+), 38 deletions(-)

diff --git a/src/completion.rs b/src/completion.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use emmylua_parser::{ LuaAst, LuaAstNode, LuaAstToken, LuaBlock, LuaNameExpr, LuaParser, LuaSyntaxTree, }; @@ -5,7 +7,7 @@ use mlua::prelude::*; use reedline::{Completer, Span, Suggestion}; use rowan::TextRange; -use crate::parse; +use crate::{lua::LuaExecutor, parse}; #[derive(Debug)] struct Variable { @@ -20,7 +22,7 @@ struct Scope { } pub struct LuaCompleter { - lua: Lua, + lua_executor: Arc<dyn LuaExecutor>, tree: LuaSyntaxTree, scopes: Vec<Scope>, @@ -28,9 +30,9 @@ pub struct LuaCompleter { } impl LuaCompleter { - pub fn new(lua: Lua) -> Self { + pub fn new(lua_executor: Arc<dyn LuaExecutor>) -> Self { Self { - lua, + lua_executor, tree: LuaParser::parse("", parse::config()), scopes: Vec::new(), text: String::new(), @@ -44,7 +46,7 @@ impl LuaCompleter { } fn globals(&self) -> Vec<String> { - self.lua + self.lua_executor .globals() .pairs() .flatten() @@ -241,8 +243,14 @@ impl Completer for LuaCompleter { #[cfg(test)] mod tests { + use crate::lua::MluaExecutor; + use super::*; + fn lua_executor() -> Arc<dyn LuaExecutor> { + Arc::new(MluaExecutor::new()) + } + fn line_to_position(line: usize, text: &str) -> u32 { let split = text.split("\n").collect::<Vec<_>>(); split[0..line].join("\n").len() as u32 @@ -250,7 +258,7 @@ mod tests { #[test] fn locals() { - let mut completer = LuaCompleter::new(Lua::new()); + let mut completer = LuaCompleter::new(lua_executor()); let text = r#" local function foo(a, b) @@ -327,7 +335,7 @@ mod tests { #[test] fn upvalues() { - let lua = Lua::new(); + let lua = lua_executor(); lua.globals().set("foobar", "").unwrap(); let mut completer = LuaCompleter::new(lua); diff --git a/src/editor.rs b/src/editor.rs @@ -15,36 +15,26 @@ use reedline::{ }; use crate::{ - completion::LuaCompleter, hinter::LuaHinter, inspect::{display_basic, TableFormat}, - parse::LuaHighlighter, validator::LuaValidator, + completion::LuaCompleter, + hinter::LuaHinter, + inspect::{TableFormat, display_basic}, + lua::{LuaExecutor, MluaExecutor}, + parse::LuaHighlighter, + validator::LuaValidator, }; pub struct Editor { prompt: DefaultPrompt, editor: Reedline, - lua: Lua, + lua_executor: Arc<dyn LuaExecutor>, table_format: TableFormat, - cancel_lua: Arc<AtomicBool>, } impl Editor { pub fn new() -> LuaResult<Self> { - let lua = Lua::new(); - let version: String = lua.globals().get("_VERSION")?; - - let cancel_lua = Arc::new(AtomicBool::new(false)); - - let inner_cancel = cancel_lua.clone(); - lua.set_hook(LuaHookTriggers::EVERY_LINE, move |_lua, _debug| { - if inner_cancel.load(Ordering::Relaxed) { - inner_cancel.store(false, Ordering::Relaxed); - - return Err(LuaError::runtime("cancelled")); - } - - Ok(LuaVmState::Continue) - }); + let mlua = Arc::new(MluaExecutor::new()); + let version: String = mlua.globals().get("_VERSION")?; let prompt = DefaultPrompt::new( DefaultPromptSegment::Basic(version), @@ -70,7 +60,9 @@ impl Editor { let mut editor = Reedline::create() .with_validator(Box::new(LuaValidator::new())) - .with_completer(Box::new(LuaCompleter::new(lua.clone()))) + .with_completer(Box::new(LuaCompleter::new( + mlua.clone() as Arc<dyn LuaExecutor> + ))) .with_highlighter(Box::new(LuaHighlighter)) .with_hinter(Box::new(LuaHinter)) .with_edit_mode(Box::new(Emacs::new(keybindings))) @@ -87,19 +79,18 @@ impl Editor { Ok(Self { prompt, editor, - lua, table_format: TableFormat::ComfyTable(true), - cancel_lua, + lua_executor: mlua, }) } fn register_ctrl_c(&self, is_running_lua: Arc<AtomicBool>) { - let inner_cancel = self.cancel_lua.clone(); + let executor = self.lua_executor.clone(); ctrlc::set_handler(move || { if is_running_lua.load(Ordering::Relaxed) { - inner_cancel.store(true, Ordering::Relaxed); + executor.cancel(); } else { process::exit(0) } @@ -178,7 +169,7 @@ impl Editor { } fn eval(&self, line: &str) -> LuaResult<()> { - let value: LuaValue = self.lua.load(line).set_name("=stdin").eval()?; + let value: LuaValue = self.lua_executor.exec(line)?; let stringify = match value { LuaValue::Table(tbl) => self.table_format.format(&tbl, true)?, diff --git a/src/hinter.rs b/src/hinter.rs @@ -59,9 +59,7 @@ impl Hinter for LuaHinter { let s = format!(" ({})", display_basic(&value, false)); if use_ansi_coloring { - Color::DarkGray - .paint(s) - .to_string() + Color::DarkGray.paint(s).to_string() } else { s } diff --git a/src/inspect.rs b/src/inspect.rs @@ -5,7 +5,7 @@ use std::{ }; use aho_corasick::AhoCorasick; -use comfy_table::{presets::UTF8_FULL_CONDENSED, Table}; +use comfy_table::{Table, presets::UTF8_FULL_CONDENSED}; use lazy_static::lazy_static; use mlua::prelude::*; use nu_ansi_term::{AnsiString, AnsiStrings, Color}; @@ -115,7 +115,7 @@ fn remove_invalid(mut bytes: &[u8]) -> String { for bad_byte in &invalid[..error_len] { // this *might* cause some false positives - buffer.push_str(&format!("\u{FFFD}{:X?}", bad_byte)); + buffer.push_str(&format!("\u{FFFD}{bad_byte:X?}")); } bytes = &invalid[error_len..]; diff --git a/src/lua.rs b/src/lua.rs @@ -0,0 +1,51 @@ +use std::sync::{ + Arc, + atomic::{AtomicBool, Ordering}, +}; + +use mlua::prelude::*; + +pub trait LuaExecutor: Send + Sync { + fn exec(&self, code: &str) -> LuaResult<LuaValue>; + fn globals(&self) -> LuaTable; + fn cancel(&self); +} + +pub struct MluaExecutor { + lua: Lua, + cancelled: Arc<AtomicBool>, +} + +impl MluaExecutor { + pub fn new() -> Self { + let lua = Lua::new(); + let cancelled = Arc::new(AtomicBool::new(false)); + + let inner_cancelled = cancelled.clone(); + lua.set_hook(LuaHookTriggers::EVERY_LINE, move |_lua, _debug| { + if inner_cancelled.load(Ordering::Relaxed) { + inner_cancelled.store(false, Ordering::Relaxed); + + return Err(LuaError::runtime("cancelled")); + } + + Ok(LuaVmState::Continue) + }); + + Self { lua, cancelled } + } +} + +impl LuaExecutor for MluaExecutor { + fn exec(&self, code: &str) -> LuaResult<LuaValue> { + self.lua.load(code).set_name("=repl").eval() + } + + fn globals(&self) -> LuaTable { + self.lua.globals() + } + + fn cancel(&self) { + self.cancelled.store(true, Ordering::Relaxed); + } +} diff --git a/src/main.rs b/src/main.rs @@ -11,13 +11,14 @@ use emmylua_parser::{LuaParser, ParserConfig}; use mlua::prelude::*; use reedline::Highlighter; -use inspect::{inspect, comfy_table}; +use inspect::{comfy_table, inspect}; use parse::LuaHighlighter; mod completion; mod editor; mod hinter; mod inspect; +mod lua; mod parse; mod validator;