mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
refactor(json): use UserData-based OrderedTable for json_encode/decode
This commit is contained in:
parent
cd895d4185
commit
5b0a5c9412
3 changed files with 221 additions and 104 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -3474,6 +3474,7 @@ dependencies = [
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"paste",
|
"paste",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"syntect",
|
"syntect",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ tokio-util = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
twox-hash = { workspace = true }
|
twox-hash = { workspace = true }
|
||||||
unicode-width = { workspace = true }
|
unicode-width = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
yazi-prebuilt = "0.1.0"
|
yazi-prebuilt = "0.1.0"
|
||||||
|
|
||||||
[target."cfg(unix)".dependencies]
|
[target."cfg(unix)".dependencies]
|
||||||
|
|
|
||||||
|
|
@ -1,126 +1,240 @@
|
||||||
use mlua::{Function, IntoLuaMulti, Lua, LuaSerdeExt, MetaMethod, Result, Table, Value};
|
use mlua::{
|
||||||
use yazi_binding::Error;
|
Function, IntoLuaMulti, Lua, LuaSerdeExt, MetaMethod, UserData, UserDataMethods, Value,
|
||||||
|
};
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use serde::Serialize;
|
||||||
|
use serde_json::value::Value as JsonValue;
|
||||||
|
|
||||||
|
use std::cell::{Ref, RefCell, RefMut};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use yazi_binding::Error;
|
||||||
|
|
||||||
use super::Utils;
|
use super::Utils;
|
||||||
use crate::config::OPTS;
|
use crate::config::OPTS;
|
||||||
|
|
||||||
struct OrderedTable<'lua> {
|
#[derive(Clone)]
|
||||||
metatable: Table,
|
enum OrderedTableIndex {
|
||||||
lua: &'lua Lua,
|
Key(String),
|
||||||
|
Index(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> OrderedTable<'lua> {
|
#[derive(Clone)]
|
||||||
const DATA_KEY: &'static str = "__data";
|
struct OrderedTable {
|
||||||
const ORDERED_KEY: &'static str = "__ordered";
|
data: Rc<RefCell<JsonValue>>,
|
||||||
|
path: Vec<OrderedTableIndex>,
|
||||||
|
}
|
||||||
|
|
||||||
fn new(lua: &'lua Lua) -> Result<Self> {
|
impl Serialize for OrderedTable {
|
||||||
let mt = lua.create_table()?;
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
use OrderedTableIndex::*;
|
||||||
|
let current = self.data.borrow();
|
||||||
|
|
||||||
mt.set(
|
let mut value = &*current;
|
||||||
MetaMethod::Index.name(),
|
for index in &self.path {
|
||||||
lua.create_function(|_, (table, key): (Table, Value)| {
|
match index {
|
||||||
let data: Table = table.get(Self::DATA_KEY)?;
|
Key(key) => {
|
||||||
data.get::<Value>(key)
|
value = value
|
||||||
})?,
|
.get(key)
|
||||||
)?;
|
.ok_or_else(|| serde::ser::Error::custom(format!("Key '{}' not found", key)))?;
|
||||||
|
}
|
||||||
|
Index(i) => {
|
||||||
|
value = value
|
||||||
|
.get(*i)
|
||||||
|
.ok_or_else(|| serde::ser::Error::custom(format!("Index {} out of bounds", i)))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mt.set(
|
value.serialize(serializer)
|
||||||
MetaMethod::Pairs.name(),
|
}
|
||||||
lua.create_function(|lua, table: Table| {
|
}
|
||||||
let ordered: Value = table.get(Self::ORDERED_KEY)?;
|
|
||||||
let data: Table = table.get(Self::DATA_KEY)?;
|
|
||||||
|
|
||||||
let idx = Rc::new(RefCell::new(0));
|
impl OrderedTable {
|
||||||
|
fn new(data: Rc<RefCell<JsonValue>>, path: Vec<OrderedTableIndex>) -> Self {
|
||||||
let iter = lua.create_function_mut(move |_, ()| {
|
Self { data, path }
|
||||||
let mut index = idx.borrow_mut();
|
|
||||||
|
|
||||||
*index += 1;
|
|
||||||
|
|
||||||
let key: Value = match &ordered {
|
|
||||||
Value::Table(ordered) => ordered.get(*index)?,
|
|
||||||
_ => {
|
|
||||||
if data.len()? >= *index {
|
|
||||||
Value::Integer(*index)
|
|
||||||
} else {
|
|
||||||
Value::Nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if key == Value::Nil {
|
|
||||||
Ok((Value::Nil, Value::Nil))
|
|
||||||
} else {
|
|
||||||
let val = data.get(key.clone())?;
|
|
||||||
Ok((key, val))
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok((iter, Value::Nil, Value::Nil))
|
|
||||||
})?,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
mt.set(
|
|
||||||
MetaMethod::Len.name(),
|
|
||||||
lua.create_function(|_, table: Table| {
|
|
||||||
let data: Table = table.get(Self::DATA_KEY)?;
|
|
||||||
|
|
||||||
data.len()
|
|
||||||
})?,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(Self { metatable: mt, lua })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap(&self, value: &serde_json::Value) -> Result<Value> {
|
fn get_current(&self) -> Option<Ref<JsonValue>> {
|
||||||
let data_table = self.lua.create_table()?;
|
let root = self.data.borrow();
|
||||||
let ordered_table = self.lua.create_table()?;
|
|
||||||
let wrapper = self.lua.create_table()?;
|
|
||||||
|
|
||||||
wrapper.set_metatable(Some(self.metatable.clone()));
|
let mut tmp = &*root;
|
||||||
|
for comp in &self.path {
|
||||||
match &value {
|
tmp = match comp {
|
||||||
serde_json::Value::Object(obj) => {
|
OrderedTableIndex::Key(k) => tmp.get(k)?,
|
||||||
let mut idx = 1;
|
OrderedTableIndex::Index(i) => tmp.get(*i)?,
|
||||||
|
};
|
||||||
for (k, v) in obj {
|
|
||||||
let key_str = self.lua.create_string(k)?;
|
|
||||||
let val = self.wrap(v)?;
|
|
||||||
|
|
||||||
data_table.set(key_str.clone(), val)?;
|
|
||||||
ordered_table.set(idx, key_str)?;
|
|
||||||
idx += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper.set(Self::DATA_KEY, data_table)?;
|
|
||||||
wrapper.set(Self::ORDERED_KEY, ordered_table)?;
|
|
||||||
|
|
||||||
Ok(Value::Table(wrapper))
|
|
||||||
}
|
|
||||||
serde_json::Value::Array(arr) => {
|
|
||||||
for (i, v) in arr.iter().enumerate() {
|
|
||||||
data_table.set(i + 1, self.wrap(v)?)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper.set(Self::DATA_KEY, data_table)?;
|
|
||||||
wrapper.set(Self::ORDERED_KEY, Value::Nil)?;
|
|
||||||
|
|
||||||
Ok(Value::Table(wrapper))
|
|
||||||
}
|
|
||||||
_ => self.lua.to_value_with(value, OPTS),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some(Ref::map(root, move |root_value| {
|
||||||
|
let mut current = root_value;
|
||||||
|
for comp in &self.path {
|
||||||
|
current = match comp {
|
||||||
|
OrderedTableIndex::Key(k) => current.get(k).unwrap(),
|
||||||
|
OrderedTableIndex::Index(i) => current.get(*i).unwrap(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
current
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_mut(&self) -> Option<RefMut<JsonValue>> {
|
||||||
|
let root = self.data.borrow_mut();
|
||||||
|
|
||||||
|
let mut tmp = &*root;
|
||||||
|
for comp in &self.path {
|
||||||
|
tmp = match comp {
|
||||||
|
OrderedTableIndex::Key(k) => tmp.get(k)?,
|
||||||
|
OrderedTableIndex::Index(i) => tmp.get(*i)?,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(RefMut::map(root, move |root_value| {
|
||||||
|
let mut current = root_value;
|
||||||
|
for comp in &self.path {
|
||||||
|
current = match comp {
|
||||||
|
OrderedTableIndex::Key(k) => current.get_mut(k).unwrap(),
|
||||||
|
OrderedTableIndex::Index(i) => current.get_mut(*i).unwrap(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
current
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserData for OrderedTable {
|
||||||
|
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||||
|
fields.add_meta_field("__ordered", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_meta_method(MetaMethod::Index, |lua, this: &OrderedTable, key: Value| {
|
||||||
|
let new_path = match key {
|
||||||
|
Value::String(s) => OrderedTableIndex::Key(s.to_str()?.to_string()),
|
||||||
|
Value::Integer(i) => OrderedTableIndex::Index((i - 1) as usize),
|
||||||
|
_ => return Ok(Value::Nil),
|
||||||
|
};
|
||||||
|
|
||||||
|
let sub = OrderedTable::new(Rc::clone(&this.data), {
|
||||||
|
let mut p = this.path.clone();
|
||||||
|
p.push(new_path);
|
||||||
|
p
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(v) = sub.clone().get_current() {
|
||||||
|
if matches!(*v, JsonValue::Object(_) | JsonValue::Array(_)) {
|
||||||
|
Ok(Value::UserData(lua.create_userdata(sub)?))
|
||||||
|
} else {
|
||||||
|
lua.to_value_with(&*v, OPTS)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(Value::Nil)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
methods.add_meta_method_mut(MetaMethod::Pairs, |lua, this: &mut OrderedTable, ()| {
|
||||||
|
let curr =
|
||||||
|
this.get_current_mut().ok_or_else(|| mlua::Error::RuntimeError("invalid path".into()))?;
|
||||||
|
|
||||||
|
let keys = match &*curr {
|
||||||
|
JsonValue::Object(obj) => {
|
||||||
|
obj.keys().map(|k| (Value::String(lua.create_string(k).unwrap()))).collect()
|
||||||
|
}
|
||||||
|
JsonValue::Array(arry) => (1..=arry.len()).map(|i| Value::Integer(i as i64)).collect(),
|
||||||
|
_ => vec![],
|
||||||
|
};
|
||||||
|
let path = this.path.clone();
|
||||||
|
let data = Rc::clone(&this.data);
|
||||||
|
let idx = Rc::new(RefCell::new(keys.into_iter()));
|
||||||
|
let iter = lua.create_function_mut(move |lua, ()| {
|
||||||
|
let mut index = idx.borrow_mut();
|
||||||
|
if let Some(key) = index.next() {
|
||||||
|
let new_path = match &key {
|
||||||
|
Value::String(s) => OrderedTableIndex::Key(s.to_str()?.to_string()),
|
||||||
|
Value::Integer(i) => OrderedTableIndex::Index((i - 1) as usize),
|
||||||
|
_ => return Ok((Value::Nil, Value::Nil)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
key.clone(),
|
||||||
|
Value::UserData(lua.create_userdata(OrderedTable::new(Rc::clone(&data), {
|
||||||
|
let mut p = path.clone();
|
||||||
|
p.push(new_path);
|
||||||
|
p
|
||||||
|
}))?),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok((Value::Nil, Value::Nil))
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((iter, Value::Nil, Value::Nil))
|
||||||
|
});
|
||||||
|
|
||||||
|
methods.add_meta_method(MetaMethod::Len, |_, this: &OrderedTable, ()| {
|
||||||
|
Ok(if let Some(data) = this.get_current() {
|
||||||
|
Value::Integer(match &*data {
|
||||||
|
JsonValue::Array(arr) => arr.len() as i64,
|
||||||
|
_ => 0,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Value::Nil
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
methods.add_meta_method_mut(
|
||||||
|
MetaMethod::NewIndex,
|
||||||
|
|_, this: &mut OrderedTable, (key, value): (Value, Value)| {
|
||||||
|
let value = serde_json::to_value(value).map_err(mlua::Error::external)?;
|
||||||
|
let mut curr =
|
||||||
|
this.get_current_mut().ok_or_else(|| mlua::Error::RuntimeError("invalid path".into()))?;
|
||||||
|
|
||||||
|
match &mut *curr {
|
||||||
|
JsonValue::Object(obj) => {
|
||||||
|
let k = key
|
||||||
|
.as_str()
|
||||||
|
.ok_or_else(|| mlua::Error::RuntimeError("object key must be string".into()))?;
|
||||||
|
obj.insert(k.to_string(), value);
|
||||||
|
}
|
||||||
|
JsonValue::Array(arr) => {
|
||||||
|
let i = key
|
||||||
|
.as_integer()
|
||||||
|
.ok_or_else(|| mlua::Error::RuntimeError("array index must be integer".into()))?;
|
||||||
|
let idx = (i - 1) as usize;
|
||||||
|
if idx < arr.len() {
|
||||||
|
arr[idx] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(mlua::Error::RuntimeError("not an object or array".into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Utils {
|
impl Utils {
|
||||||
pub(super) fn json_encode(lua: &Lua) -> mlua::Result<Function> {
|
pub(super) fn json_encode(lua: &Lua) -> mlua::Result<Function> {
|
||||||
lua.create_async_function(|lua, value: Value| async move {
|
lua.create_async_function(|lua, value: Value| async move {
|
||||||
match serde_json::to_string(&value) {
|
let result = match value {
|
||||||
Ok(s) => (s, Value::Nil).into_lua_multi(&lua),
|
Value::UserData(ud) => {
|
||||||
Err(e) => (Value::Nil, Error::Serde(e)).into_lua_multi(&lua),
|
let table = ud
|
||||||
|
.borrow::<OrderedTable>()
|
||||||
|
.map_err(|_| mlua::Error::RuntimeError("Unknown userdata type".into()))?;
|
||||||
|
serde_json::to_string(&*table).map_err(Error::Serde)
|
||||||
|
}
|
||||||
|
_ => serde_json::to_string(&value).map_err(Error::Serde),
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(s) => Ok((s, Value::Nil).into_lua_multi(&lua)?),
|
||||||
|
Err(e) => Ok((Value::Nil, e).into_lua_multi(&lua)?),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -129,11 +243,12 @@ impl Utils {
|
||||||
lua.create_async_function(|lua, (s, ordered): (mlua::String, Option<Value>)| async move {
|
lua.create_async_function(|lua, (s, ordered): (mlua::String, Option<Value>)| async move {
|
||||||
let ordered = ordered.unwrap_or(Value::Boolean(false));
|
let ordered = ordered.unwrap_or(Value::Boolean(false));
|
||||||
|
|
||||||
match serde_json::from_slice::<serde_json::Value>(&s.as_bytes()) {
|
match serde_json::from_slice::<JsonValue>(&s.as_bytes()) {
|
||||||
Ok(v) => (
|
Ok(v) => (
|
||||||
if let Value::Boolean(true) = ordered {
|
if let Value::Boolean(true) = ordered {
|
||||||
let ordered_table = OrderedTable::new(&lua)?;
|
let data = Rc::new(RefCell::new(v));
|
||||||
ordered_table.wrap(&v)?
|
|
||||||
|
Value::UserData(lua.create_userdata(OrderedTable::new(data, vec![]))?)
|
||||||
} else {
|
} else {
|
||||||
lua.to_value_with(&v, OPTS)?
|
lua.to_value_with(&v, OPTS)?
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue