perf!: cacheable iterator (#2997)

This commit is contained in:
三咲雅 misaki masa 2025-07-18 07:48:00 +08:00 committed by GitHub
parent 5f8a1496fe
commit 0fd74ae44c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 414 additions and 303 deletions

2
Cargo.lock generated
View file

@ -3670,6 +3670,7 @@ dependencies = [
"yazi-boot",
"yazi-fs",
"yazi-macro",
"yazi-parser",
"yazi-shared",
]
@ -3772,7 +3773,6 @@ dependencies = [
"yazi-binding",
"yazi-boot",
"yazi-config",
"yazi-dds",
"yazi-fs",
"yazi-macro",
"yazi-shared",

View file

@ -3,9 +3,10 @@ use std::{ffi::OsString, mem, path::{MAIN_SEPARATOR_STR, Path, PathBuf}};
use anyhow::Result;
use tokio::fs;
use yazi_fs::{CWD, expand_path};
use yazi_macro::{act, emit, render, succ};
use yazi_macro::{act, render, succ};
use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt};
use yazi_shared::{event::{Cmd, Data}, natsort};
use yazi_proxy::CmpProxy;
use yazi_shared::{event::Data, natsort};
use crate::{Actor, Ctx};
@ -55,13 +56,7 @@ impl Actor for Trigger {
cache.sort_unstable_by(|a, b| {
natsort(a.name.as_encoded_bytes(), b.name.as_encoded_bytes(), false)
});
emit!(Call(
Cmd::new("cmp:show")
.with_any("cache", cache)
.with_any("cache-name", parent)
.with("word", word)
.with("ticket", ticket)
));
CmpProxy::show(ShowOpt { cache, cache_name: parent, word: word.into(), ticket });
}
Ok::<_, anyhow::Error>(())

View file

@ -4,11 +4,10 @@ use anyhow::Result;
use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg;
use yazi_fs::FilterCase;
use yazi_macro::{emit, succ};
use yazi_macro::succ;
use yazi_parser::tab::FilterOpt;
use yazi_proxy::InputProxy;
use yazi_shared::{Debounce, errors::InputError, event::{Cmd, Data}};
use yazi_proxy::{InputProxy, MgrProxy};
use yazi_shared::{Debounce, errors::InputError, event::Data};
use crate::{Actor, Ctx};
@ -30,12 +29,7 @@ impl Actor for Filter {
let done = result.is_ok();
let (Ok(s) | Err(InputError::Typed(s))) = result else { continue };
emit!(Call(
Cmd::args("mgr:filter_do", [s])
.with("smart", opt.case == FilterCase::Smart)
.with("insensitive", opt.case == FilterCase::Insensitive)
.with("done", done)
));
MgrProxy::filter_do(FilterOpt { query: s.into(), case: opt.case, done });
}
});
succ!();

View file

@ -4,11 +4,10 @@ use anyhow::Result;
use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg;
use yazi_fs::FilterCase;
use yazi_macro::{emit, succ};
use yazi_parser::tab::FindOpt;
use yazi_proxy::InputProxy;
use yazi_shared::{Debounce, errors::InputError, event::{Cmd, Data}};
use yazi_macro::succ;
use yazi_parser::tab::{FindDoOpt, FindOpt};
use yazi_proxy::{InputProxy, MgrProxy};
use yazi_shared::{Debounce, errors::InputError, event::Data};
use crate::{Actor, Ctx};
@ -27,12 +26,7 @@ impl Actor for Find {
pin!(rx);
while let Some(Ok(s)) | Some(Err(InputError::Typed(s))) = rx.next().await {
emit!(Call(
Cmd::args("mgr:find_do", [s])
.with("previous", opt.prev)
.with("smart", opt.case == FilterCase::Smart)
.with("insensitive", opt.case == FilterCase::Insensitive)
));
MgrProxy::find_do(FindDoOpt { query: s.into(), prev: opt.prev, case: opt.case });
}
});
succ!();

View file

@ -9,7 +9,7 @@ use crate::{Actor, Ctx};
pub struct UpdateYanked;
impl Actor for UpdateYanked {
type Options = UpdateYankedOpt;
type Options = UpdateYankedOpt<'static>;
const NAME: &'static str = "update_yanked";
@ -18,7 +18,7 @@ impl Actor for UpdateYanked {
succ!();
}
cx.mgr.yanked = Yanked::new(opt.cut, opt.urls);
cx.mgr.yanked = Yanked::new(opt.cut, opt.urls.into_owned());
succ!(render!());
}
}

68
yazi-binding/src/iter.rs Normal file
View file

@ -0,0 +1,68 @@
use mlua::{AnyUserData, ExternalError, FromLua, IntoLua, IntoLuaMulti, Lua, MetaMethod, UserData, UserDataMethods, UserDataRefMut, Value};
pub struct Iter<I: Iterator<Item = T>, T> {
iter: I,
len: Option<usize>,
count: usize,
cache: Vec<Value>,
}
impl<I, T> Iter<I, T>
where
I: Iterator<Item = T> + 'static,
T: IntoLua + 'static,
{
#[inline]
pub fn new(iter: I, len: Option<usize>) -> Self { Self { iter, len, count: 0, cache: vec![] } }
}
impl<I, T> Iter<I, T>
where
I: Iterator<Item = T> + 'static,
T: FromLua + 'static,
{
#[inline]
pub fn into_iter(self, lua: &Lua) -> impl Iterator<Item = mlua::Result<T>> {
self
.cache
.into_iter()
.map(|cached| T::from_lua(cached, lua))
.chain(self.iter.map(|rest| Ok(rest)))
}
}
impl<I, T> UserData for Iter<I, T>
where
I: Iterator<Item = T> + 'static,
T: IntoLua + 'static,
{
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Len, |_, me, ()| {
if let Some(len) = me.len {
Ok(len)
} else {
Err(format!("Length is unknown for {}", std::any::type_name::<Self>()).into_lua_err())
}
});
methods.add_meta_function(MetaMethod::Pairs, |lua, ud: AnyUserData| {
let iter = lua.create_function(|lua, mut me: UserDataRefMut<Self>| {
if let Some(next) = me.cache.get(me.count).cloned() {
me.count += 1;
(me.count, next).into_lua_multi(lua)
} else if let Some(next) = me.iter.next() {
let value = next.into_lua(lua)?;
me.cache.push(value.clone());
me.count += 1;
(me.count, value).into_lua_multi(lua)
} else {
().into_lua_multi(lua)
}
})?;
ud.borrow_mut::<Self>()?.count = 0;
Ok((iter, ud))
});
}
}

