1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-23 15:04:36 +03:00

wezterm.GLOBAL now returns references to stored data

This smoothes out a rough edge and makes things work
more naturally.

refs: https://github.com/wez/wezterm/discussions/2983
This commit is contained in:
Wez Furlong 2023-01-24 18:26:14 -07:00
parent 6f62a0f2b1
commit 9d7e613cfa
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
5 changed files with 413 additions and 19 deletions

1
Cargo.lock generated
View File

@ -4473,6 +4473,7 @@ dependencies = [
"config",
"lazy_static",
"luahelper",
"ordered-float",
"wezterm-dynamic",
]

View File

@ -114,6 +114,8 @@ As features stabilize some brief notes about them will accumulate here.
* Config warnings, such as using deprecated or invalid fields will now cause
the configuration error window to be shown. Previously, only hard errors were
shown, which meant that a number of minor config issues could be overlooked.
* Referencing `wezterm.GLOBAL` now returns references rather than copies, making
it less cumbersome to code read/modify/write with global state
#### Updated
* Bundled harfbuzz updated to version 6.0.0

View File

@ -48,3 +48,30 @@ You may store values with the following types:
Attempting to assign other types will raise an error.
## Note about referencing table values
Accessing `wezterm.GLOBAL` returns a copy of the data that you'd read, making
it cumbersome to perform read/modify/write updates; for example this:
```lua
wezterm.GLOBAL.tab_titles['T0'] = 'Test'
```
will not result in `wezterm.GLOBAL.tab_titles.T0` being set, and you need
to explicitly break it apart into:
```lua
local tab_titles = wezterm.GLOBAL.tab_titles
tab_titles['T0'] = 'Test'
wezterm.GLOBAL.tab_titles = tab_titles
```
*Since: nightly builds only*
You no longer need to split apart read/modify/write and the simple assignment
now works as you would expect:
```lua
wezterm.GLOBAL.tab_titles['T0'] = 'Test'
```

View File

@ -11,3 +11,4 @@ config = { path = "../../config" }
wezterm-dynamic = { path = "../../wezterm-dynamic" }
lazy_static = "1.4"
luahelper = { path = "../../luahelper" }
ordered-float = "3.0"

View File

