1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-17 17:57:28 +03:00

restore pretty printing lua values in repl

This time with more correctly working cycle detection
This commit is contained in:
Wez Furlong 2022-05-17 22:51:33 -07:00
parent 55e7d845e9
commit 92eea8e064
5 changed files with 147 additions and 12 deletions

2
Cargo.lock generated
View File

@ -2229,7 +2229,7 @@ dependencies = [
[[package]]
name = "mlua"
version = "0.8.0-beta.4"
source = "git+https://github.com/wez/mlua?branch=table_to_pointer#7884481691169044c18a77ec495886db0d02bee9"
source = "git+https://github.com/khvzak/mlua?branch=master#bcf2cbea3798504471d88345327671bf2da7e8e1"
dependencies = [
"bstr 0.2.17",
"cc",

View File

@ -24,4 +24,4 @@ split-debuginfo = "unpacked"
[patch.crates-io]
xcb = {version="1.1", git="https://github.com/wez/rust-xcb", branch="ffi"}
mlua = {version="0.8.0-beta.4", git="https://github.com/wez/mlua", branch="table_to_pointer"}
mlua = {version="0.8.0-beta.4", git="https://github.com/khvzak/mlua", branch="master"}

View File

@ -133,7 +133,7 @@ pub fn make_lua_context(config_file: &Path) -> anyhow::Result<Lua> {
}
},
item @ _ => {
let item = format!("{:?}", item);
let item = format!("{:#?}", ValuePrinter(item));
output.push_str(&item);
}
}

View File