View file

@ -4,4 +4,4 @@ mod macros;
yazi_macro::mod_pub!(elements);
yazi_macro::mod_flat!(cha color composer error file icon id permit runtime stage style url urn);
yazi_macro::mod_flat!(cha color composer error file icon id iter permit runtime stage style url urn utils);

10
yazi-binding/src/utils.rs Normal file
View file

@ -0,0 +1,10 @@
use mlua::{IntoLua, Lua, Table, Value};
pub fn get_metatable(lua: &Lua, value: impl IntoLua) -> mlua::Result<Table> {
let (_, mt): (Value, Table) = unsafe {
lua.exec_raw(value.into_lua(lua)?, |state| {
mlua::ffi::lua_getmetatable(state, -1);
})
}?;
Ok(mt)
}

View file

@ -3,10 +3,9 @@ use std::{sync::Arc, time::Duration};
use parking_lot::Mutex;
use tokio::{task::JoinHandle, time::sleep};
use yazi_adapter::Dimension;
use yazi_macro::emit;
use yazi_parser::app::TasksProgress;
use yazi_proxy::AppProxy;
use yazi_scheduler::{Ongoing, Scheduler, TaskSummary};
use yazi_shared::event::Cmd;
use super::{TASKS_BORDER, TASKS_PADDING, TASKS_PERCENT};
@ -33,7 +32,7 @@ impl Tasks {
let new = ongoing.lock().progress();
if last != new {
last = new;
emit!(Call(Cmd::new("app:update_progress").with_any("progress", new)));
AppProxy::update_progress(new);
}
}
});

View file

@ -17,6 +17,7 @@ yazi-binding = { path = "../yazi-binding", version = "25.6.11" }
yazi-boot = { path = "../yazi-boot", version = "25.6.11" }
yazi-fs = { path = "../yazi-fs", version = "25.6.11" }
yazi-macro = { path = "../yazi-macro", version = "25.6.11" }
yazi-parser = { path = "../yazi-parser", version = "25.6.11" }
yazi-shared = { path = "../yazi-shared", version = "25.6.11" }
# External dependencies

View file

@ -0,0 +1,7 @@
use yazi_shared::Source;
pub struct BeforeQuit {
// FIXME
// pub opts: BeforeQuitOpt,
pub source: Source,
}

View file

@ -47,9 +47,9 @@ impl Body<'static> {
})
}
pub fn from_lua(kind: &str, value: Value) -> mlua::Result<Self> {
pub fn from_lua(lua: &Lua, kind: &str, value: Value) -> mlua::Result<Self> {
Self::validate(kind).into_lua_err()?;
BodyCustom::from_lua(kind, value)
BodyCustom::from_lua(lua, kind, value)
}
pub fn validate(kind: &str) -> Result<()> {

View file

@ -41,6 +41,7 @@ impl IntoLua for BodyBulk<'static> {
}
// --- Iterator
// TODO: use `yazi_binding::Iter` instead
pub struct BodyBulkIter {
pub inner: hash_map::IntoIter<Cow<'static, Url>, Cow<'static, Url>>,
}

View file

@ -16,8 +16,8 @@ impl BodyCustom {
Ok(Self { kind: kind.to_owned(), data: serde_json::from_str(data)? }.into())
}
pub fn from_lua(kind: &str, data: Value) -> mlua::Result<Body<'static>> {
Ok(Self { kind: kind.to_owned(), data: Sendable::value_to_data(data)? }.into())
pub fn from_lua(lua: &Lua, kind: &str, data: Value) -> mlua::Result<Body<'static>> {
Ok(Self { kind: kind.to_owned(), data: Sendable::value_to_data(lua, data)? }.into())
}
}

View file

@ -1,5 +1,5 @@
#![allow(clippy::module_inception)]
yazi_macro::mod_flat!(
body bulk bye cd custom delete hey hi hover load mount r#move rename tab trash yank
before_quit body bulk bye cd custom delete hey hi hover load mount r#move rename tab trash yank
);

View file

