manen

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

commit f4354cbb9f46e00ee4831657d32095d50758798f
parent 762308025171194a7d1afcc125f95d7d5e12810d
Author: Sylvia Ivory <git@sivory.net>
Date:   Sun, 22 Jun 2025 00:36:22 -0700

Improve string escaping

Diffstat:
MCargo.lock | 1+
MCargo.toml | 1+
Msrc/editor.rs | 2+-
Msrc/format.rs | 41++++++++++++++++++++++++++---------------
Asrc/inspect.rs | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.rs | 1+
6 files changed, 103 insertions(+), 16 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -488,6 +488,7 @@ dependencies = [ name = "manen" version = "0.1.0" dependencies = [ + "aho-corasick", "clap", "color-eyre", "comfy-table", diff --git a/Cargo.toml b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +aho-corasick = "1.1.3" clap = { version = "4.5.40", features = ["derive"] } color-eyre = "0.6.5" comfy-table = "7.1.4" diff --git a/src/editor.rs b/src/editor.rs @@ -117,7 +117,7 @@ impl Editor { let value: LuaValue = self.lua.load(line).set_name("=stdin").eval()?; let stringify = match value { - LuaValue::Table(tbl) => self.table_format.format(&self.lua, &tbl)?, + LuaValue::Table(tbl) => self.table_format.format(&self.lua, &tbl, true)?, value => lua_to_string(&value)?, }; diff --git a/src/format.rs b/src/format.rs @@ -2,6 +2,10 @@ use std::collections::HashMap; use comfy_table::{Table, presets::UTF8_FULL_CONDENSED}; use mlua::prelude::*; +use nu_ansi_term::Color; +use reedline::Highlighter; + +use crate::{highlight::LuaHighlighter, inspect::cleanup_string}; const INSPECT_CODE: &str = include_str!("inspect.lua"); @@ -15,19 +19,9 @@ fn addr_tbl(tbl: &LuaTable) -> String { format!("table@{:?}", tbl.to_pointer()) } -fn convert_string(string: &LuaString) -> String { - let bytes = string - .as_bytes() - .iter() - .flat_map(|b| std::ascii::escape_default(*b)) - .collect::<Vec<_>>(); - - String::from_utf8_lossy(&bytes).to_string() -} - pub fn lua_to_string(value: &LuaValue) -> LuaResult<String> { match value { - LuaValue::String(string) => Ok(convert_string(string)), + LuaValue::String(string) => Ok(cleanup_string(string)), LuaValue::Table(tbl) => Ok(addr_tbl(tbl)), value => value.to_string(), } @@ -114,17 +108,34 @@ fn comfy_table( } impl TableFormat { - pub fn format(&self, lua: &Lua, tbl: &LuaTable) -> LuaResult<String> { + pub fn format(&self, lua: &Lua, tbl: &LuaTable, colorize: bool) -> LuaResult<String> { match self { - TableFormat::Address => Ok(format!("table@{:?}", tbl.to_pointer())), + TableFormat::Address => { + if colorize { + Ok(format!( + "{}{}{}", + Color::LightBlue.paint("table"), + Color::Default.paint("@"), + Color::LightYellow.paint(format!("{:?}", tbl.to_pointer())) + )) + } else { + Ok(format!("table@{:?}", tbl.to_pointer())) + } + } TableFormat::Inspect => { if let Some(inspect) = lua.globals().get::<Option<LuaTable>>("_inspect")? { - inspect.call::<String>(tbl) + let out = inspect.call::<String>(tbl)?; + + if colorize { + Ok(LuaHighlighter::new().highlight(&out, 0).render_simple()) + } else { + Ok(out) + } } else { let inspect: LuaTable = lua.load(INSPECT_CODE).eval()?; lua.globals().set("_inspect", inspect)?; - self.format(lua, tbl) + self.format(lua, tbl, colorize) } } TableFormat::ComfyTable(recursive) => { diff --git a/src/inspect.rs b/src/inspect.rs @@ -0,0 +1,73 @@ +use aho_corasick::AhoCorasick; +use mlua::prelude::*; + +fn escape_control(s: &str) -> String { + let mut escapes = vec![ + String::from("\x07"), + String::from("\x08"), + String::from("\x0C"), + String::from("\n"), + String::from("\r"), + String::from("\t"), + String::from("\x0B"), + String::from("\x7F"), + ]; + + let mut replacements = vec![ + String::from("\\a"), + String::from("\\b"), + String::from("\\f"), + String::from("\\n"), + String::from("\\r"), + String::from("\\t"), + String::from("\\v"), + String::from("\\127"), + ]; + + for i in 0..=31 { + escapes.push(String::from_utf8_lossy(&[i]).to_string()); + replacements.push(format!("\\{i}")); + } + + // TODO; reuse + let ac = AhoCorasick::new(escapes).unwrap(); + + ac.replace_all(s, &replacements).replace("\\\\x", "\\x") +} + +fn remove_invalid(mut bytes: &[u8]) -> String { + let mut buffer = String::new(); + + loop { + match str::from_utf8(bytes) { + Ok(s) => { + buffer.push_str(s); + return buffer; + } + Err(e) => { + let (valid, invalid) = bytes.split_at(e.valid_up_to()); + + if !valid.is_empty() { + // SAFETY: We already know the bytes until this point are valid + buffer.push_str(unsafe { str::from_utf8_unchecked(valid) }) + } + + let error_len = if let Some(len) = e.error_len() { + len + } else { + return buffer; + }; + + for bad_byte in &invalid[..error_len] { + buffer.push_str(&format!("\\x{:X?}", bad_byte)); + } + + bytes = &invalid[error_len..]; + } + } + } +} + +pub fn cleanup_string(lua_str: &LuaString) -> String { + escape_control(&remove_invalid(&lua_str.as_bytes())) +} diff --git a/src/main.rs b/src/main.rs @@ -12,6 +12,7 @@ use reedline::Highlighter; mod editor; mod format; mod highlight; +mod inspect; mod validator; #[derive(Parser)]