commit c4f012b2dcd46d330c65e5552a857098c14af29a
parent 87523cfad6d1e110f49536892e1a742060bfb8f3
Author: Sylvia Ivory <git@sivory.net>
Date: Mon, 23 Jun 2025 16:46:17 -0700
Allow cancelling Lua execution
Diffstat:
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
};