@ -1,28 +1,24 @@
use std::{borrow::Cow, collections::HashSet};
use mlua::{IntoLua, Lua, MetaMethod, UserData, Value};
use mlua::{IntoLua, Lua, Value};
use serde::{Deserialize, Serialize};
use yazi_parser::mgr::UpdateYankedOpt;
use yazi_shared::url::Url;
use super::Body;
#[derive(Debug, Serialize, Deserialize)]
pub struct BodyYank<'a> {
pub cut: bool,
pub urls: Cow<'a, HashSet<Url>>,
#[serde(skip)]
dummy: bool,
}
pub struct BodyYank<'a>(UpdateYankedOpt<'a>);
impl<'a> BodyYank<'a> {
pub fn borrowed(cut: bool, urls: &'a HashSet<Url>) -> Body<'a> {
Self { cut, urls: Cow::Borrowed(urls), dummy: false }.into()
Self(UpdateYankedOpt { cut, urls: Cow::Borrowed(urls) }).into()
}
}
impl BodyYank<'static> {
pub fn owned(cut: bool, _: &HashSet<Url>) -> Body<'static> {
Self { cut, urls: Default::default(), dummy: true }.into()
Self(UpdateYankedOpt { cut, urls: Default::default() }).into()
}
}
@ -31,36 +27,5 @@ impl<'a> From<BodyYank<'a>> for Body<'a> {
}
impl IntoLua for BodyYank<'static> {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
if let Some(Cow::Owned(urls)) = Some(self.urls).filter(|_| !self.dummy) {
BodyYankIter { cut: self.cut, urls: urls.into_iter().collect() }.into_lua(lua)
} else {
lua.create_table()?.into_lua(lua)
}
}
}
// --- Iterator
#[derive(Clone)]
pub struct BodyYankIter {
pub cut: bool,
pub urls: Vec<Url>,
}
impl UserData for BodyYankIter {
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("cut", |_, me| Ok(me.cut));
}
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.urls.len()));
methods.add_meta_method(MetaMethod::Index, |_, me, idx: usize| {
Ok(if idx > me.urls.len() || idx == 0 {
None
} else {
Some(yazi_binding::Url::new(me.urls[idx - 1].clone()))
})
});
}
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { self.0.into_lua(lua) }
}

View file