@ -2,7 +2,9 @@
pub use mlua;
use mlua::{ToLua, Value as LuaValue};
use std::cell::RefCell;
use std::collections::{BTreeMap, HashSet};
use std::rc::Rc;
use wezterm_dynamic::{FromDynamic, ToDynamic, Value as DynValue};
/// Implement lua conversion traits for a type.
@ -85,6 +87,13 @@ fn lua_value_to_dynamic_impl(
value: LuaValue,
visited: &mut HashSet<usize>,
) -> mlua::Result<DynValue> {
let ptr = value.to_pointer() as usize;
if visited.contains(&ptr) {
// Skip this one, as we've seen it before.
// Treat it as a Null value.
return Ok(DynValue::Null);
}
visited.insert(ptr);
Ok(match value {
LuaValue::Nil => DynValue::Null,
LuaValue::String(s) => DynValue::String(s.to_str()?.to_string()),
@ -114,14 +123,6 @@ fn lua_value_to_dynamic_impl(
}
LuaValue::Error(e) => return Err(e),
LuaValue::Table(table) => {
let ptr = table.to_pointer() as usize;
if visited.contains(&ptr) {
// Skip this one, as we've seen it before.
// Treat it as a Null value.
return Ok(DynValue::Null);
}
visited.insert(ptr);
if let Ok(true) = table.contains_key(1) {
let mut array = vec![];
for value in table.sequence_values() {
@ -155,3 +156,136 @@ pub struct ValueLua {
pub value: wezterm_dynamic::Value,
}
impl_lua_conversion_dynamic!(ValueLua);
pub struct ValuePrinter<'lua>(pub LuaValue<'lua>);
impl<'lua> std::fmt::Debug for ValuePrinter<'lua> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
let visited = Rc::new(RefCell::new(HashSet::new()));
ValuePrinterHelper {
visited,
value: self.0.clone(),
}
.fmt(fmt)
}
}
struct ValuePrinterHelper<'lua> {
visited: Rc<RefCell<HashSet<usize>>>,
value: LuaValue<'lua>,
}
impl<'lua> PartialEq for ValuePrinterHelper<'lua> {
fn eq(&self, rhs: &Self) -> bool {
self.value.eq(&rhs.value)
}
}
impl<'lua> Eq for ValuePrinterHelper<'lua> {}
impl<'lua> PartialOrd for ValuePrinterHelper<'lua> {
fn partial_cmp(&self, rhs: &Self) -> Option<std::cmp::Ordering> {
let lhs = lua_value_to_dynamic(self.value.clone()).unwrap_or(DynValue::Null);
let rhs = lua_value_to_dynamic(rhs.value.clone()).unwrap_or(DynValue::Null);
lhs.partial_cmp(&rhs)
}
}
impl<'lua> Ord for ValuePrinterHelper<'lua> {
fn cmp(&self, rhs: &Self) -> std::cmp::Ordering {
let lhs = lua_value_to_dynamic(self.value.clone()).unwrap_or(DynValue::Null);
let rhs = lua_value_to_dynamic(rhs.value.clone()).unwrap_or(DynValue::Null);
lhs.cmp(&rhs)
}
}
impl<'lua> ValuePrinterHelper<'lua> {
fn has_cycle(&self, value: &mlua::Value) -> bool {
self.visited
.borrow()
.contains(&(value.to_pointer() as usize))
}
}
impl<'lua> std::fmt::Debug for ValuePrinterHelper<'lua> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
match &self.value {
LuaValue::Nil => fmt.write_str("nil"),
LuaValue::Boolean(b) => fmt.write_str(if *b { "true" } else { "false" }),
LuaValue::Integer(i) => fmt.write_fmt(format_args!("{}", i)),
LuaValue::Number(i) => fmt.write_fmt(format_args!("{}", i)),
LuaValue::String(s) => match s.to_str() {
Ok(s) => fmt.write_fmt(format_args!("{:?}", s)),
Err(_) => fmt.write_fmt(format_args!("{:?}", s.as_bytes())),
},
LuaValue::Table(t) => {
self.visited
.borrow_mut()
.insert(self.value.to_pointer() as usize);
if let Ok(true) = t.contains_key(1) {
// Treat as list
let mut list = fmt.debug_list();
for (idx, value) in t.clone().sequence_values().enumerate() {
match value {
Ok(value) => {
if !self.has_cycle(&value) {
list.entry(&Self {
visited: Rc::clone(&self.visited),
value,
});
} else {
log::warn!("Ignoring value at ordinal position {} which has cyclical reference", idx);
}
}
Err(err) => {
list.entry(&err);
}
}
}
list.finish()?;
drop(list);
Ok(())
} else {
// Treat as map; put it into a BTreeMap so that we have a stable
// order for our tests.
let mut map = BTreeMap::new();
for pair in t.clone().pairs::<LuaValue, LuaValue>() {
match pair {
Ok(pair) => {
if !self.has_cycle(&pair.1) {
map.insert(
Self {
visited: Rc::clone(&self.visited),
value: pair.0,
},
Self {
visited: Rc::clone(&self.visited),
value: pair.1,
},
);
} else {
log::warn!(
"Ignoring field {:?} which has cyclical reference",
Self {
visited: Rc::clone(&self.visited),
value: pair.0
}
);
}
}
Err(err) => {
log::error!("error while retrieving map entry: {}", err);
break;
}
}
}
fmt.debug_map().entries(&map).finish()
}
}
LuaValue::UserData(_) | LuaValue::LightUserData(_) => fmt.write_str("userdata"),
LuaValue::Thread(_) => fmt.write_str("thread"),
LuaValue::Function(_) => fmt.write_str("function"),
LuaValue::Error(e) => fmt.write_fmt(format_args!("error {}", e)),
}
}
}

View File

@ -1,6 +1,7 @@
use crate::scripting::guiwin::GuiWin;
use chrono::prelude::*;
use log::Level;
use luahelper::ValuePrinter;
use mlua::Value;
use mux::termwiztermtab::TermWizTerminal;
use termwiz::cell::{AttributeChange, CellAttributes, Intensity};
@ -133,7 +134,7 @@ pub fn show_debug_overlay(mut term: TermWizTerminal, gui_win: GuiWin) -> anyhow:
let chunk = host.lua.load(&expr);
match smol::block_on(chunk.eval_async::<Value>()) {
Ok(result) => {
let value = luahelper::lua_value_to_dynamic(result);
let value = ValuePrinter(result);
let text = format!("{:#?}", value);
term.render(&[Change::Text(format!("{}\r\n", text.replace("\n", "\r\n")))])?;
}