manen

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

commit c4f012b2dcd46d330c65e5552a857098c14af29a
parent 87523cfad6d1e110f49536892e1a742060bfb8f3
Author: Sylvia Ivory <git@sivory.net>
Date:   Mon, 23 Jun 2025 16:46:17 -0700

Allow cancelling Lua execution

Diffstat:
MCargo.lock | 86++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
MCargo.toml | 3++-
Msrc/editor.rs | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/format.rs | 1+
Msrc/main.rs | 23+++++++++++++----------
5 files changed, 163 insertions(+), 18 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -144,6 +144,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] name = "cc" version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -337,6 +343,31 @@ dependencies = [ ] [[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -444,7 +475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.2", ] [[package]] @@ -515,6 +546,7 @@ dependencies = [ "mlua", "nu-ansi-term", "reedline", + "tokio", "tree-sitter-highlight", "tree-sitter-lua", ] @@ -555,6 +587,7 @@ dependencies = [ "anyhow", "bstr", "either", + "futures-util", "libloading", "mlua-sys", "num-traits", @@ -652,6 +685,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -868,12 +907,28 @@ dependencies = [ ] [[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] name = "streaming-iterator" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -954,6 +1009,35 @@ dependencies = [ ] [[package]] +name = "tokio" +version = "1.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -22,8 +22,9 @@ clap = { version = "4.5.40", features = ["derive"] } color-eyre = "0.6.5" comfy-table = "7.1.4" lazy_static = "1.5.0" -mlua = { version = "0.10.5", features = ["anyhow", "send"] } +mlua = { version = "0.10.5", features = ["anyhow", "send", "async"] } nu-ansi-term = "0.50.1" reedline = "0.40.0" +tokio = { version = "1.45.1", features = ["full"] } tree-sitter-highlight = "0.25.6" tree-sitter-lua = "0.2.0" diff --git a/src/editor.rs b/src/editor.rs @@ -1,8 +1,17 @@ -use mlua::prelude::*; +use std::{ + process, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, +}; + +use mlua::{HookTriggers, prelude::*}; use reedline::{ DefaultPrompt, DefaultPromptSegment, EditCommand, Emacs, KeyCode, KeyModifiers, Reedline, ReedlineEvent, Signal, default_emacs_keybindings, }; +use tokio::{signal, task}; use crate::{ format::TableFormat, highlight::LuaHighlighter, inspect::display_basic, validator::LuaValidator, @@ -14,6 +23,7 @@ pub struct Editor { lua: Lua, table_format: TableFormat, + cancel_lua: Arc<AtomicBool>, } impl Editor { @@ -21,6 +31,19 @@ impl Editor { 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(HookTriggers::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 prompt = DefaultPrompt::new( DefaultPromptSegment::Basic(version), DefaultPromptSegment::Empty, @@ -45,10 +68,31 @@ impl Editor { lua, table_format: TableFormat::ComfyTable(true), + cancel_lua, }) } - pub fn run(mut self) { + fn register_ctrl_c(&self, is_running_lua: Arc<AtomicBool>) { + let inner_cancel = self.cancel_lua.clone(); + + tokio::spawn(async move { + loop { + signal::ctrl_c().await.unwrap(); + + if is_running_lua.load(Ordering::Relaxed) { + inner_cancel.store(true, Ordering::Relaxed); + } else { + process::exit(0) + } + } + }); + } + + pub async fn run(mut self) { + let is_running_lua = Arc::new(AtomicBool::new(false)); + + self.register_ctrl_c(is_running_lua.clone()); + loop { let signal = self.editor.read_line(&self.prompt); @@ -62,9 +106,21 @@ impl Editor { continue; } - if let Err(e) = self.eval(&line) { + is_running_lua.store(true, Ordering::Relaxed); + + let line = line.clone(); + let lua = self.lua.clone(); + let table_format = self.table_format; + + if let Err(e) = + task::spawn_blocking(move || Self::eval(lua, table_format, &line)) + .await + .unwrap() + { eprintln!("{e}") } + + is_running_lua.store(false, Ordering::Relaxed); } // TODO; this should cancel the current Lua execution if possible Ok(Signal::CtrlC) | Ok(Signal::CtrlD) => break, @@ -111,11 +167,11 @@ impl Editor { Ok(()) } - fn eval(&mut self, line: &str) -> LuaResult<()> { - let value: LuaValue = self.lua.load(line).set_name("=stdin").eval()?; + fn eval(lua: Lua, table_format: TableFormat, line: &str) -> LuaResult<()> { + let value: LuaValue = lua.load(line).set_name("=stdin").eval()?; let stringify = match value { - LuaValue::Table(tbl) => self.table_format.format(&tbl, true)?, + LuaValue::Table(tbl) => table_format.format(&tbl, true)?, value => display_basic(&value, true), }; diff --git a/src/format.rs b/src/format.rs @@ -6,6 +6,7 @@ use nu_ansi_term::Color; use crate::inspect::{display_basic, display_table, is_short_printable, print_array}; +#[derive(Clone, Copy)] pub enum TableFormat { ComfyTable(bool), Inspect, diff --git a/src/main.rs b/src/main.rs @@ -1,6 +1,4 @@ use std::{ - fs, - io::{self, Read}, path::{Path, PathBuf}, process, }; @@ -10,6 +8,10 @@ use editor::Editor; use highlight::LuaHighlighter; use mlua::prelude::*; use reedline::Highlighter; +use tokio::{ + fs, + io::{AsyncReadExt, stdin}, +}; use crate::{format::comfy_table, inspect::inspect}; @@ -33,13 +35,13 @@ enum Command { Repl, /// Run a Lua file Run { - /// Path to Lua file - path: PathBuf + /// Path to Lua file + path: PathBuf, }, /// Highlight a Lua file Highlight { /// Path to Lua file (default: stdin) - path: Option<PathBuf> + path: Option<PathBuf>, }, } @@ -84,22 +86,23 @@ fn eval_lua(file: String, path: &Path) -> LuaResult<()> { } } -fn main() -> color_eyre::Result<()> { +#[tokio::main] +async fn main() -> color_eyre::Result<()> { color_eyre::install()?; let cli = Cli::parse(); match &cli.command { - None | Some(Command::Repl) => Editor::new()?.run(), + None | Some(Command::Repl) => Editor::new()?.run().await, Some(Command::Run { path }) => { - eval_lua(fs::read_to_string(path)?, path)?; + eval_lua(fs::read_to_string(path).await?, path)?; } Some(Command::Highlight { path }) => { let file = if let Some(path) = path { - fs::read_to_string(path)? + fs::read_to_string(path).await? } else { let mut buffer = String::new(); - io::stdin().read_to_string(&mut buffer)?; + stdin().read_to_string(&mut buffer).await?; buffer };