refactor: make Lua bindings Send (#4027)

This commit is contained in:
sxyazi 2026-06-09 22:52:43 +08:00
parent f6113d3865
commit 9cfeb67db7
No known key found for this signature in database
56 changed files with 395 additions and 504 deletions

79
Cargo.lock generated
View file

@ -1935,9 +1935,9 @@ dependencies = [
[[package]]
name = "imgref"
version = "1.12.1"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2"
checksum = "89194689a993ab15268672e99e7b0e19da2da3268ac682e8f02d29d4d1434cd7"
[[package]]
name = "indexmap"
@ -3337,9 +3337,8 @@ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]]
name = "ratatui"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc"
version = "0.30.1"
source = "git+https://github.com/yazi-rs/ratatui.git?rev=a1c4922a242861d755163ae85d7e41b85ed277d2#a1c4922a242861d755163ae85d7e41b85ed277d2"
dependencies = [
"instability",
"ratatui-core",
@ -3353,8 +3352,7 @@ dependencies = [
[[package]]
name = "ratatui-core"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d3603f354bba8c595fa47860e60142d7372b7210c27044c6a7d0e1a4336b44"
source = "git+https://github.com/yazi-rs/ratatui.git?rev=a1c4922a242861d755163ae85d7e41b85ed277d2#a1c4922a242861d755163ae85d7e41b85ed277d2"
dependencies = [
"bitflags 2.13.0",
"compact_str",
@ -3366,7 +3364,7 @@ dependencies = [
"lru",
"palette",
"serde",
"strum 0.28.0",
"strum",
"thiserror 2.0.18",
"unicode-segmentation",
"unicode-truncate",
@ -3376,8 +3374,7 @@ dependencies = [
[[package]]
name = "ratatui-crossterm"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2867bedcbd6a690ca4f8672a687b730ec07660c79844517b084311b529980c"
source = "git+https://github.com/yazi-rs/ratatui.git?rev=a1c4922a242861d755163ae85d7e41b85ed277d2#a1c4922a242861d755163ae85d7e41b85ed277d2"
dependencies = [
"cfg-if",
"crossterm 0.28.1",
@ -3389,8 +3386,7 @@ dependencies = [
[[package]]
name = "ratatui-termion"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c16cc35a9d9114e0b2bb4b22018b96ae7f5fe60e2595dc73e622b4e78624835"
source = "git+https://github.com/yazi-rs/ratatui.git?rev=a1c4922a242861d755163ae85d7e41b85ed277d2#a1c4922a242861d755163ae85d7e41b85ed277d2"
dependencies = [
"instability",
"ratatui-core",
@ -3400,8 +3396,7 @@ dependencies = [
[[package]]
name = "ratatui-termwiz"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "386b8ff8f74ed749509391c56d549761a2fcdb408e1f42e467286bcb7dac8967"
source = "git+https://github.com/yazi-rs/ratatui.git?rev=a1c4922a242861d755163ae85d7e41b85ed277d2#a1c4922a242861d755163ae85d7e41b85ed277d2"
dependencies = [
"ratatui-core",
"termwiz",
@ -3409,19 +3404,18 @@ dependencies = [
[[package]]
name = "ratatui-widgets"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db"
version = "0.3.1"
source = "git+https://github.com/yazi-rs/ratatui.git?rev=a1c4922a242861d755163ae85d7e41b85ed277d2#a1c4922a242861d755163ae85d7e41b85ed277d2"
dependencies = [
"bitflags 2.13.0",
"hashbrown 0.16.1",
"hashbrown 0.17.1",
"indoc",
"instability",
"itertools",
"line-clipping",
"ratatui-core",
"serde",
"strum 0.27.2",
"strum",
"time",
"unicode-segmentation",
"unicode-width",
@ -4221,34 +4215,13 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
dependencies = [
"strum_macros 0.27.2",
]
[[package]]
name = "strum"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
dependencies = [
"strum_macros 0.28.0",
]
[[package]]
name = "strum_macros"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.117",
"strum_macros",
]
[[package]]
@ -5131,9 +5104,9 @@ dependencies = [
[[package]]
name = "which"
version = "8.0.2"
version = "8.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81995fafaaaf6ae47a7d0cc83c67caf92aeb7e5331650ae6ff856f7c0c60c459"
checksum = "c789537cf2f7f55be8e6192f92e464174ee55f91af622777f7f1ceb0dbccd03e"
dependencies = [
"libc",
]
@ -5674,7 +5647,7 @@ dependencies = [
"palette",
"quantette",
"ratatui",
"strum 0.28.0",
"strum",
"tokio",
"tracing",
"yazi-config",
@ -5695,8 +5668,10 @@ dependencies = [
"anyhow",
"futures",
"hashbrown 0.17.1",
"inventory",
"libc",
"mlua",
"ordered-float 5.3.0",
"paste",
"ratatui",
"serde_json",
@ -5797,7 +5772,7 @@ dependencies = [
"regex",
"serde",
"serde_with",
"strum 0.28.0",
"strum",
"tokio",
"toml",
"tracing",
@ -5823,7 +5798,7 @@ dependencies = [
"ratatui",
"serde",
"serde_with",
"strum 0.28.0",
"strum",
"syntect",
"tokio",
"tokio-stream",
@ -5856,7 +5831,6 @@ dependencies = [
"indexmap 2.14.0",
"inventory",
"mlua",
"ordered-float 5.3.0",
"parking_lot",
"paste",
"serde",
@ -5966,7 +5940,7 @@ dependencies = [
"regex",
"scopeguard",
"serde",
"strum 0.28.0",
"strum",
"tokio",
"tracing",
"trash",
@ -5999,7 +5973,7 @@ dependencies = [
"ratatui",
"serde",
"serde_with",
"strum 0.28.0",
"strum",
"tokio",
"yazi-binding",
"yazi-boot",
@ -6089,7 +6063,6 @@ dependencies = [
"tracing",
"yazi-binding",
"yazi-config",
"yazi-dds",
"yazi-fs",
"yazi-macro",
"yazi-shared",
@ -6111,7 +6084,7 @@ dependencies = [
"ordered-float 5.3.0",
"parking_lot",
"serde",
"strum 0.28.0",
"strum",
"tokio",
"tracing",
"yazi-binding",
@ -6156,7 +6129,7 @@ dependencies = [
"percent-encoding",
"serde",
"serde_with",
"strum 0.28.0",
"strum",
"thiserror 2.0.18",
"tokio",
"typed-path",
@ -6199,7 +6172,7 @@ dependencies = [
"rustix 1.1.4",
"serde",
"signal-hook 0.4.4",
"strum 0.28.0",
"strum",
"thiserror 2.0.18",
"tokio",
"windows-sys 0.61.2",

View file

@ -61,7 +61,7 @@ parking_lot = "0.12.5"
paste = "1.0.15"
percent-encoding = "2.3.2"
rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] }
ratatui = { version = "=0.30.0", default-features = false, features = [ "layout-cache", "serde", "underline-color", "unstable-rendered-line-info", "unstable-widget-ref" ] }
ratatui = { version = "0.30.1", default-features = false, features = [ "std", "layout-cache", "serde", "underline-color", "unstable-rendered-line-info", "unstable-widget-ref" ] }
regex = "1.12.3"
russh = { version = "0.61.2", default-features = false, features = [ "ring", "rsa" ] }
scopeguard = "1.2.0"
@ -91,3 +91,8 @@ module_inception = "allow"
option_map_unit_fn = "allow"
unit_arg = "allow"
use_self = "warn"
[patch.crates-io]
ratatui = { git = "https://github.com/yazi-rs/ratatui.git", package = "ratatui", rev = "a1c4922a242861d755163ae85d7e41b85ed277d2" }
ratatui-core = { git = "https://github.com/yazi-rs/ratatui.git", package = "ratatui-core", rev = "a1c4922a242861d755163ae85d7e41b85ed277d2" }
ratatui-widgets = { git = "https://github.com/yazi-rs/ratatui.git", package = "ratatui-widgets", rev = "a1c4922a242861d755163ae85d7e41b85ed277d2" }

View file

@ -28,6 +28,9 @@ rustPlatform.buildRustPackage (finalAttrs: {
cargoLock = {
lockFile = "${src}/Cargo.lock";
outputHashes = {
"ratatui-0.30.1" = "sha256-wkEOgwAhW0ObtPDlJmOOoY5qf9c/P79ktN4b43jtcGw=";
};
};
env = {
@ -72,7 +75,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
hash = "sha256-kEVXejDg4ChFoMNBvKlwdFEyUuTcY2VuK9j0PdafKus=";
};
};
meta = {
description = "Blazing fast terminal file manager written in Rust, based on async I/O";

View file

@ -1,6 +1,5 @@
use anyhow::Result;
use yazi_binding::runtime_scope;
use yazi_dds::Sendable;
use yazi_binding::{Sendable, runtime_scope};
use yazi_macro::succ;
use yazi_parser::app::LuaForm;
use yazi_plugin::LUA;

View file

@ -1,7 +1,7 @@
use std::{ops::Deref, ptr};
use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Value};
use yazi_binding::{Range, Style, cached_field};
use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods};
use yazi_binding::{Range, Style};
use yazi_config::THEME;
use yazi_shared::{path::AsPath, url::UrlLike};
@ -12,16 +12,6 @@ pub(super) struct File {
idx: usize,
folder: PtrCell<yazi_core::tab::Folder>,
tab: PtrCell<yazi_core::tab::Tab>,
v_cha: Option<Value>,
v_url: Option<Value>,
v_link_to: Option<Value>,
v_name: Option<Value>,
v_path: Option<Value>,
v_cache: Option<Value>,
v_bare: Option<Value>,
}
impl Deref for File {
@ -45,21 +35,7 @@ impl File {
Ok(match super::FILE_CACHE.borrow_mut().entry(PtrCell(&folder.files[idx])) {
Entry::Occupied(oe) => oe.into_mut().clone(),
Entry::Vacant(ve) => {
let ud = Lives::scoped_userdata(Self {
idx,
folder: folder.into(),
tab: tab.into(),
v_cha: None,
v_url: None,
v_link_to: None,
v_name: None,
v_path: None,
v_cache: None,
v_bare: None,
})?;
let ud = Lives::scoped_userdata(Self { idx, folder: folder.into(), tab: tab.into() })?;
ve.insert(ud.clone());
ud
}
@ -73,7 +49,7 @@ impl File {
impl UserData for File {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
yazi_binding::impl_file_fields!(fields);
cached_field!(fields, bare, |_, me| Ok(yazi_binding::File::new(&**me)));
fields.add_cached_field("bare", |_, me| Ok(yazi_binding::File::new(&**me)));
fields.add_field_method_get("idx", |_, me| Ok(me.idx + 1));
fields.add_field_method_get("is_hovered", |_, me| Ok(me.is_hovered()));

View file

@ -1,7 +1,7 @@
use std::ops::{Deref, Range};
use mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, Value};
use yazi_binding::cached_field;
use mlua::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods};
use yazi_shim::mlua::UserDataFieldsExt;
use super::{File, Filter, Lives, PtrCell};
@ -9,8 +9,6 @@ pub(super) struct Files {
window: Range<usize>,
folder: PtrCell<yazi_core::tab::Folder>,
tab: PtrCell<yazi_core::tab::Tab>,
v_filter: Option<Value>,
}
impl Deref for Files {
@ -25,13 +23,13 @@ impl Files {
folder: &yazi_core::tab::Folder,
tab: &yazi_core::tab::Tab,
) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { window, folder: folder.into(), tab: tab.into(), v_filter: None })
Lives::scoped_userdata(Self { window, folder: folder.into(), tab: tab.into() })
}
}
impl UserData for Files {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, filter, |_, me| me.filter().map(Filter::make).transpose());
fields.add_static_field("filter", |_, me| me.filter().map(Filter::make).transpose());
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {

View file

@ -1,8 +1,9 @@
use std::ops::{Deref, Range};
use mlua::{AnyUserData, UserData, UserDataFields, Value};
use yazi_binding::{FolderStage, Url, cached_field};
use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_binding::{FolderStage, Url};
use yazi_config::LAYOUT;
use yazi_shim::mlua::UserDataFieldsExt;
use super::{File, Files, Lives, PtrCell};
@ -10,12 +11,6 @@ pub(super) struct Folder {
window: Range<usize>,
inner: PtrCell<yazi_core::tab::Folder>,
tab: PtrCell<yazi_core::tab::Tab>,
v_cwd: Option<Value>,
v_files: Option<Value>,
v_stage: Option<Value>,
v_window: Option<Value>,
v_hovered: Option<Value>,
}
impl Deref for Folder {
@ -38,30 +33,20 @@ impl Folder {
}
};
Lives::scoped_userdata(Self {
window,
inner: inner.into(),
tab: tab.into(),
v_cwd: None,
v_files: None,
v_stage: None,
v_window: None,
v_hovered: None,
})
Lives::scoped_userdata(Self { window, inner: inner.into(), tab: tab.into() })
}
}
impl UserData for Folder {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, cwd, |_, me| Ok(Url::new(&me.url)));
cached_field!(fields, files, |_, me| Files::make(0..me.files.len(), me, &me.tab));
cached_field!(fields, stage, |_, me| Ok(FolderStage::new(me.stage.clone())));
cached_field!(fields, window, |_, me| Files::make(me.window.clone(), me, &me.tab));
fields.add_cached_field("cwd", |_, me| Ok(Url::new(&me.url)));
fields.add_static_field("files", |_, me| Files::make(0..me.files.len(), me, &me.tab));
fields.add_cached_field("stage", |_, me| Ok(FolderStage::new(me.stage.clone())));
fields.add_static_field("window", |_, me| Files::make(me.window.clone(), me, &me.tab));
fields.add_field_method_get("offset", |_, me| Ok(me.offset));
fields.add_field_method_get("cursor", |_, me| Ok(me.cursor));
cached_field!(fields, hovered, |_, me| {
fields.add_static_field("hovered", |_, me| {
me.hovered().map(|_| File::make(me.cursor, me, &me.tab)).transpose()
});
}

View file

@ -1,18 +1,12 @@
use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataFields, Value};
use yazi_binding::cached_field;
use yazi_shim::strum::IntoStr;
use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_shim::{mlua::UserDataFieldsExt, strum::IntoStr};
use super::{Lives, PtrCell};
pub(super) struct Preference {
inner: PtrCell<yazi_core::tab::Preference>,
v_name: Option<Value>,
v_linemode: Option<Value>,
v_sort_by: Option<Value>,
}
impl Deref for Preference {
@ -23,26 +17,19 @@ impl Deref for Preference {
impl Preference {
pub(super) fn make(inner: &yazi_core::tab::Preference) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self {
inner: inner.into(),
v_name: None,
v_linemode: None,
v_sort_by: None,
})
Lives::scoped_userdata(Self { inner: inner.into() })
}
}
impl UserData for Preference {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
// Display
cached_field!(fields, name, |lua, me| lua.create_string(&me.name));
cached_field!(fields, linemode, |lua, me| lua.create_string(&*me.linemode));
fields.add_cached_field("name", |lua, me| lua.create_string(&me.name));
fields.add_cached_field("linemode", |lua, me| lua.create_string(&*me.linemode));
fields.add_field_method_get("show_hidden", |_, me| Ok(me.show_hidden));
// Sorting
cached_field!(fields, sort_by, |_, me| Ok(me.sort_by.into_str()));
fields.add_cached_field("sort_by", |_, me| Ok(me.sort_by.into_str()));
fields.add_field_method_get("sort_sensitive", |_, me| Ok(me.sort_sensitive));
fields.add_field_method_get("sort_reverse", |_, me| Ok(me.sort_reverse));
fields.add_field_method_get("sort_dir_first", |_, me| Ok(me.sort_dir_first));

View file

@ -1,15 +1,13 @@
use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataFields, Value};
use yazi_binding::cached_field;
use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_config::LAYOUT;
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Folder, Lives, PtrCell};
pub(super) struct Preview {
tab: PtrCell<yazi_core::tab::Tab>,
v_folder: Option<Value>,
}
impl Deref for Preview {
@ -20,14 +18,14 @@ impl Deref for Preview {
impl Preview {
pub(super) fn make(tab: &yazi_core::tab::Tab) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { tab: tab.into(), v_folder: None })
Lives::scoped_userdata(Self { tab: tab.into() })
}
}
impl UserData for Preview {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("skip", |_, me| Ok(me.skip));
cached_field!(fields, folder, |_, me| {
fields.add_static_field("folder", |_, me| {
me.tab
.hovered_folder()
.map(|f| {

View file

@ -1,21 +1,13 @@
use std::{borrow::Cow, ops::Deref};
use mlua::{AnyUserData, UserData, UserDataFields, UserDataMethods, Value};
use yazi_binding::{Id, UrlRef, cached_field};
use mlua::{AnyUserData, UserData, UserDataFields, UserDataMethods};
use yazi_binding::{Id, UrlRef};
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Finder, Folder, Lives, Mode, Preference, Preview, PtrCell, Selected};
pub(super) struct Tab {
inner: PtrCell<yazi_core::tab::Tab>,
v_name: Option<Value>,
v_mode: Option<Value>,
v_pref: Option<Value>,
v_current: Option<Value>,
v_parent: Option<Value>,
v_selected: Option<Value>,
v_preview: Option<Value>,
v_finder: Option<Value>,
}
impl Deref for Tab {
@ -26,42 +18,29 @@ impl Deref for Tab {
impl Tab {
pub(super) fn make(inner: &yazi_core::tab::Tab) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self {
inner: inner.into(),
v_name: None,
v_mode: None,
v_pref: None,
v_current: None,
v_parent: None,
v_selected: None,
v_preview: None,
v_finder: None,
})
Lives::scoped_userdata(Self { inner: inner.into() })
}
}
impl UserData for Tab {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, name, |lua, me| {
match me.name() {
Cow::Borrowed(s) => lua.create_string(s),
Cow::Owned(s) => lua.create_external_string(s),
}
fields.add_cached_field("name", |lua, me| match me.name() {
Cow::Borrowed(s) => lua.create_string(s),
Cow::Owned(s) => lua.create_external_string(s),
});
cached_field!(fields, mode, |_, me| Mode::make(&me.mode));
cached_field!(fields, pref, |_, me| Preference::make(&me.pref));
cached_field!(fields, current, |_, me| Folder::make(None, &me.current, me));
cached_field!(fields, parent, |_, me| {
fields.add_static_field("mode", |_, me| Mode::make(&me.mode));
fields.add_static_field("pref", |_, me| Preference::make(&me.pref));
fields.add_static_field("current", |_, me| Folder::make(None, &me.current, me));
fields.add_static_field("parent", |_, me| {
me.parent.as_ref().map(|f| Folder::make(None, f, me)).transpose()
});
cached_field!(fields, selected, |_, me| Selected::make(&me.selected));
fields.add_static_field("selected", |_, me| Selected::make(&me.selected));
cached_field!(fields, preview, |_, me| Preview::make(me));
cached_field!(fields, finder, |_, me| me.finder.as_ref().map(Finder::make).transpose());
fields.add_static_field("preview", |_, me| Preview::make(me));
fields.add_static_field("finder", |_, me| me.finder.as_ref().map(Finder::make).transpose());
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {

View file

@ -1,16 +1,14 @@
use std::ops::Deref;
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
use yazi_binding::{SER_OPT, cached_field};
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields};
use yazi_binding::SER_OPT;
use yazi_scheduler::Progress;
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Lives, PtrCell};
pub(super) struct TaskSnap {
inner: PtrCell<yazi_scheduler::TaskSnap>,
v_title: Option<Value>,
v_prog: Option<Value>,
}
impl Deref for TaskSnap {
@ -21,14 +19,14 @@ impl Deref for TaskSnap {
impl TaskSnap {
pub(super) fn make(inner: &yazi_scheduler::TaskSnap) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into(), v_title: None, v_prog: None })
Lives::scoped_userdata(Self { inner: inner.into() })
}
}
impl UserData for TaskSnap {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, title, |lua, me| lua.create_string(&me.title));
cached_field!(fields, prog, |lua, me| lua.to_value_with(&me.prog, SER_OPT));
fields.add_cached_field("title", |lua, me| lua.create_string(&me.title));
fields.add_cached_field("prog", |lua, me| lua.to_value_with(&me.prog, SER_OPT));
fields.add_field_method_get("running", |_, me| Ok(me.prog.running()));
fields.add_field_method_get("cooked", |_, me| Ok(me.prog.cooked()));

View file

@ -1,17 +1,14 @@
use std::ops::Deref;
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value};
use yazi_binding::{SER_OPT, cached_field};
use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields};
use yazi_binding::SER_OPT;
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Lives, PtrCell};
use crate::lives::{Behavior, TaskSnap};
pub(super) struct Tasks {
inner: PtrCell<yazi_core::tasks::Tasks>,
v_behavior: Option<Value>,
v_snaps: Option<Value>,
v_summary: Option<Value>,
}
impl Deref for Tasks {
@ -22,13 +19,7 @@ impl Deref for Tasks {
impl Tasks {
pub(super) fn make(inner: &yazi_core::tasks::Tasks) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self {
inner: inner.into(),
v_behavior: None,
v_snaps: None,
v_summary: None,
})
Lives::scoped_userdata(Self { inner: inner.into() })
}
}
@ -36,9 +27,9 @@ impl UserData for Tasks {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("cursor", |_, me| Ok(me.cursor));
cached_field!(fields, behavior, |_, me| Behavior::make(&me.scheduler.behavior));
fields.add_static_field("behavior", |_, me| Behavior::make(&me.scheduler.behavior));
cached_field!(fields, snaps, |lua, me| {
fields.add_static_field("snaps", |lua, me| {
let tbl = lua.create_table_with_capacity(me.snaps.len(), 0)?;
for snap in &me.snaps {
tbl.raw_push(TaskSnap::make(snap)?)?;
@ -46,6 +37,6 @@ impl UserData for Tasks {
Ok(tbl)
});
cached_field!(fields, summary, |lua, me| lua.to_value_with(&me.summary, SER_OPT));
fields.add_cached_field("summary", |lua, me| lua.to_value_with(&me.summary, SER_OPT));
}
}

View file

@ -1,15 +1,12 @@
use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataFields, Value};
use yazi_binding::cached_field;
use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Lives, PtrCell};
pub(super) struct Which {
inner: PtrCell<yazi_core::which::Which>,
v_tx: Option<Value>,
v_cands: Option<Value>,
}
impl Deref for Which {
@ -20,15 +17,15 @@ impl Deref for Which {
impl Which {
pub(super) fn make(inner: &yazi_core::which::Which) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into(), v_tx: None, v_cands: None })
Lives::scoped_userdata(Self { inner: inner.into() })
}
}
impl UserData for Which {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, tx, |_, me| Ok(me.tx.clone().map(yazi_binding::MpscUnboundedTx)));
fields.add_cached_field("tx", |_, me| Ok(me.tx.clone().map(yazi_binding::MpscUnboundedTx)));
fields.add_field_method_get("times", |_, me| Ok(me.inner.times));
cached_field!(fields, cands, |lua, me| {
fields.add_cached_field("cands", |lua, me| {
lua.create_sequence_from(me.inner.cands.iter().cloned().map(yazi_binding::keymap::ChordCow))
});

View file

@ -34,7 +34,9 @@ ansi-to-tui = { workspace = true }
anyhow = { workspace = true }
futures = { workspace = true }
hashbrown = { workspace = true }
inventory = { workspace = true }
mlua = { workspace = true }
ordered-float = { workspace = true }
paste = { workspace = true }
ratatui = { workspace = true }
serde_json = { workspace = true }

View file

@ -2,14 +2,13 @@ use std::{ops::Deref, sync::Arc};
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, Value};
use yazi_config::YAZI;
use yazi_shim::mlua::UserDataFieldsExt;
use crate::{FileRef, Id, Iter, cached_field};
use crate::{FileRef, Id, Iter};
#[derive(Clone)]
pub struct Fetcher {
inner: Arc<yazi_config::plugin::Fetcher>,
v_name: Option<Value>,
}
impl Deref for Fetcher {
@ -20,7 +19,7 @@ impl Deref for Fetcher {
impl Fetcher {
pub fn new(inner: impl Into<Arc<yazi_config::plugin::Fetcher>>) -> Self {
Self { inner: inner.into(), v_name: None }
Self { inner: inner.into() }
}
}
@ -28,7 +27,7 @@ impl UserData for Fetcher {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
fields.add_cached_field("name", |lua, me| lua.create_string(&*me.name));
}
}

View file

@ -2,13 +2,12 @@ use std::{ops::Deref, sync::Arc};
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Table, UserData, UserDataFields, Value};
use yazi_config::YAZI;
use yazi_shim::mlua::UserDataFieldsExt;
use crate::{FileRef, Id, Iter, cached_field};
use crate::{FileRef, Id, Iter};
pub struct OpenRule {
inner: Arc<yazi_config::open::OpenRule>,
v_use: Option<Value>,
}
impl Deref for OpenRule {
@ -23,7 +22,7 @@ impl From<OpenRule> for Arc<yazi_config::open::OpenRule> {
impl OpenRule {
pub fn new(inner: impl Into<Arc<yazi_config::open::OpenRule>>) -> Self {
Self { inner: inner.into(), v_use: None }
Self { inner: inner.into() }
}
}
@ -36,7 +35,7 @@ impl FromLua for OpenRule {
impl UserData for OpenRule {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, use, |lua, me| {
fields.add_cached_field("use", |lua, me| {
lua.create_sequence_from(me.r#use.iter().map(|s| s.as_str()))
});
}

View file

@ -1,14 +1,12 @@
use std::{ops::Deref, sync::Arc};
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Table, UserData, UserDataFields, Value};
use yazi_shim::mlua::UserDataFieldsExt;
use crate::{Id, Iter, cached_field};
use crate::{Id, Iter};
pub struct OpenerRule {
inner: Arc<yazi_config::opener::OpenerRule>,
v_run: Option<Value>,
v_desc: Option<Value>,
}
impl Deref for OpenerRule {
@ -23,7 +21,7 @@ impl From<OpenerRule> for Arc<yazi_config::opener::OpenerRule> {
impl OpenerRule {
pub fn new(inner: impl Into<Arc<yazi_config::opener::OpenerRule>>) -> Self {
Self { inner: inner.into(), v_run: None, v_desc: None }
Self { inner: inner.into() }
}
}
@ -39,10 +37,10 @@ impl FromLua for OpenerRule {
impl UserData for OpenerRule {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, run, |lua, me| lua.create_string(&*me.run));
fields.add_cached_field("run", |lua, me| lua.create_string(&*me.run));
fields.add_field_method_get("block", |_, me| Ok(me.block));
fields.add_field_method_get("orphan", |_, me| Ok(me.orphan));
cached_field!(fields, desc, |lua, me| lua.create_string(&*me.desc));
fields.add_cached_field("desc", |lua, me| lua.create_string(&*me.desc));
}
}

View file

@ -2,14 +2,13 @@ use std::{ops::Deref, sync::Arc};
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, Value};
use yazi_config::YAZI;
use yazi_shim::mlua::UserDataFieldsExt;
use crate::{FileRef, Id, Iter, cached_field};
use crate::{FileRef, Id, Iter};
#[derive(Clone)]
pub struct Preloader {
inner: Arc<yazi_config::plugin::Preloader>,
v_name: Option<Value>,
}
impl Deref for Preloader {
@ -20,7 +19,7 @@ impl Deref for Preloader {
impl Preloader {
pub fn new(inner: impl Into<Arc<yazi_config::plugin::Preloader>>) -> Self {
Self { inner: inner.into(), v_name: None }
Self { inner: inner.into() }
}
}
@ -28,7 +27,7 @@ impl UserData for Preloader {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
fields.add_cached_field("name", |lua, me| lua.create_string(&*me.name));
}
}

View file

@ -2,14 +2,13 @@ use std::{ops::Deref, sync::Arc};
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Table, UserData, UserDataFields, Value};
use yazi_config::YAZI;
use yazi_shim::mlua::UserDataFieldsExt;
use crate::{FileRef, Id, Iter, cached_field};
use crate::{FileRef, Id, Iter};
#[derive(Clone)]
pub struct Previewer {
inner: Arc<yazi_config::plugin::Previewer>,
v_name: Option<Value>,
}
impl Deref for Previewer {
@ -24,7 +23,7 @@ impl From<Previewer> for Arc<yazi_config::plugin::Previewer> {
impl Previewer {
pub fn new(inner: impl Into<Arc<yazi_config::plugin::Previewer>>) -> Self {
Self { inner: inner.into(), v_name: None }
Self { inner: inner.into() }
}
}
@ -38,7 +37,7 @@ impl UserData for Previewer {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
fields.add_cached_field("name", |lua, me| lua.create_string(&*me.name));
}
}

View file

@ -2,14 +2,13 @@ use std::{ops::Deref, sync::Arc};
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, Value};
use yazi_config::YAZI;
use yazi_shim::mlua::UserDataFieldsExt;
use crate::{FileRef, Id, Iter, cached_field};
use crate::{FileRef, Id, Iter};
#[derive(Clone)]
pub struct Spotter {
inner: Arc<yazi_config::plugin::Spotter>,
v_name: Option<Value>,
}
impl Deref for Spotter {
@ -20,7 +19,7 @@ impl Deref for Spotter {
impl Spotter {
pub fn new(inner: impl Into<Arc<yazi_config::plugin::Spotter>>) -> Self {
Self { inner: inner.into(), v_name: None }
Self { inner: inner.into() }
}
}
@ -28,7 +27,7 @@ impl UserData for Spotter {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
fields.add_cached_field("name", |lua, me| lua.create_string(&*me.name));
}
}

View file

@ -1,16 +1,11 @@
use std::{mem, ops::Deref};
use mlua::{UserData, UserDataFields, Value};
use yazi_shim::strum::IntoStr;
use mlua::{IntoLua, UserData, UserDataFields, Value};
use yazi_shim::{mlua::UserDataFieldsExt, strum::IntoStr};
use yazi_term::event::{DndDropArrive, DndEvent as Inner};
use crate::{cached_field, cached_field_mut};
pub struct DndEvent {
inner: Inner,
v_mimes: Option<Value>,
v_data: Option<mlua::Result<Value>>,
}
impl Deref for DndEvent {
@ -20,7 +15,7 @@ impl Deref for DndEvent {
}
impl From<Inner> for DndEvent {
fn from(inner: Inner) -> Self { Self { inner, v_mimes: None, v_data: None } }
fn from(inner: Inner) -> Self { Self { inner } }
}
impl UserData for DndEvent {
@ -35,7 +30,7 @@ impl UserData for DndEvent {
fields.add_field_method_get("op", |_, me| Ok(me.inner.op().map(IntoStr::into_str)));
cached_field!(fields, mimes, |lua, me| {
fields.add_cached_field("mimes", |lua, me| {
if let Some(mimes) = me.inner.mimes() {
lua.create_sequence_from(mimes.iter())?.into_lua(lua)
} else {
@ -43,13 +38,11 @@ impl UserData for DndEvent {
}
});
cached_field_mut!(fields, data, |lua, me| {
match &mut me.inner {
Inner::DropArrive(DndDropArrive { data, .. }) => {
lua.create_external_string(mem::take(data))?.into_lua(lua)
}
_ => Ok(Value::Nil),
fields.add_cached_field_mut("data", |lua, me| match &mut me.inner {
Inner::DropArrive(DndDropArrive { data, .. }) => {
lua.create_external_string(mem::take(data))?.into_lua(lua)
}
_ => Ok(Value::Nil),
});
}
}

View file

@ -1,6 +1,9 @@
use std::ops::Deref;
use std::{mem, ops::{Deref, DerefMut}};
use mlua::UserData;
use mlua::{UserData, UserDataFields};
use yazi_shim::mlua::UserDataFieldsExt;
use crate::event::Cmd;
pub struct Action {
inner: yazi_shared::event::Action,
@ -12,8 +15,16 @@ impl Deref for Action {
fn deref(&self) -> &Self::Target { &self.inner }
}
impl DerefMut for Action {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
}
impl Action {
pub fn new(inner: impl Into<yazi_shared::event::Action>) -> Self { Self { inner: inner.into() } }
}
impl UserData for Action {}
impl UserData for Action {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_cached_field_mut("cmd", |_, me| Ok(Cmd::new(mem::take(&mut me.cmd))));
}
}

View file

@ -1,6 +1,9 @@
use std::ops::Deref;
use std::{mem, ops::{Deref, DerefMut}};
use mlua::UserData;
use mlua::{UserData, UserDataFields};
use yazi_shim::mlua::UserDataFieldsExt;
use crate::Sendable;
pub struct Cmd {
inner: yazi_shared::event::Cmd,
@ -12,8 +15,20 @@ impl Deref for Cmd {
fn deref(&self) -> &Self::Target { &self.inner }
}
impl DerefMut for Cmd {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
}
impl Cmd {
pub fn new(inner: impl Into<yazi_shared::event::Cmd>) -> Self { Self { inner: inner.into() } }
}
impl UserData for Cmd {}
impl UserData for Cmd {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_cached_field_mut("name", |_, me| Ok(mem::take(&mut me.name)));
fields.add_cached_field_mut("args", |lua, me| {
Sendable::args_to_table(lua, mem::take(&mut me.args))
});
}
}

View file

@ -11,14 +11,6 @@ const EXPECTED: &str = "expected a table, File, or fs::File";
#[derive(Clone)]
pub struct File {
pub(crate) inner: yazi_fs::File,
v_cha: Option<Value>,
v_url: Option<Value>,
v_link_to: Option<Value>,
v_name: Option<Value>,
v_path: Option<Value>,
v_cache: Option<Value>,
}
impl Deref for File {
@ -32,18 +24,7 @@ impl From<File> for yazi_fs::File {
}
impl File {
pub fn new(inner: impl Into<yazi_fs::File>) -> Self {
Self {
inner: inner.into(),
v_cha: None,
v_url: None,
v_link_to: None,
v_name: None,
v_path: None,
v_cache: None,
}
}
pub fn new(inner: impl Into<yazi_fs::File>) -> Self { Self { inner: inner.into() } }
pub fn install(lua: &Lua) -> mlua::Result<()> {
lua.globals().raw_set("File", lua.create_function(|_, file: Self| Ok(file))?)

View file

@ -1,14 +1,12 @@
use std::{ops::Deref, sync::Arc};
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Table, UserData, UserDataFields, Value};
use yazi_shim::mlua::UserDataFieldsExt;
use crate::{Id, Iter, cached_field, event::Action, keymap::Key};
use crate::{Id, Iter, event::Action, keymap::Key};
pub struct Chord {
inner: Arc<yazi_config::keymap::Chord>,
v_on: Option<Value>,
v_run: Option<Value>,
}
impl Deref for Chord {
@ -23,7 +21,7 @@ impl From<Chord> for Arc<yazi_config::keymap::Chord> {
impl Chord {
pub fn new(inner: impl Into<Arc<yazi_config::keymap::Chord>>) -> Self {
Self { inner: inner.into(), v_on: None, v_run: None }
Self { inner: inner.into() }
}
}
@ -37,10 +35,14 @@ impl UserData for Chord {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(Id(me.inner.id)));
cached_field!(fields, on, |lua, me| lua.create_sequence_from(me.on.iter().copied().map(Key)));
fields
.add_cached_field("on", |lua, me| lua.create_sequence_from(me.on.iter().copied().map(Key)));
cached_field!(fields, run, |lua, me| lua
.create_sequence_from(me.run.iter().cloned().map(Action::new)));
fields.add_cached_field("run", |lua, me| {
lua.create_sequence_from(me.run.iter().cloned().map(Action::new))
});
fields.add_cached_field("desc", |lua, me| lua.create_string(&me.desc));
}
}

View file

@ -1,16 +1,15 @@
use std::ops::Deref;
use anyhow::bail;
use mlua::{UserData, UserDataFields, Value};
use mlua::{UserData, UserDataFields};
use yazi_config::KEYMAP;
use yazi_shared::Layer;
use yazi_shim::mlua::UserDataFieldsExt;
use crate::{cached_field, keymap::Chords};
use crate::keymap::Chords;
pub struct KeymapSection {
inner: &'static yazi_config::keymap::KeymapSection,
v_rules: Option<Value>,
}
impl Deref for KeymapSection {
@ -37,12 +36,12 @@ impl TryFrom<Layer> for KeymapSection {
Layer::Notify => bail!("invalid layer"),
};
Ok(Self { inner, v_rules: None })
Ok(Self { inner })
}
}
impl UserData for KeymapSection {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, rules, |_, me| Ok(Chords::new(me.inner)));
fields.add_cached_field("rules", |_, me| Ok(Chords::new(me.inner)));
}
}

View file

@ -2,4 +2,4 @@ mod macros;
yazi_macro::mod_pub!(config elements event keymap process theme);
yazi_macro::mod_flat!(access calculator cha chan composer dnd error fd file handle icon id image input iter layer mouse path permit range runtime scheme selector stage style tty url utils);
yazi_macro::mod_flat!(access calculator cha chan composer dnd error fd file handle icon id image input iter layer mouse path permit range runtime scheme selector sendable stage style tty url utils);

View file

@ -43,48 +43,6 @@ macro_rules! deprecate {
}};
}
#[macro_export]
macro_rules! cached_field {
($fields:ident, $key:ident, $value:expr) => {
$fields.add_field_function_get(stringify!($key), |lua, ud| {
use mlua::{Error::UserDataDestructed, IntoLua, Lua, Result, Value, Value::UserData};
ud.borrow_mut_scoped::<Self, Result<Value>>(|me| match paste::paste! { &me.[<v_ $key>] } {
Some(v) if !v.is_userdata() => Ok(v.clone()),
Some(v @ UserData(ud)) if !matches!(ud.borrow::<()>(), Err(UserDataDestructed)) => {
Ok(v.clone())
}
_ => {
let v = ($value as fn(&Lua, &Self) -> Result<_>)(lua, me)?.into_lua(lua)?;
paste::paste! { me.[<v_ $key>] = Some(v.clone()) };
Ok(v)
}
})?
});
};
}
#[macro_export]
macro_rules! cached_field_mut {
($fields:ident, $key:ident, $value:expr) => {
$fields.add_field_function_get(stringify!($key), |lua, ud| {
use mlua::{Error::UserDataDestructed, IntoLua, Lua, Result, Value, Value::UserData};
ud.borrow_mut_scoped::<Self, Result<Value>>(|me| match paste::paste! { &me.[<v_ $key>] } {
Some(Ok(v)) if !v.is_userdata() => Ok(v.clone()),
Some(Ok(v @ UserData(ud))) if !matches!(ud.borrow::<()>(), Err(UserDataDestructed)) => {
Ok(v.clone())
}
Some(Err(e)) => Err(e.clone()),
_ => {
let v =
($value as fn(&Lua, &mut Self) -> Result<_>)(lua, me).and_then(|v| v.into_lua(lua));
paste::paste! { me.[<v_ $key>] = Some(v.clone()) };
v
}
})?
});
};
}
#[macro_export]
macro_rules! impl_area_method {
($methods:ident) => {
@ -245,19 +203,21 @@ macro_rules! impl_style_shorthands {
#[macro_export]
macro_rules! impl_file_fields {
($fields:ident) => {
$crate::cached_field!($fields, cha, |_, me| Ok($crate::Cha(me.cha)));
$crate::cached_field!($fields, url, |_, me| Ok($crate::Url::new(me.url_owned())));
$crate::cached_field!($fields, link_to, |_, me| Ok(me.link_to.as_ref().map($crate::Path::new)));
use yazi_shim::mlua::UserDataFieldsExt;
$crate::cached_field!($fields, name, |lua, me| {
$fields.add_cached_field("cha", |_, me| Ok($crate::Cha(me.cha)));
$fields.add_cached_field("url", |_, me| Ok($crate::Url::new(me.url_owned())));
$fields.add_cached_field("link_to", |_, me| Ok(me.link_to.as_ref().map($crate::Path::new)));
$fields.add_cached_field("name", |lua, me| {
me.name().map(|s| lua.create_string(s.encoded_bytes())).transpose()
});
$crate::cached_field!($fields, path, |_, me| {
$fields.add_cached_field("path", |_, me| {
use yazi_fs::FsUrl;
use yazi_shared::url::AsUrl;
Ok($crate::Path::new(me.url.as_url().unified_path()))
});
$crate::cached_field!($fields, cache, |_, me| {
$fields.add_cached_field("cache", |_, me| {
use yazi_fs::FsUrl;
Ok(me.url.cache().map($crate::Path::new))
});

View file

@ -3,19 +3,13 @@ use std::ops::Deref;
use mlua::{ExternalError, ExternalResult, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};
use yazi_codegen::FromLuaOwned;
use yazi_shared::{path::{PathBufDyn, PathLike, StripPrefixError}, strand::{AsStrand, Strand, StrandCow}};
use crate::cached_field;
use yazi_shim::mlua::UserDataFieldsExt;
pub type PathRef = UserDataRef<Path>;
#[derive(FromLuaOwned)]
pub struct Path {
inner: PathBufDyn,
v_ext: Option<Value>,
v_name: Option<Value>,
v_parent: Option<Value>,
v_stem: Option<Value>,
}
impl Deref for Path {
@ -37,16 +31,7 @@ impl AsStrand for &Path {
}
impl Path {
pub fn new(path: impl Into<PathBufDyn>) -> Self {
Self {
inner: path.into(),
v_ext: None,
v_name: None,
v_parent: None,
v_stem: None,
}
}
pub fn new(path: impl Into<PathBufDyn>) -> Self { Self { inner: path.into() } }
pub fn install(lua: &Lua) -> mlua::Result<()> {
lua.globals().raw_set(
@ -110,14 +95,14 @@ impl Path {
impl UserData for Path {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, ext, |lua, me| {
fields.add_cached_field("ext", |lua, me| {
me.ext().map(|s| lua.create_string(s.encoded_bytes())).transpose()
});
cached_field!(fields, name, |lua, me| {
fields.add_cached_field("name", |lua, me| {
me.name().map(|s| lua.create_string(s.encoded_bytes())).transpose()
});
cached_field!(fields, parent, |_, me| Ok(me.parent().map(Self::new)));
cached_field!(fields, stem, |lua, me| {
fields.add_cached_field("parent", |_, me| Ok(me.parent().map(Self::new)));
fields.add_cached_field("stem", |lua, me| {
me.stem().map(|s| lua.create_string(s.encoded_bytes())).transpose()
});

View file

@ -1,31 +1,25 @@
use std::mem;
use mlua::{UserData, UserDataFields, Value};
use mlua::{UserData, UserDataFields};
use yazi_shim::mlua::UserDataFieldsExt;
use super::Status;
use crate::{cached_field, cached_field_mut};
pub struct Output {
inner: std::process::Output,
v_status: Option<Value>,
v_stdout: Option<mlua::Result<Value>>,
v_stderr: Option<mlua::Result<Value>>,
}
impl Output {
pub fn new(inner: std::process::Output) -> Self {
Self { inner, v_status: None, v_stdout: None, v_stderr: None }
}
pub fn new(inner: std::process::Output) -> Self { Self { inner } }
}
impl UserData for Output {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, status, |_, me| Ok(Status::new(me.inner.status)));
cached_field_mut!(fields, stdout, |lua, me| {
fields.add_cached_field("status", |_, me| Ok(Status::new(me.inner.status)));
fields.add_cached_field_mut("stdout", |lua, me| {
lua.create_external_string(mem::take(&mut me.inner.stdout))
});
cached_field_mut!(fields, stderr, |lua, me| {
fields.add_cached_field_mut("stderr", |lua, me| {
lua.create_external_string(mem::take(&mut me.inner.stderr))
});
}

View file

@ -1,17 +1,14 @@
use std::ops::Deref;
use mlua::{UserData, UserDataFields, Value};
use mlua::{UserData, UserDataFields};
use yazi_fs::FsScheme;
use yazi_shared::scheme::SchemeLike;
use yazi_shim::strum::IntoStr;
use yazi_shim::{mlua::UserDataFieldsExt, strum::IntoStr};
use crate::{Path, cached_field};
use crate::Path;
pub struct Scheme {
inner: yazi_shared::scheme::Scheme,
v_kind: Option<Value>,
v_cache: Option<Value>,
}
impl Deref for Scheme {
@ -22,14 +19,14 @@ impl Deref for Scheme {
impl Scheme {
pub fn new(scheme: impl Into<yazi_shared::scheme::Scheme>) -> Self {
Self { inner: scheme.into(), v_kind: None, v_cache: None }
Self { inner: scheme.into() }
}
}
impl UserData for Scheme {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, kind, |_, me| Ok(me.kind().into_str()));
cached_field!(fields, cache, |_, me| Ok(me.cache().map(Path::new)));
fields.add_cached_field("kind", |_, me| Ok(me.kind().into_str()));
fields.add_cached_field("cache", |_, me| Ok(me.cache().map(Path::new)));
fields.add_field_method_get("is_virtual", |_, me| Ok(me.is_virtual()));
}

View file

@ -5,6 +5,8 @@ use mlua::{ExternalError, IntoLua, Lua, MultiValue, Table, Value};
use ordered_float::OrderedFloat;
use yazi_shared::{any_data::AnyData, data::{Data, DataInventory, DataKey}, replace_cow};
use crate::{Id, Path, Url};
pub struct Sendable;
impl Sendable {
@ -40,14 +42,14 @@ impl Sendable {
});
}
Value::UserData(ud) => match ud.type_id() {
Some(t) if t == TypeId::of::<yazi_binding::Url>() => {
return Ok(Data::Url(ud.take::<yazi_binding::Url>()?.into()));
Some(t) if t == TypeId::of::<Url>() => {
return Ok(Data::Url(ud.take::<Url>()?.into()));
}
Some(t) if t == TypeId::of::<yazi_binding::Path>() => {
return Ok(Data::Path(ud.take::<yazi_binding::Path>()?.into()));
Some(t) if t == TypeId::of::<Path>() => {
return Ok(Data::Path(ud.take::<Path>()?.into()));
}
Some(t) if t == TypeId::of::<yazi_binding::Id>() => {
return Ok(Data::Id(**ud.borrow::<yazi_binding::Id>()?));
Some(t) if t == TypeId::of::<Id>() => {
return Ok(Data::Id(**ud.borrow::<Id>()?));
}
Some(t) if t == TypeId::of::<AnyData>() => return Ok(Data::Any(ud.take::<AnyData>()?.0)),
_ => {}
@ -87,8 +89,8 @@ impl Sendable {
}
Value::Table(tbl)
}
Data::Url(u) => yazi_binding::Url::new(u).into_lua(lua)?,
Data::Path(u) => yazi_binding::Path::new(u).into_lua(lua)?,
Data::Url(u) => Url::new(u).into_lua(lua)?,
Data::Path(u) => Path::new(u).into_lua(lua)?,
Data::Bytes(b) => Value::String(lua.create_external_string(b)?),
Data::Any(a) => a.into_lua(lua)?,
_ => Self::data_to_value_ref(lua, &data)?,
@ -117,9 +119,9 @@ impl Sendable {
}
Value::Table(tbl)
}
Data::Id(i) => yazi_binding::Id(*i).into_lua(lua)?,
Data::Url(u) => yazi_binding::Url::new(u).into_lua(lua)?,
Data::Path(u) => yazi_binding::Path::new(u).into_lua(lua)?,
Data::Id(i) => Id(*i).into_lua(lua)?,
Data::Url(u) => Url::new(u).into_lua(lua)?,
Data::Path(u) => Path::new(u).into_lua(lua)?,
Data::Bytes(b) => Value::String(lua.create_string(b)?),
Data::Any(a) => a.to_lua(lua)?,
})
@ -201,15 +203,9 @@ impl Sendable {
Value::Function(_) => Err("function is not supported".into_lua_err())?,
Value::Thread(_) => Err("thread is not supported".into_lua_err())?,
Value::UserData(ud) => match ud.type_id() {
Some(t) if t == TypeId::of::<yazi_binding::Url>() => {
DataKey::Url(ud.take::<yazi_binding::Url>()?.into())
}
Some(t) if t == TypeId::of::<yazi_binding::Path>() => {
DataKey::Path(ud.take::<yazi_binding::Path>()?.into())
}
Some(t) if t == TypeId::of::<yazi_binding::Id>() => {
DataKey::Id(**ud.borrow::<yazi_binding::Id>()?)
}
Some(t) if t == TypeId::of::<Url>() => DataKey::Url(ud.take::<Url>()?.into()),
Some(t) if t == TypeId::of::<Path>() => DataKey::Path(ud.take::<Path>()?.into()),
Some(t) if t == TypeId::of::<Id>() => DataKey::Id(**ud.borrow::<Id>()?),
_ => Err(format!("unsupported userdata included: {ud:?}").into_lua_err())?,
},
Value::Error(_) => Err("error is not supported".into_lua_err())?,
@ -220,8 +216,8 @@ impl Sendable {
fn key_to_value(lua: &Lua, key: DataKey) -> mlua::Result<Value> {
match key {
DataKey::String(Cow::Owned(s)) => lua.create_external_string(s).map(Value::String),
DataKey::Url(u) => yazi_binding::Url::new(u).into_lua(lua),
DataKey::Path(u) => yazi_binding::Path::new(u).into_lua(lua),
DataKey::Url(u) => Url::new(u).into_lua(lua),
DataKey::Path(u) => Path::new(u).into_lua(lua),
DataKey::Bytes(b) => lua.create_external_string(b).map(Value::String),
_ => Self::key_to_value_ref(lua, &key),
}
@ -234,9 +230,9 @@ impl Sendable {
DataKey::Integer(i) => Value::Integer(*i),
DataKey::Number(n) => Value::Number(n.0),
DataKey::String(s) => Value::String(lua.create_string(&**s)?),
DataKey::Id(i) => yazi_binding::Id(*i).into_lua(lua)?,
DataKey::Url(u) => yazi_binding::Url::new(u).into_lua(lua)?,
DataKey::Path(u) => yazi_binding::Path::new(u).into_lua(lua)?,
DataKey::Id(i) => Id(*i).into_lua(lua)?,
DataKey::Url(u) => Url::new(u).into_lua(lua)?,
DataKey::Path(u) => Path::new(u).into_lua(lua)?,
DataKey::Bytes(b) => Value::String(lua.create_string(b)?),
})
}

View file

@ -4,8 +4,9 @@ use mlua::{AnyUserData, ExternalError, ExternalResult, IntoLua, Lua, MetaMethod,
use yazi_codegen::FromLuaOwned;
use yazi_fs::{FsHash64, FsHash128, FsUrl};
use yazi_shared::{path::{PathLike, StripPrefixError}, scheme::{SchemeCow, SchemeLike}, strand::{StrandLike, ToStrand}, url::{AsUrl, UrlCow, UrlLike}};
use yazi_shim::mlua::UserDataFieldsExt;
use crate::{Path, Scheme, cached_field};
use crate::{Path, Scheme};
pub type UrlRef = UserDataRef<Url>;
@ -14,19 +15,6 @@ const EXPECTED: &str = "expected a string, Url, or Path";
#[derive(FromLuaOwned)]
pub struct Url {
inner: yazi_shared::url::UrlBuf,
v_path: Option<Value>,
v_name: Option<Value>,
v_stem: Option<Value>,
v_ext: Option<Value>,
v_urn: Option<Value>,
v_base: Option<Value>,
v_parent: Option<Value>,
v_scheme: Option<Value>,
v_domain: Option<Value>,
v_cache: Option<Value>,
}
impl Deref for Url {
@ -62,24 +50,7 @@ impl TryFrom<&[u8]> for Url {
}
impl Url {
pub fn new(url: impl Into<yazi_shared::url::UrlBuf>) -> Self {
Self {
inner: url.into(),
v_path: None,
v_name: None,
v_stem: None,
v_ext: None,
v_urn: None,
v_base: None,
v_parent: None,
v_scheme: None,
v_domain: None,
v_cache: None,
}
}
pub fn new(url: impl Into<yazi_shared::url::UrlBuf>) -> Self { Self { inner: url.into() } }
pub fn install(lua: &Lua) -> mlua::Result<()> {
lua.globals().raw_set(
@ -171,28 +142,28 @@ impl Url {
impl UserData for Url {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, path, |_, me| Ok(Path::new(me.loc())));
cached_field!(fields, name, |lua, me| {
fields.add_cached_field("path", |_, me| Ok(Path::new(me.loc())));
fields.add_cached_field("name", |lua, me| {
me.name().map(|s| lua.create_string(s.encoded_bytes())).transpose()
});
cached_field!(fields, stem, |lua, me| {
fields.add_cached_field("stem", |lua, me| {
me.stem().map(|s| lua.create_string(s.encoded_bytes())).transpose()
});
cached_field!(fields, ext, |lua, me| {
fields.add_cached_field("ext", |lua, me| {
me.ext().map(|s| lua.create_string(s.encoded_bytes())).transpose()
});
cached_field!(fields, urn, |_, me| Ok(Path::new(me.urn())));
cached_field!(fields, base, |_, me| {
fields.add_cached_field("urn", |_, me| Ok(Path::new(me.urn())));
fields.add_cached_field("base", |_, me| {
Ok(Some(me.base()).filter(|u| !u.loc().is_empty()).map(Self::new))
});
cached_field!(fields, parent, |_, me| Ok(me.parent().map(Self::new)));
fields.add_cached_field("parent", |_, me| Ok(me.parent().map(Self::new)));
cached_field!(fields, scheme, |_, me| Ok(Scheme::new(me.scheme())));
cached_field!(fields, domain, |lua, me| {
fields.add_cached_field("scheme", |_, me| Ok(Scheme::new(me.scheme())));
fields.add_cached_field("domain", |lua, me| {
me.scheme().domain().map(|s| lua.create_string(s)).transpose()
});
cached_field!(fields, cache, |_, me| Ok(me.cache().map(Path::new)));
fields.add_cached_field("cache", |_, me| Ok(me.cache().map(Path::new)));
fields.add_field_method_get("is_regular", |_, me| Ok(me.is_regular()));
fields.add_field_method_get("is_search", |_, me| Ok(me.is_search()));

View file

@ -3,7 +3,7 @@ use std::{borrow::Cow, hash::{Hash, Hasher}, sync::{Arc, OnceLock}};
use regex::Regex;
use serde::{Deserialize, Deserializer, de};
use serde_with::{DeserializeAs, DisplayFromStr, OneOrMany};
use yazi_shared::{Id, Layer, Source, event::Action};
use yazi_shared::{Id, Layer, event::{Actions, deserialize_actions}};
use super::{Key, ids::chord_id};
use crate::{Mixable, Platform, keymap::Chords};
@ -16,8 +16,8 @@ pub struct Chord<const L: u8 = { Layer::Null as u8 }> {
pub id: Id,
#[serde(deserialize_with = "deserialize_on")]
pub on: Vec<Key>,
#[serde(deserialize_with = "deserialize_run::<L, _>")]
pub run: Vec<Action>,
#[serde(deserialize_with = "deserialize_actions::<L, _>")]
pub run: Actions,
#[serde(default)]
pub desc: String,
#[serde(default)]
@ -95,26 +95,6 @@ where
Ok(keys)
}
fn deserialize_run<'de, const L: u8, D>(deserializer: D) -> Result<Vec<Action>, D::Error>
where
D: Deserializer<'de>,
{
let Some(layer) = Layer::from_repr(L) else {
return Err(de::Error::custom(format!("invalid keymap layer const: {L}")));
};
let mut actions: Vec<Action> = OneOrMany::<DisplayFromStr>::deserialize_as(deserializer)?;
for action in &mut actions {
action.source = Source::Key;
if action.layer == Layer::Null {
action.layer = layer;
}
}
Ok(actions)
}
// --- Matcher
#[derive(Default)]
pub struct ChordMatcher {

View file

@ -1 +1 @@
yazi_macro::mod_flat!(chord chords cow ids key keymap section);
yazi_macro::mod_flat!(chord chord_cow chords ids key keymap section);

View file

@ -31,7 +31,6 @@ hashbrown = { workspace = true }
indexmap = { workspace = true }
inventory = { workspace = true }
mlua = { workspace = true }
ordered-float = { workspace = true }
parking_lot = { workspace = true }
paste = { workspace = true }
serde = { workspace = true }

View file

@ -1,9 +1,9 @@
use mlua::{IntoLua, Lua, Value};
use serde::Serialize;
use yazi_binding::Sendable;
use yazi_shared::data::Data;
use super::Ember;
use crate::Sendable;
#[derive(Clone, Debug)]
pub struct EmberCustom {

View file

@ -1,6 +1,6 @@
yazi_macro::mod_pub!(ember);
yazi_macro::mod_flat!(client payload pubsub pump sendable server state stream);
yazi_macro::mod_flat!(client payload pubsub pump server state stream);
pub fn init() {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();

View file

@ -20,9 +20,9 @@ yazi-term = { path = "../yazi-term", version = "26.5.6" }
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
# External dependencies
anyhow = { workspace = true }
either = { workspace = true }
ratatui = { workspace = true }
scopeguard = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
anyhow = { workspace = true }
either = { workspace = true }
ratatui = { workspace = true }
scopeguard = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }

View file

@ -1,6 +1,6 @@
use mlua::{ExternalError, Function, Lua, Table};
use tokio::sync::mpsc;
use yazi_dds::Sendable;
use yazi_binding::Sendable;
use yazi_macro::emit;
use yazi_shared::{Layer, Source, event::Action};

View file

@ -27,7 +27,7 @@ impl Utils {
Ok(ChordCow::Owned(Chord {
id: yazi_config::keymap::chord_id(),
on: Self::parse_keys(cand.raw_get("on")?)?,
run: vec![relay!(which:callback, [i + 1])],
run: relay!(which:callback, [i + 1]).into(),
desc: cand.raw_get("desc").unwrap_or_default(),
r#for: Platform::All,
}))

View file

@ -2,9 +2,8 @@ use anyhow::Context;
use futures::future::join_all;
use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, MultiValue, Table, Value, Variadic};
use tokio::sync::mpsc;
use yazi_binding::{Handle, MpscRx, MpscTx, MpscUnboundedRx, MpscUnboundedTx, OneshotRx, OneshotTx, runtime, runtime_mut};
use yazi_binding::{Handle, MpscRx, MpscTx, MpscUnboundedRx, MpscUnboundedTx, OneshotRx, OneshotTx, Sendable, runtime, runtime_mut};
use yazi_core::{AppProxy, app::PluginOpt};
use yazi_dds::Sendable;
use yazi_runner::loader::LOADER;
use yazi_shared::{LOCAL_SET, data::Data};

View file

@ -19,7 +19,6 @@ vendored-lua = [ "mlua/vendored" ]
[dependencies]
yazi-binding = { path = "../yazi-binding", version = "26.5.6" }
yazi-config = { path = "../yazi-config", version = "26.5.6" }
yazi-dds = { path = "../yazi-dds", version = "26.5.6" }
yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }

View file

@ -1,6 +1,6 @@
use hashbrown::HashMap;
use mlua::{IntoLua, Lua, Value};
use yazi_dds::Sendable;
use yazi_binding::Sendable;
use yazi_shared::{Id, SStr, data::{Data, DataKey}};
#[derive(Clone, Debug, Default)]

View file

@ -1,8 +1,7 @@
use std::sync::Arc;
use mlua::{IntoLua, Lua, Value};
use yazi_binding::File;
use yazi_dds::Sendable;
use yazi_binding::{File, Sendable};
pub struct FetchJob {
pub fetcher: Arc<yazi_config::plugin::Fetcher>,

View file

@ -1,9 +1,8 @@
use std::sync::Arc;
use mlua::{IntoLua, Lua, Value};
use yazi_binding::{File, elements::Rect};
use yazi_binding::{File, Sendable, elements::Rect};
use yazi_config::LAYOUT;
use yazi_dds::Sendable;
pub struct PreloadJob {
pub preloader: Arc<yazi_config::plugin::Preloader>,

View file

@ -1,9 +1,8 @@
use std::sync::Arc;
use mlua::{IntoLua, Lua, Value};
use yazi_binding::{File, elements::Rect};
use yazi_binding::{File, Sendable, elements::Rect};
use yazi_config::LAYOUT;
use yazi_dds::Sendable;
use yazi_shared::pool::Symbol;
#[derive(Clone, Debug)]

View file

@ -4,9 +4,8 @@ use mlua::{ExternalError, ExternalResult, HookTriggers, IntoLua, ObjectLike, VmS
use tokio::{runtime::Handle, select};
use tokio_util::sync::CancellationToken;
use tracing::error;
use yazi_binding::{File, Id};
use yazi_binding::{File, Id, Sendable};
use yazi_config::plugin::Spotter;
use yazi_dds::Sendable;
use yazi_shared::{Ids, pool::Symbol};
use crate::{Runner, loader::LOADER};

View file

@ -2,7 +2,7 @@ use std::borrow::Cow;
use hashbrown::HashMap;
use mlua::{ExternalError, FromLua, Lua, Value};
use yazi_dds::Sendable;
use yazi_binding::Sendable;
use yazi_runner::entry::EntryJob;
use yazi_shared::{Id, SStr, data::{Data, DataKey}};

View file

@ -0,0 +1,57 @@
use std::{ops::{Deref, DerefMut}, slice, vec};
use serde::{Deserializer, de};
use serde_with::{DeserializeAs, DisplayFromStr, OneOrMany};
use crate::{Layer, Source, event::Action};
#[derive(Clone, Debug, Default)]
pub struct Actions(Vec<Action>);
impl Deref for Actions {
type Target = Vec<Action>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl DerefMut for Actions {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}
impl From<Action> for Actions {
fn from(value: Action) -> Self { Self(vec![value]) }
}
impl IntoIterator for Actions {
type IntoIter = vec::IntoIter<Action>;
type Item = Action;
fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
}
impl<'a> IntoIterator for &'a Actions {
type IntoIter = slice::Iter<'a, Action>;
type Item = &'a Action;
fn into_iter(self) -> Self::IntoIter { self.0.iter() }
}
pub fn deserialize_actions<'de, const L: u8, D>(deserializer: D) -> Result<Actions, D::Error>
where
D: Deserializer<'de>,
{
let Some(layer) = Layer::from_repr(L) else {
return Err(de::Error::custom(format!("invalid keymap layer const: {L}")));
};
let mut actions: Vec<Action> = OneOrMany::<DisplayFromStr>::deserialize_as(deserializer)?;
for action in &mut actions {
action.source = Source::Key;
if action.layer == Layer::Null {
action.layer = layer;
}
}
Ok(Actions(actions))
}

View file

@ -1,4 +1,4 @@
yazi_macro::mod_flat!(action cmd cow de de_owned event);
yazi_macro::mod_flat!(action action_cow actions cmd de de_owned event);
pub type Replier = tokio::sync::mpsc::UnboundedSender<anyhow::Result<crate::data::Data>>;

View file

@ -0,0 +1,9 @@
use mlua::{Error::UserDataDestructed, Value};
pub(super) fn is_alive(v: &Value) -> bool {
if let Value::UserData(ud) = v {
!matches!(ud.borrow::<()>(), Err(UserDataDestructed))
} else {
true
}
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(sequence string traits);
yazi_macro::mod_flat!(cache sequence string traits);

View file

@ -1,8 +1,8 @@
use std::marker::PhantomData;
use mlua::{ExternalError, FromLua, Lua, Table, Value};
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, Value};
use crate::{mlua::SequenceIter, toml::DeserializeOverWith};
use crate::{mlua::{SequenceIter, is_alive}, toml::DeserializeOverWith};
// --- DeserializeOverLua
pub trait DeserializeOverLua: DeserializeOverWith {
@ -29,3 +29,67 @@ impl LuaTableExt for Table {
}
}
}
// --- UserDataFieldsExt
pub trait UserDataFieldsExt<S: UserData + 'static>: UserDataFields<S> {
fn add_cached_field<R: IntoLua + 'static>(
&mut self,
key: &'static str,
compute: fn(&Lua, &S) -> mlua::Result<R>,
) -> &mut Self {
self.add_field_function_get(key, move |lua, ud| {
match ud.named_user_value::<Option<Value>>(key)? {
Some(v) if is_alive(&v) => Ok(v),
_ => {
let v = compute(lua, &*ud.borrow::<S>()?)?.into_lua(lua)?;
ud.set_named_user_value(key, &v)?;
Ok(v)
}
}
});
self
}
fn add_cached_field_mut<R: IntoLua + 'static>(
&mut self,
key: &'static str,
compute: fn(&Lua, &mut S) -> mlua::Result<R>,
) -> &mut Self {
self.add_field_function_get(key, move |lua, ud| {
match ud.named_user_value::<Option<Value>>(key)? {
Some(Value::Error(e)) => Err(*e),
Some(v) if is_alive(&v) => Ok(v),
_ => {
let v = match compute(lua, &mut *ud.borrow_mut::<S>()?).and_then(|r| r.into_lua(lua)) {
Ok(v) => v,
Err(e) => Value::Error(Box::new(e)),
};
ud.set_named_user_value(key, &v)?;
Ok(v)
}
}
});
self
}
fn add_static_field<R: IntoLua + 'static>(
&mut self,
key: &'static str,
compute: fn(&Lua, &S) -> mlua::Result<R>,
) -> &mut Self {
self.add_field_function_get(key, move |lua, ud| {
match ud.named_user_value::<Option<Value>>(key)? {
Some(v) => Ok(v),
_ => {
let v = compute(lua, &*ud.borrow::<S>()?)?.into_lua(lua)?;
ud.set_named_user_value(key, &v)?;
Ok(v)
}
}
});
self
}
}
impl<S: UserData + 'static, F: UserDataFields<S>> UserDataFieldsExt<S> for F {}