@ -1,35 +1,398 @@
use config::lua::get_or_create_module;
use config::lua::mlua::{self, Lua, UserData, UserDataMethods};
use std::collections::HashMap;
use std::sync::Mutex;
use wezterm_dynamic::Value;
use config::lua::mlua::{self, Lua, ToLua, UserData, UserDataMethods, Value as LuaValue};
use ordered_float::OrderedFloat;
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashSet};
use std::hash::{Hash, Hasher};
use std::sync::{Arc, Mutex};
lazy_static::lazy_static! {
static ref GLOBALS: Mutex<HashMap<String, Value>> = Mutex::new(HashMap::new());
#[derive(Debug, Clone)]
struct Object {
inner: Arc<Mutex<BTreeMap<String, Value>>>,
}
struct Global {}
impl Ord for Object {
fn cmp(&self, other: &Self) -> Ordering {
let self_ptr = self as *const Self;
let other_ptr = other as *const Self;
self_ptr.cmp(&other_ptr)
}
}
impl UserData for Global {
impl PartialOrd for Object {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Object {
fn eq(&self, other: &Self) -> bool {
let a = self.inner.lock().unwrap();
let b = other.inner.lock().unwrap();
*a == *b
}
}
impl Eq for Object {}
impl Hash for Object {
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner.lock().unwrap().hash(state)
}
}
#[derive(Debug, Clone)]
struct Array {
inner: Arc<Mutex<Vec<Value>>>,
}
impl Ord for Array {
fn cmp(&self, other: &Self) -> Ordering {
let self_ptr = self as *const Self;
let other_ptr = other as *const Self;
self_ptr.cmp(&other_ptr)
}
}
impl PartialOrd for Array {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Array {
fn eq(&self, other: &Self) -> bool {
let a = self.inner.lock().unwrap();
let b = other.inner.lock().unwrap();
*a == *b
}
}
impl Eq for Array {}
impl Hash for Array {
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner.lock().unwrap().hash(state)
}
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
enum Value {
Null,
Bool(bool),
String(String),
Array(Array),
Object(Object),
I64(i64),
F64(OrderedFloat<f64>),
}
fn lua_value_to_gvalue(value: LuaValue) -> mlua::Result<Value> {
let mut visited = HashSet::new();
lua_value_to_gvalue_impl(value, &mut visited)
}
fn lua_value_to_gvalue_impl(value: LuaValue, visited: &mut HashSet<usize>) -> mlua::Result<Value> {
if let LuaValue::Table(_) = &value {
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(Value::Null);
}
visited.insert(ptr);
}
Ok(match value {
LuaValue::Nil => Value::Null,
LuaValue::String(s) => Value::String(s.to_str()?.to_string()),
LuaValue::Boolean(b) => Value::Bool(b),
LuaValue::Integer(i) => Value::I64(i),
LuaValue::Number(i) => Value::F64(i.into()),
// Handle our special Null userdata case and map it to Null
LuaValue::LightUserData(ud) if ud.0.is_null() => Value::Null,
LuaValue::LightUserData(_) => {
return Err(mlua::Error::FromLuaConversionError {
from: "userdata",
to: "Value",
message: None,
})
}
LuaValue::UserData(ud) => match ud.get_metatable() {
Ok(mt) => {
if let Ok(to_dynamic) = mt.get::<mlua::MetaMethod, mlua::Function>(
mlua::MetaMethod::Custom("__wezterm_to_dynamic".to_string()),
) {
match to_dynamic.call(LuaValue::UserData(ud.clone())) {
Ok(value) => {
return lua_value_to_gvalue_impl(value, visited);
}
Err(err) => {
return Err(mlua::Error::FromLuaConversionError {
from: "userdata",
to: "Value",
message: Some(format!(
"error calling __wezterm_to_dynamic: {err:#}"
)),
})
}
}
}
match mt.get::<mlua::MetaMethod, mlua::Function>(mlua::MetaMethod::ToString) {
Ok(to_string) => match to_string.call(LuaValue::UserData(ud.clone())) {
Ok(value) => {
return lua_value_to_gvalue_impl(value, visited);
}
Err(err) => {
return Err(mlua::Error::FromLuaConversionError {
from: "userdata",
to: "Value",
message: Some(format!("error calling tostring: {err:#}")),
})
}
},
Err(err) => {
return Err(mlua::Error::FromLuaConversionError {
from: "userdata",
to: "Value",
message: Some(format!("error getting tostring: {err:#}")),
})
}
}
}
Err(err) => {
return Err(mlua::Error::FromLuaConversionError {
from: "userdata",
to: "Value",
message: Some(format!("error getting metatable: {err:#}")),
})
}
},
LuaValue::Function(_) => {
return Err(mlua::Error::FromLuaConversionError {
from: "function",
to: "Value",
message: None,
})
}
LuaValue::Thread(_) => {
return Err(mlua::Error::FromLuaConversionError {
from: "thread",
to: "Value",
message: None,
})
}
LuaValue::Error(e) => return Err(e),
LuaValue::Table(table) => {
if let Ok(true) = table.contains_key(1) {
let mut array = vec![];
let pairs = table.clone();
for value in table.sequence_values() {
array.push(lua_value_to_gvalue(value?)?);
}
for pair in pairs.pairs::<LuaValue, LuaValue>() {
let (key, _value) = pair?;
match &key {
LuaValue::Integer(n) if *n >= 1 && *n as usize <= array.len() => {
// Ok!
}
_ => {
let type_name = key.type_name();
return Err(mlua::Error::FromLuaConversionError {
from: type_name,
to: "numeric array index",
message: Some(format!(
"Unexpected key {key:?} for array style table"
)),
});
}
}
}
Value::Array(Array {
inner: Arc::new(Mutex::new(array.into())),
})
} else {
let mut obj = BTreeMap::default();
for pair in table.pairs::<String, LuaValue>() {
let (key, value) = pair?;
let lua_type = value.type_name();
let value = lua_value_to_gvalue(value).map_err(|e| {
mlua::Error::FromLuaConversionError {
from: lua_type,
to: "value",
message: Some(format!("while processing {key:?}: {}", e.to_string())),
}
})?;
obj.insert(key, value);
}
Value::Object(Object {
inner: Arc::new(Mutex::new(obj.into())),
})
}
}
})
}
lazy_static::lazy_static! {
static ref GLOBALS: Value = Value::Object(Object{inner:Arc::new(Mutex::new(BTreeMap::new()))});
}
fn gvalue_to_lua<'lua>(lua: &'lua Lua, value: &Value) -> mlua::Result<LuaValue<'lua>> {
match value {
Value::Array(arr) => {
let result = lua.create_table()?;
let arr = arr.inner.lock().unwrap();
for (idx, value) in arr.iter().enumerate() {
result.set(idx + 1, gvalue_to_lua(lua, value)?)?;
}
Ok(LuaValue::Table(result))
}
Value::Object(obj) => {
let result = lua.create_table()?;
let obj = obj.inner.lock().unwrap();
for (key, value) in obj.iter() {
result.set(key.clone(), gvalue_to_lua(lua, value)?)?;
}
Ok(LuaValue::Table(result))
}
Value::Bool(b) => Ok(LuaValue::Boolean(*b)),
Value::Null => Ok(LuaValue::Nil),
Value::String(s) => s.to_string().to_lua(lua),
Value::I64(i) => Ok(LuaValue::Integer(*i)),
Value::F64(n) => n.to_lua(lua),
}
}
impl UserData for Value {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(
mlua::MetaMethod::Custom("__wezterm_to_dynamic".to_string()),
|lua: &Lua, this, _: ()| -> mlua::Result<mlua::Value> { gvalue_to_lua(lua, this) },
);
methods.add_meta_method(
mlua::MetaMethod::Index,
|lua: &Lua, _, key: String| -> mlua::Result<Option<mlua::Value>> {
match GLOBALS.lock().unwrap().get(key.as_str()) {
Some(value) => Ok(Some(
luahelper::dynamic_to_lua_value(lua, value.clone())
.map_err(|e| mlua::Error::external(format!("{:#}", e)))?,
|lua: &Lua, this, key: LuaValue| -> mlua::Result<mlua::Value> {
match this {
Value::Array(arr) => match key {
LuaValue::Integer(i) => {
if i <= 0 {
return Err(mlua::Error::external(format!(
"invalid array index {i}"
)));
}
// Convert lua 1-based indices to 0-based
let i = (i as usize) - 1;
let arr = arr.inner.lock().unwrap();
let value = match arr.get(i) {
None => return Ok(LuaValue::Nil),
Some(v) => v,
};
match value {
Value::Null => Ok(LuaValue::Nil),
Value::Bool(b) => Ok(LuaValue::Boolean(*b)),
Value::String(s) => s.clone().to_lua(lua),
Value::F64(u) => u.to_lua(lua),
Value::I64(u) => u.to_lua(lua),
Value::Array(_) => value.clone().to_lua(lua),
Value::Object(_) => value.clone().to_lua(lua),
}
}
_ => Err(mlua::Error::external(
"can only index arrays using integer values",
)),
},
Value::Object(obj) => match key {
LuaValue::String(s) => match s.to_str() {
Err(e) => Err(mlua::Error::external(format!(
"can only index objects using unicode strings: {e:#}"
))),
Ok(s) => {
let obj = obj.inner.lock().unwrap();
let value = match obj.get(s) {
None => return Ok(LuaValue::Nil),
Some(v) => v,
};
match value {
Value::Null => Ok(LuaValue::Nil),
Value::Bool(b) => Ok(LuaValue::Boolean(*b)),
Value::String(s) => s.clone().to_lua(lua),
Value::F64(u) => u.to_lua(lua),
Value::I64(u) => u.to_lua(lua),
Value::Array(_) => value.clone().to_lua(lua),
Value::Object(_) => value.clone().to_lua(lua),
}
}
},
_ => Err(mlua::Error::external(
"can only index objects using string values",
)),
},
_ => Err(mlua::Error::external(
"can only index array or object values".to_string(),
)),
None => Ok(None),
}
},
);
methods.add_meta_method(
mlua::MetaMethod::NewIndex,
|_, _, (key, value): (String, mlua::Value)| -> mlua::Result<()> {
let value = luahelper::lua_value_to_dynamic(value)?;
GLOBALS.lock().unwrap().insert(key, value);
Ok(())
|_, this, (key, value): (LuaValue, LuaValue)| -> mlua::Result<()> {
match this {
Value::Array(arr) => match key {
LuaValue::Integer(i) => {
if i <= 0 {
return Err(mlua::Error::external(format!(
"invalid array index {i}"
)));
}
// Convert lua 1-based indices to 0-based
let i = (i as usize) - 1;
let mut arr = arr.inner.lock().unwrap();
if i >= arr.len() {
return Err(mlua::Error::external(format!(
"cannot make sparse array by inserting at {i} when len is {}",
arr.len()
)));
}
let value = lua_value_to_gvalue(value)?;
if i == arr.len() - 1 {
arr.push(value);
} else {
arr[i] = value;
}
Ok(())
}
_ => Err(mlua::Error::external(
"can only index arrays using integer values",
)),
},
Value::Object(obj) => match key {
LuaValue::String(s) => match s.to_str() {
Err(e) => Err(mlua::Error::external(format!(
"can only index objects using unicode strings: {e:#}"
))),
Ok(s) => {
let mut obj = obj.inner.lock().unwrap();
let value = lua_value_to_gvalue(value)?;
obj.insert(s.to_string(), value);
Ok(())
}
},
_ => Err(mlua::Error::external(
"can only index objects using string values",
)),
},
_ => Err(mlua::Error::external(
"can only index array or object values".to_string(),
)),
}
},
);
}
@ -37,6 +400,6 @@ impl UserData for Global {
pub fn register(lua: &Lua) -> anyhow::Result<()> {
let wezterm_mod = get_or_create_module(lua, "wezterm")?;
wezterm_mod.set("GLOBAL", Global {})?;
wezterm_mod.set("GLOBAL", GLOBALS.clone())?;
Ok(())
}