@ -7,7 +7,7 @@ use yazi_shared::{event::{Data, DataKey}, replace_cow};
pub struct Sendable;
impl Sendable {
pub fn value_to_data(value: Value) -> mlua::Result<Data> {
pub fn value_to_data(lua: &Lua, value: Value) -> mlua::Result<Data> {
Ok(match value {
Value::Nil => Data::Nil,
Value::Boolean(b) => Data::Boolean(b),
@ -30,7 +30,7 @@ impl Sendable {
if k == DataKey::Integer(i) {
i += 1;
}
map.insert(k, Self::value_to_data(v)?);
map.insert(k, Self::value_to_data(lua, v)?);
}
if map.len() == i as usize - 1 {
@ -54,8 +54,8 @@ impl Sendable {
Some(t) if t == TypeId::of::<yazi_fs::FilesOp>() => {
Data::Any(Box::new(ud.take::<yazi_fs::FilesOp>()?))
}
Some(t) if t == TypeId::of::<super::body::BodyYankIter>() => {
Data::Any(Box::new(ud.take::<super::body::BodyYankIter>()?))
Some(t) if t == TypeId::of::<yazi_parser::mgr::UpdateYankedIter>() => {
Data::Any(Box::new(ud.take::<yazi_parser::mgr::UpdateYankedIter>()?.into_opt(lua)?))
}
_ => Err(format!("unsupported userdata included: {ud:?}").into_lua_err())?,
},
@ -83,13 +83,15 @@ impl Sendable {
}
Data::Url(u) => yazi_binding::Url::new(u).into_lua(lua)?,
Data::Urn(u) => yazi_binding::Urn::new(u).into_lua(lua)?,
Data::Any(a) => Value::UserData(if a.is::<yazi_fs::FilesOp>() {
lua.create_any_userdata(*a.downcast::<yazi_fs::FilesOp>().unwrap())?
} else if a.is::<super::body::BodyYankIter>() {
lua.create_userdata(*a.downcast::<super::body::BodyYankIter>().unwrap())?
} else {
Err("unsupported Data::Any included".into_lua_err())?
}),
Data::Any(a) => {
if a.is::<yazi_fs::FilesOp>() {
lua.create_any_userdata(*a.downcast::<yazi_fs::FilesOp>().unwrap())?.into_lua(lua)?
} else if a.is::<yazi_parser::mgr::UpdateYankedOpt>() {
a.downcast::<yazi_parser::mgr::UpdateYankedOpt>().unwrap().into_lua(lua)?
} else {
Err("unsupported Data::Any included".into_lua_err())?
}
}
data => Self::data_to_value_ref(lua, &data)?,
})
}
@ -120,28 +122,30 @@ impl Sendable {
Data::Url(u) => yazi_binding::Url::new(u.clone()).into_lua(lua)?,
Data::Urn(u) => yazi_binding::Urn::new(u.clone()).into_lua(lua)?,
Data::Bytes(b) => Value::String(lua.create_string(b)?),
Data::Any(a) => Value::UserData(if let Some(t) = a.downcast_ref::<yazi_fs::FilesOp>() {
lua.create_any_userdata(t.clone())?
} else if let Some(t) = a.downcast_ref::<super::body::BodyYankIter>() {
lua.create_userdata(t.clone())?
} else {
Err("unsupported Data::Any included".into_lua_err())?
}),
Data::Any(a) => {
if let Some(t) = a.downcast_ref::<yazi_fs::FilesOp>() {
lua.create_any_userdata(t.clone())?.into_lua(lua)?
} else if let Some(t) = a.downcast_ref::<yazi_parser::mgr::UpdateYankedOpt>() {
t.clone().into_lua(lua)?
} else {
Err("unsupported Data::Any included".into_lua_err())?
}
}
})
}
pub fn table_to_args(t: Table) -> mlua::Result<HashMap<DataKey, Data>> {
pub fn table_to_args(lua: &Lua, t: Table) -> mlua::Result<HashMap<DataKey, Data>> {
let mut args = HashMap::with_capacity(t.raw_len());
for pair in t.pairs::<Value, Value>() {
let (k, v) = pair?;
match k {
Value::Integer(i) if i > 0 => {
args.insert(DataKey::Integer(i - 1), Self::value_to_data(v)?);
args.insert(DataKey::Integer(i - 1), Self::value_to_data(lua, v)?);
}
Value::String(s) => {
args.insert(
DataKey::String(Cow::Owned(s.to_str()?.replace('_', "-"))),
Self::value_to_data(v)?,
Self::value_to_data(lua, v)?,
);
}
_ => return Err("invalid key in Cmd".into_lua_err()),
@ -182,8 +186,8 @@ impl Sendable {
data.into_iter().map(|d| Self::data_to_value(lua, d)).collect()
}
pub fn values_to_list(values: MultiValue) -> mlua::Result<Vec<Data>> {
values.into_iter().map(Self::value_to_data).collect()
pub fn values_to_list(lua: &Lua, values: MultiValue) -> mlua::Result<Vec<Data>> {
values.into_iter().map(|v| Self::value_to_data(lua, v)).collect()
}
}

View file

@ -1,19 +1,22 @@
use anyhow::Result;
use anyhow::{Result, bail};
use mlua::IntoLua;
use tracing::error;
use yazi_binding::runtime_mut;
use yazi_dds::{LOCAL, REMOTE};
use yazi_dds::{LOCAL, Payload, REMOTE};
use yazi_macro::succ;
use yazi_parser::app::AcceptPayload;
use yazi_plugin::LUA;
use yazi_shared::event::Data;
use yazi_shared::event::{CmdCow, Data};
use crate::{app::App, lives::Lives};
impl App {
pub(crate) fn accept_payload(&mut self, opt: AcceptPayload) -> Result<Data> {
let kind = opt.payload.body.kind().to_owned();
let lock = if opt.payload.receiver == 0 || opt.payload.receiver != opt.payload.sender {
pub(crate) fn accept_payload(&mut self, mut c: CmdCow) -> Result<Data> {
let Some(payload) = c.take_any2::<Payload>("payload").transpose()? else {
bail!("'payload' is required for accept_payload");
};
let kind = payload.body.kind().to_owned();
let lock = if payload.receiver == 0 || payload.receiver != payload.sender {
REMOTE.read()
} else {
LOCAL.read()
@ -23,7 +26,7 @@ impl App {
drop(lock);
succ!(Lives::scope(&self.core, || {
let body = opt.payload.body.into_lua(&LUA)?;
let body = payload.body.into_lua(&LUA)?;
for (id, cb) in handlers {
runtime_mut!(LUA)?.push(&id);
if let Err(e) = cb.call::<()>(body.clone()) {

View file

@ -1,27 +0,0 @@
use mlua::{AnyUserData, UserData};
use super::Lives;
pub(super) struct Iter<I: Iterator<Item = T>, T> {
inner: I,
count: usize,
}
impl<I: Iterator<Item = T> + 'static, T: 'static> Iter<I, T> {
#[inline]
pub(super) fn make(inner: I) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner, count: 0 })
}
}
impl<I: Iterator<Item = T>, T> Iterator for Iter<I, T> {
type Item = (usize, T);
fn next(&mut self) -> Option<Self::Item> {
let next = self.inner.next()?;
self.count += 1;
Some((self.count, next))
}
}
impl<I: Iterator<Item = T>, T> UserData for Iter<I, T> {}

View file

@ -1,3 +1,3 @@
#![allow(clippy::module_inception)]
yazi_macro::mod_flat!(core file files filter finder folder iter lives mode preference preview ptr selected tab tabs tasks yanked);
yazi_macro::mod_flat!(core file files filter finder folder lives mode preference preview ptr selected tab tabs tasks yanked);

View file

@ -1,45 +1,20 @@
use std::ops::Deref;
use indexmap::IndexMap;
use mlua::AnyUserData;
use indexmap::{IndexMap, map::Keys};
use mlua::{AnyUserData, IntoLuaMulti, MetaMethod, UserData, UserDataMethods, UserDataRefMut};
use yazi_binding::Url;
use super::{Iter, Lives, PtrCell};
use super::Lives;
use crate::lives::PtrCell;
#[derive(Clone, Copy)]
pub(super) struct Selected {
inner: PtrCell<IndexMap<yazi_shared::url::Url, u64>>,
}
impl Deref for Selected {
type Target = IndexMap<yazi_shared::url::Url, u64>;
fn deref(&self) -> &Self::Target { &self.inner }
}
pub(super) struct Selected;
impl Selected {
#[inline]
pub(super) fn make(inner: &IndexMap<yazi_shared::url::Url, u64>) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into() })
}
}
impl UserData for Selected {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
methods.add_meta_method(MetaMethod::Pairs, |lua, me, ()| {
let iter = lua.create_function(
|lua, mut iter: UserDataRefMut<Iter<Keys<yazi_shared::url::Url, u64>, _>>| {
if let Some(next) = iter.next() {
(next.0, Url::new(next.1.clone())).into_lua_multi(lua)
} else {
().into_lua_multi(lua)
}
},
)?;
Ok((iter, Iter::make(me.inner.as_static().keys())))
});
let inner = PtrCell::from(inner);
Lives::scoped_userdata(yazi_binding::Iter::new(
inner.as_static().keys().cloned().map(yazi_binding::Url::new),
Some(inner.len()),
))
}
}

View file

@ -1,12 +1,13 @@
use std::{collections::hash_set, ops::Deref};
use std::ops::Deref;
use mlua::{AnyUserData, IntoLuaMulti, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRefMut};
use yazi_binding::Url;
use mlua::{AnyUserData, MetaMethod, MultiValue, ObjectLike, UserData, UserDataFields, UserDataMethods};
use yazi_binding::{Iter, get_metatable};
use super::{Iter, Lives, PtrCell};
use super::{Lives, PtrCell};
pub(super) struct Yanked {
inner: PtrCell<yazi_core::mgr::Yanked>,
iter: AnyUserData,
}
impl Deref for Yanked {
@ -18,7 +19,15 @@ impl Deref for Yanked {
impl Yanked {
#[inline]
pub(super) fn make(inner: &yazi_core::mgr::Yanked) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into() })
let inner = PtrCell::from(inner);
Lives::scoped_userdata(Self {
inner,
iter: Lives::scoped_userdata(Iter::new(
inner.as_static().iter().cloned().map(yazi_binding::Url::new),
Some(inner.len()),
))?,
})
}
}
@ -30,18 +39,9 @@ impl UserData for Yanked {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
methods.add_meta_method(MetaMethod::Pairs, |lua, me, ()| {
let iter = lua.create_function(
|lua, mut iter: UserDataRefMut<Iter<hash_set::Iter<yazi_shared::url::Url>, _>>| {
if let Some(next) = iter.next() {
(next.0, Url::new(next.1.clone())).into_lua_multi(lua)
} else {
().into_lua_multi(lua)
}
},
)?;
Ok((iter, Iter::make(me.inner.as_static().iter())))
methods.add_meta_function(MetaMethod::Pairs, |lua, ud: AnyUserData| {
let me = ud.borrow::<Self>()?;
get_metatable(lua, &me.iter)?.call_function::<MultiValue>(MetaMethod::Pairs.name(), ud)
});
}
}

View file

@ -41,7 +41,7 @@ impl Display for Filter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.raw) }
}
#[derive(Default, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum FilterCase {
Smart,
#[default]

View file

@ -12,7 +12,6 @@ repository = "https://github.com/sxyazi/yazi"
yazi-binding = { path = "../yazi-binding", version = "25.6.11" }
yazi-boot = { path = "../yazi-boot", version = "25.6.11" }
yazi-config = { path = "../yazi-config", version = "25.6.11" }
yazi-dds = { path = "../yazi-dds", version = "25.6.11" }
yazi-fs = { path = "../yazi-fs", version = "25.6.11" }
yazi-macro = { path = "../yazi-macro", version = "25.6.11" }
yazi-shared = { path = "../yazi-shared", version = "25.6.11" }

View file

@ -1,19 +0,0 @@
use anyhow::bail;
use yazi_dds::Payload;
use yazi_shared::event::CmdCow;
pub struct AcceptPayload {
pub payload: Payload<'static>,
}
impl TryFrom<CmdCow> for AcceptPayload {
type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
let Some(payload) = c.take_any("payload") else {
bail!("Invalid 'payload' in AcceptPayload");
};
Ok(Self { payload })
}
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(accept_payload deprecate mouse notify plugin stop update_progress);
yazi_macro::mod_flat!(deprecate mouse notify plugin stop update_progress);

View file

@ -1,6 +1,6 @@
use std::{ffi::OsString, path::{MAIN_SEPARATOR_STR, PathBuf}};
use yazi_shared::{Id, SStr, event::{Cmd, CmdCow}};
use yazi_shared::{Id, SStr, event::CmdCow};
#[derive(Default)]
pub struct ShowOpt {
@ -10,21 +10,23 @@ pub struct ShowOpt {
pub ticket: Id,
}
impl From<CmdCow> for ShowOpt {
fn from(mut c: CmdCow) -> Self {
Self {
impl TryFrom<CmdCow> for ShowOpt {
type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
if let Some(opt) = c.take_any2("opt") {
return opt;
}
Ok(Self {
cache: c.take_any("cache").unwrap_or_default(),
cache_name: c.take_any("cache-name").unwrap_or_default(),
word: c.take_str("word").unwrap_or_default(),
ticket: c.id("ticket").unwrap_or_default(),
}
})
}
}
impl From<Cmd> for ShowOpt {
fn from(c: Cmd) -> Self { Self::from(CmdCow::from(c)) }
}
// --- Item
#[derive(Debug, Clone)]
pub struct CmpItem {

View file

@ -1,6 +1,8 @@
use mlua::{IntoLua, Lua, LuaSerdeExt, Value};
use serde::{Deserialize, Serialize};
use yazi_shared::event::{CmdCow, Data, EventQuit};
#[derive(Default)]
#[derive(Default, Serialize, Deserialize)]
pub struct QuitOpt {
pub code: i32,
pub no_cwd_file: bool,
@ -20,3 +22,7 @@ impl From<QuitOpt> for EventQuit {
EventQuit { code: value.code, no_cwd_file: value.no_cwd_file, ..Default::default() }
}
}
impl IntoLua for QuitOpt {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.to_value(&self) }
}

View file

@ -1,23 +1,76 @@
use std::collections::HashSet;
use std::{borrow::Cow, collections::HashSet};
use anyhow::bail;
use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, MultiValue, ObjectLike, UserData, UserDataFields, UserDataMethods, Value};
use serde::{Deserialize, Serialize};
use yazi_binding::get_metatable;
use yazi_shared::{event::CmdCow, url::Url};
#[derive(Default)]
pub struct UpdateYankedOpt {
type Iter = yazi_binding::Iter<
std::iter::Map<std::collections::hash_set::IntoIter<Url>, fn(Url) -> yazi_binding::Url>,
yazi_binding::Url,
>;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct UpdateYankedOpt<'a> {
pub cut: bool,
pub urls: HashSet<Url>,
pub urls: Cow<'a, HashSet<Url>>,
}
impl TryFrom<CmdCow> for UpdateYankedOpt {
impl TryFrom<CmdCow> for UpdateYankedOpt<'_> {
type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
// TODO: remove `BodyYankIter`
if let Some(iter) = c.take_any::<yazi_dds::body::BodyYankIter>("urls") {
Ok(Self { urls: iter.urls.into_iter().collect(), cut: iter.cut })
if let Some(opt) = c.take_any2("opt") {
opt
} else {
bail!("Invalid 'urls' argument in UpdateYankedOpt");
bail!("'opt' is required for UpdateYankedOpt");
}
}
}
impl IntoLua for UpdateYankedOpt<'static> {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
let len = self.urls.len();
let iter = Iter::new(self.urls.into_owned().into_iter().map(yazi_binding::Url::new), Some(len));
UpdateYankedIter { cut: self.cut, len, inner: lua.create_userdata(iter)? }.into_lua(lua)
}
}
// --- Iter
pub struct UpdateYankedIter {
cut: bool,
len: usize,
inner: AnyUserData,
}
impl UpdateYankedIter {
pub fn into_opt(self, lua: &Lua) -> mlua::Result<UpdateYankedOpt<'static>> {
Ok(UpdateYankedOpt {
cut: self.cut,
urls: Cow::Owned(
self
.inner
.take::<Iter>()?
.into_iter(lua)
.map(|result| result.map(Into::into))
.collect::<mlua::Result<_>>()?,
),
})
}
}
impl UserData for UpdateYankedIter {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("cut", |_, me| Ok(me.cut));
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len));
methods.add_meta_function(MetaMethod::Pairs, |lua, ud: AnyUserData| {
let me = ud.borrow::<Self>()?;
get_metatable(lua, &me.inner)?.call_function::<MultiValue>(MetaMethod::Pairs.name(), ud)
});
}
}

View file

@ -8,12 +8,18 @@ pub struct FilterOpt {
pub done: bool,
}
impl From<CmdCow> for FilterOpt {
fn from(mut c: CmdCow) -> Self {
Self {
impl TryFrom<CmdCow> for FilterOpt {
type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
if let Some(opt) = c.take_any2("opt") {
return opt;
}
Ok(Self {
query: c.take_first_str().unwrap_or_default(),
case: FilterCase::from(&*c),
done: c.bool("done"),
}
})
}
}

View file

@ -1,21 +0,0 @@
use anyhow::bail;
use yazi_fs::FilterCase;
use yazi_shared::{SStr, event::CmdCow};
pub struct FindDoOpt {
pub query: SStr,
pub prev: bool,
pub case: FilterCase,
}
impl TryFrom<CmdCow> for FindDoOpt {
type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
let Some(query) = c.take_first_str() else {
bail!("'query' is required for FindOpt");
};
Ok(Self { query, prev: c.bool("previous"), case: FilterCase::from(&*c) })
}
}

View file

@ -1 +1,25 @@
use anyhow::bail;
use yazi_fs::FilterCase;
use yazi_shared::{SStr, event::CmdCow};
pub struct FindDoOpt {
pub query: SStr,
pub prev: bool,
pub case: FilterCase,
}
impl TryFrom<CmdCow> for FindDoOpt {
type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
if let Some(opt) = c.take_any2("opt") {
return opt;
}
let Some(query) = c.take_first_str() else {
bail!("'query' is required for FindDoOpt");
};
Ok(Self { query, prev: c.bool("previous"), case: FilterCase::from(&*c) })
}
}

View file

@ -5,7 +5,6 @@ yazi_macro::mod_flat!(
enter
escape
filter
filter_do
find
find_arrow
find_do

View file

@ -11,6 +11,10 @@ impl TryFrom<CmdCow> for UpdatePeekedOpt {
type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
if let Some(opt) = c.take_any2("opt") {
return opt;
}
let Some(lock) = c.take_any("lock") else {
bail!("Invalid 'lock' argument in UpdatePeekedOpt");
};

View file

@ -11,6 +11,10 @@ impl TryFrom<CmdCow> for UpdateSpottedOpt {
type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
if let Some(opt) = c.take_any2("opt") {
return opt;
}
let Some(lock) = c.take_any("lock") else {
bail!("Invalid 'lock' argument in UpdateSpottedOpt");
};

View file

@ -10,6 +10,10 @@ impl TryFrom<CmdCow> for ShowOpt {
type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
if let Some(opt) = c.take_any2("opt") {
return opt;
}
Ok(Self { cands: c.take_any("candidates").unwrap_or_default(), silent: c.bool("silent") })
}
}

View file

@ -1,6 +1,6 @@
local function setup(_, opts)
if opts.sync_yanked then
ps.sub_remote("@yank", function(body) ya.emit("update_yanked", { cut = body.cut, urls = body }) end)
ps.sub_remote("@yank", function(opt) ya.emit("update_yanked", { opt = opt }) end)
end
end

View file

@ -6,14 +6,15 @@ pub struct Pubsub;
impl Pubsub {
pub(super) fn r#pub(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, (kind, value): (mlua::String, Value)| {
yazi_dds::Pubsub::r#pub(Body::from_lua(&kind.to_str()?, value)?).into_lua_err()
lua.create_function(|lua, (kind, value): (mlua::String, Value)| {
yazi_dds::Pubsub::r#pub(Body::from_lua(lua, &kind.to_str()?, value)?).into_lua_err()
})
}
pub(super) fn pub_to(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, (receiver, kind, value): (Id, mlua::String, Value)| {
yazi_dds::Pubsub::pub_to(*receiver, Body::from_lua(&kind.to_str()?, value)?).into_lua_err()
lua.create_function(|lua, (receiver, kind, value): (Id, mlua::String, Value)| {
yazi_dds::Pubsub::pub_to(*receiver, Body::from_lua(lua, &kind.to_str()?, value)?)
.into_lua_err()
})
}

View file

@ -17,18 +17,18 @@ impl Utils {
}
pub(super) fn emit(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, (name, args): (String, Table)| {
lua.create_function(|lua, (name, args): (String, Table)| {
let mut cmd = Cmd::new_or(name, Layer::Mgr)?;
cmd.args = Sendable::table_to_args(args)?;
cmd.args = Sendable::table_to_args(lua, args)?;
Ok(emit!(Call(cmd)))
})
}
pub(super) fn mgr_emit(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, (name, args): (String, Table)| {
lua.create_function(|lua, (name, args): (String, Table)| {
emit!(Call(Cmd {
name: name.into(),
args: Sendable::table_to_args(args)?,
args: Sendable::table_to_args(lua, args)?,
layer: Layer::Mgr,
}));
Ok(())

View file

@ -5,8 +5,8 @@ use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream;
use yazi_binding::{deprecate, elements::{Line, Pos, Text}};
use yazi_config::{keymap::{Chord, Key}, popup::{ConfirmCfg, InputCfg}};
use yazi_macro::emit;
use yazi_proxy::{AppProxy, ConfirmProxy, InputProxy};
use yazi_parser::which::ShowOpt;
use yazi_proxy::{AppProxy, ConfirmProxy, InputProxy, WhichProxy};
use yazi_shared::{Debounce, event::Cmd};
use super::Utils;
@ -29,11 +29,7 @@ impl Utils {
}
drop(tx);
emit!(Call(
Cmd::new("which:show")
.with_any("candidates", cands)
.with("silent", t.raw_get::<bool>("silent").unwrap_or_default())
));
WhichProxy::show(ShowOpt { cands, silent: t.raw_get("silent").unwrap_or_default() });
Ok(rx.recv().await.map(|idx| idx + 1))
})

View file

@ -1,9 +1,9 @@
use mlua::{AnyUserData, ExternalError, Function, IntoLuaMulti, Lua, Table, Value};
use yazi_binding::{Error, elements::{Area, Renderable, Text}};
use yazi_config::YAZI;
use yazi_macro::emit;
use yazi_parser::tab::PreviewLock;
use yazi_shared::{errors::PeekError, event::Cmd};
use yazi_parser::tab::{PreviewLock, UpdatePeekedOpt};
use yazi_proxy::MgrProxy;
use yazi_shared::errors::PeekError;
use super::Utils;
use crate::external::Highlighter;
@ -29,7 +29,7 @@ impl Utils {
scroll: Default::default(),
})];
emit!(Call(Cmd::new("mgr:update_peeked").with_any("lock", lock)));
MgrProxy::update_peeked(UpdatePeekedOpt { lock });
().into_lua_multi(&lua)
})
}
@ -56,7 +56,7 @@ impl Utils {
_ => Err("preview widget must be a renderable element or a table of them".into_lua_err())?,
};
emit!(Call(Cmd::new("mgr:update_peeked").with_any("lock", lock)));
MgrProxy::update_peeked(UpdatePeekedOpt { lock });
Ok(())
})
}

