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:
parent
6f62a0f2b1
commit
9d7e613cfa
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4473,6 +4473,7 @@ dependencies = [
|
||||
"config",
|
||||
"lazy_static",
|
||||
"luahelper",
|
||||
"ordered-float",
|
||||
"wezterm-dynamic",
|
||||
]
|
||||
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
```
|
||||
|
||||
|
@ -11,3 +11,4 @@ config = { path = "../../config" }
|
||||
wezterm-dynamic = { path = "../../wezterm-dynamic" }
|
||||
lazy_static = "1.4"
|
||||
luahelper = { path = "../../luahelper" }
|
||||
ordered-float = "3.0"
|
||||
|
@ -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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user