View file

@ -1,9 +1,8 @@
use mlua::{AnyUserData, Function, Lua, Table};
use yazi_binding::elements::{Edge, Renderable};
use yazi_config::THEME;
use yazi_macro::emit;
use yazi_parser::tab::SpotLock;
use yazi_shared::event::Cmd;
use yazi_parser::tab::{SpotLock, UpdateSpottedOpt};
use yazi_proxy::MgrProxy;
use super::Utils;
@ -32,7 +31,7 @@ impl Utils {
}),
Renderable::Table(Box::new(table)),
];
emit!(Call(Cmd::new("mgr:update_spotted").with_any("lock", lock)));
MgrProxy::update_spotted(UpdateSpottedOpt { lock });
Ok(())
})
@ -43,7 +42,7 @@ impl Utils {
let mut lock = SpotLock::try_from(t)?;
lock.data = widgets.into_iter().map(Renderable::try_from).collect::<mlua::Result<_>>()?;
emit!(Call(Cmd::new("mgr:update_spotted").with_any("lock", lock)));
MgrProxy::update_spotted(UpdateSpottedOpt { lock });
Ok(())
})
}

View file

@ -22,7 +22,7 @@ impl Utils {
let Some(cur) = runtime!(lua)?.current_owned() else {
return Err("block spawned by `ya.sync()` must be called in a plugin").into_lua_err();
};
Sendable::list_to_values(&lua, Self::retrieve(cur, block, args).await?)
Sendable::list_to_values(&lua, Self::retrieve(&lua, cur, block, args).await?)
})
})
} else {
@ -79,8 +79,13 @@ impl Utils {
lua.create_async_function(|_lua, _futs: MultiValue| async move { Ok(()) })
}
async fn retrieve(id: String, calls: usize, args: MultiValue) -> mlua::Result<Vec<Data>> {
let args = Sendable::values_to_list(args)?;
async fn retrieve(
lua: &Lua,
id: String,
calls: usize,
args: MultiValue,
) -> mlua::Result<Vec<Data>> {
let args = Sendable::values_to_list(lua, args)?;
let (tx, rx) = oneshot::channel::<Vec<Data>>();
let callback: PluginCallback = {
@ -95,7 +100,7 @@ impl Utils {
.chain(args.into_iter().map(|d| Sendable::data_to_value(lua, d)))
.collect::<mlua::Result<MultiValue>>()?;
let values = Sendable::values_to_list(block.call(args)?)?;
let values = Sendable::values_to_list(lua, block.call(args)?)?;
tx.send(values).map_err(|_| "send failed".into_lua_err())
})
};

View file

@ -2,7 +2,7 @@ use std::time::Duration;
use tokio::sync::oneshot;
use yazi_macro::emit;
use yazi_parser::app::{NotifyLevel, NotifyOpt, PluginOpt};
use yazi_parser::app::{NotifyLevel, NotifyOpt, PluginOpt, TasksProgress};
use yazi_shared::event::Cmd;
pub struct AppProxy;
@ -51,4 +51,8 @@ impl AppProxy {
pub fn plugin_do(opt: PluginOpt) {
emit!(Call(Cmd::new("app:plugin_do").with_any("opt", opt)));
}
pub fn update_progress(progress: TasksProgress) {
emit!(Call(Cmd::new("app:update_progress").with_any("progress", progress)));
}
}

View file

@ -1,9 +1,14 @@
use yazi_macro::emit;
use yazi_parser::cmp::ShowOpt;
use yazi_shared::{Id, event::Cmd};
pub struct CmpProxy;
impl CmpProxy {
pub fn show(opt: ShowOpt) {
emit!(Call(Cmd::new("cmp:show").with_any("opt", opt)));
}
pub fn trigger(word: &str, ticket: Id) {
emit!(Call(Cmd::args("cmp:trigger", [word]).with("ticket", ticket)));
}

View file

@ -1,5 +1,5 @@
mod macros;
yazi_macro::mod_flat!(app cmp confirm input mgr pick semaphore tasks);
yazi_macro::mod_flat!(app cmp confirm input mgr pick semaphore tasks which);
pub fn init() { crate::init_semaphore(); }

View file

@ -1,7 +1,7 @@
use std::borrow::Cow;
use yazi_macro::emit;
use yazi_parser::mgr::{OpenDoOpt, SearchOpt};
use yazi_parser::{mgr::{OpenDoOpt, SearchOpt}, tab::{FilterOpt, FindDoOpt, UpdatePeekedOpt, UpdateSpottedOpt}};
use yazi_shared::{SStr, event::Cmd, url::Url};
pub struct MgrProxy;
@ -29,6 +29,14 @@ impl MgrProxy {
));
}
pub fn find_do(opt: FindDoOpt) {
emit!(Call(Cmd::new("mgr:find_do").with_any("opt", opt)));
}
pub fn filter_do(opt: FilterOpt) {
emit!(Call(Cmd::new("mgr:filter_do").with_any("opt", opt)));
}
pub fn search_do(opt: SearchOpt) {
emit!(Call(
// TODO: use second positional argument instead of `args` parameter
@ -38,6 +46,14 @@ impl MgrProxy {
));
}
pub fn update_peeked(opt: UpdatePeekedOpt) {
emit!(Call(Cmd::new("mgr:update_peeked").with_any("opt", opt)));
}
pub fn update_spotted(opt: UpdateSpottedOpt) {
emit!(Call(Cmd::new("mgr:update_spotted").with_any("opt", opt)));
}
pub fn update_tasks(url: &Url) {
emit!(Call(Cmd::new("mgr:update_tasks").with_any("urls", vec![url.clone()])));
}

11
yazi-proxy/src/which.rs Normal file
View file

@ -0,0 +1,11 @@
use yazi_macro::emit;
use yazi_parser::which::ShowOpt;
use yazi_shared::event::Cmd;
pub struct WhichProxy;
impl WhichProxy {
pub fn show(opt: ShowOpt) {
emit!(Call(Cmd::new("which:show").with_any("opt", opt)));
}
}

View file

@ -85,20 +85,16 @@ impl Cmd {
pub fn get(&self, name: impl Into<DataKey>) -> Option<&Data> { self.args.get(&name.into()) }
#[inline]
pub fn str(&self, name: impl Into<DataKey>) -> Option<&str> {
self.get(name).and_then(Data::as_str)
}
pub fn str(&self, name: impl Into<DataKey>) -> Option<&str> { self.get(name)?.as_str() }
#[inline]
pub fn bool(&self, name: impl Into<DataKey>) -> bool { self.maybe_bool(name).unwrap_or(false) }
#[inline]
pub fn maybe_bool(&self, name: impl Into<DataKey>) -> Option<bool> {
self.get(name).and_then(Data::as_bool)
}
pub fn maybe_bool(&self, name: impl Into<DataKey>) -> Option<bool> { self.get(name)?.as_bool() }
#[inline]
pub fn id(&self, name: impl Into<DataKey>) -> Option<Id> { self.get(name).and_then(Data::as_id) }
pub fn id(&self, name: impl Into<DataKey>) -> Option<Id> { self.get(name)?.as_id() }
#[inline]
pub fn first(&self) -> Option<&Data> { self.get(0) }
@ -136,7 +132,12 @@ impl Cmd {
#[inline]
pub fn take_any<T: 'static>(&mut self, name: impl Into<DataKey>) -> Option<T> {
self.args.remove(&name.into()).and_then(|d| d.into_any())
self.args.remove(&name.into())?.into_any()
}
#[inline]
pub fn take_any2<T: 'static>(&mut self, name: impl Into<DataKey>) -> Option<Result<T>> {
self.args.remove(&name.into()).map(Data::into_any2)
}
// Parse

View file

@ -1,5 +1,7 @@
use std::{borrow::Cow, ops::Deref};
use anyhow::Result;
use super::{Cmd, Data, DataKey};
use crate::{SStr, url::Url};
@ -75,4 +77,12 @@ impl CmdCow {
Self::Borrowed(_) => None,
}
}
#[inline]
pub fn take_any2<T: 'static>(&mut self, name: impl Into<DataKey>) -> Option<Result<T>> {
match self {
Self::Owned(c) => c.take_any2(name),
Self::Borrowed(_) => None,
}
}
}

View file

@ -1,5 +1,6 @@
use std::{any::Any, borrow::Cow, collections::HashMap};
use anyhow::{Result, bail};
use ordered_float::OrderedFloat;
use serde::{Deserialize, Serialize, de};
@ -80,6 +81,18 @@ impl Data {
}
}
// FIXME: find a better name
#[inline]
pub fn into_any2<T: 'static>(self) -> Result<T> {
if let Self::Any(b) = self
&& let Ok(t) = b.downcast::<T>()
{
Ok(*t)
} else {
bail!("Failed to downcast Data into {}", std::any::type_name::<T>())
}
}
#[inline]
pub fn to_url(&self) -> Option<Url> {
match self {