refactor: make command data cloneable (#3613)

This commit is contained in:
三咲雅 misaki masa 2026-01-26 07:36:35 +08:00 committed by GitHub
parent face6aed40
commit 92880b844b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
81 changed files with 297 additions and 203 deletions

8
Cargo.lock generated
View file

@ -1242,6 +1242,12 @@ dependencies = [
"litrs", "litrs",
] ]
[[package]]
name = "dyn-clone"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]] [[package]]
name = "ecdsa" name = "ecdsa"
version = "0.16.9" version = "0.16.9"
@ -5726,6 +5732,7 @@ dependencies = [
"anyhow", "anyhow",
"bitflags 2.10.0", "bitflags 2.10.0",
"crossterm 0.29.0", "crossterm 0.29.0",
"dyn-clone",
"hashbrown", "hashbrown",
"mlua", "mlua",
"ordered-float 5.1.0", "ordered-float 5.1.0",
@ -5844,6 +5851,7 @@ version = "26.1.22"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"crossterm 0.29.0", "crossterm 0.29.0",
"dyn-clone",
"foldhash", "foldhash",
"futures", "futures",
"hashbrown", "hashbrown",

View file

@ -34,6 +34,7 @@ clap = { version = "4.5.54", features = [ "derive" ] }
core-foundation-sys = "0.8.7" core-foundation-sys = "0.8.7"
crossterm = { version = "0.29.0", features = [ "event-stream" ] } crossterm = { version = "0.29.0", features = [ "event-stream" ] }
dirs = "6.0.0" dirs = "6.0.0"
dyn-clone = "1.0.20"
foldhash = "0.2.0" foldhash = "0.2.0"
futures = "0.3.31" futures = "0.3.31"
globset = "0.4.18" globset = "0.4.18"

View file

@ -13,10 +13,7 @@ impl Actor for Close {
const NAME: &str = "close"; const NAME: &str = "close";
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
if let Some(cb) = cx.confirm.callback.take() { cx.confirm.token.complete(opt.submit);
_ = cb.send(opt.submit);
}
cx.confirm.visible = false; cx.confirm.visible = false;
succ!(render!()); succ!(render!());
} }

View file

@ -23,7 +23,7 @@ impl Actor for Show {
confirm.position = opt.cfg.position; confirm.position = opt.cfg.position;
confirm.offset = 0; confirm.offset = 0;
confirm.callback = Some(opt.tx); confirm.token = opt.token;
confirm.visible = true; confirm.visible = true;
succ!(render!()); succ!(render!());

View file

@ -13,6 +13,7 @@ pub(super) struct Core {
c_tasks: Option<Value>, c_tasks: Option<Value>,
c_yanked: Option<Value>, c_yanked: Option<Value>,
c_layer: Option<Value>, c_layer: Option<Value>,
c_which: Option<Value>,
} }
impl Deref for Core { impl Deref for Core {
@ -31,6 +32,7 @@ impl Core {
c_tasks: None, c_tasks: None,
c_yanked: None, c_yanked: None,
c_layer: None, c_layer: None,
c_which: None,
}) })
} }
} }
@ -58,6 +60,7 @@ impl UserData for Core {
b"layer" => { b"layer" => {
reuse!(layer, Ok::<_, mlua::Error>(yazi_plugin::bindings::Layer::from(me.layer()))) reuse!(layer, Ok::<_, mlua::Error>(yazi_plugin::bindings::Layer::from(me.layer())))
} }
b"which" => reuse!(which, super::Which::make(&me.which)),
_ => Value::Nil, _ => Value::Nil,
}) })
}); });

View file

@ -1 +1 @@
yazi_macro::mod_flat!(core file files filter finder folder lives mode preference preview ptr selected tab tabs task tasks yanked); yazi_macro::mod_flat!(core file files filter finder folder lives mode preference preview ptr selected tab tabs task tasks which yanked);

View file

@ -0,0 +1,25 @@
use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataFields};
use super::{Lives, PtrCell};
pub(super) struct Which {
inner: PtrCell<yazi_core::which::Which>,
}
impl Deref for Which {
type Target = yazi_core::which::Which;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl Which {
pub(super) fn make(inner: &yazi_core::which::Which) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into() })
}
}
impl UserData for Which {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {}
}

View file

@ -22,7 +22,10 @@ impl Actor for Displace {
let tab = cx.tab().id; let tab = cx.tab().id;
let from = cx.cwd().to_owned(); let from = cx.cwd().to_owned();
tokio::spawn(async move { tokio::spawn(async move {
MgrProxy::displace_do(tab, DisplaceDoOpt { to: provider::canonicalize(&from).await, from }); MgrProxy::displace_do(tab, DisplaceDoOpt {
to: provider::canonicalize(&from).await.map_err(Into::into),
from,
});
}); });
succ!(); succ!();

View file

@ -20,7 +20,7 @@ impl Actor for DisplaceDo {
let to = match opt.to { let to = match opt.to {
Ok(url) => url, Ok(url) => url,
Err(e) => return act!(mgr:update_files, cx, FilesOp::IOErr(opt.from, e.into())), Err(e) => return act!(mgr:update_files, cx, FilesOp::IOErr(opt.from, e)),
}; };
if !to.is_absolute() { if !to.is_absolute() {

View file

@ -41,7 +41,7 @@ impl Actor for OpenDo {
let urls: Vec<_> = let urls: Vec<_> =
[UrlCow::default()].into_iter().chain(targets.into_iter().map(|(u, _)| u)).collect(); [UrlCow::default()].into_iter().chain(targets.into_iter().map(|(u, _)| u)).collect();
tokio::spawn(async move { tokio::spawn(async move {
if let Ok(choice) = pick.await { if let Some(choice) = pick.await {
TasksProxy::open_shell_compat(ProcessOpenOpt { TasksProxy::open_shell_compat(ProcessOpenOpt {
cwd: opt.cwd, cwd: opt.cwd,
cmd: openers[choice].run.clone().into(), cmd: openers[choice].run.clone().into(),

View file

@ -33,7 +33,7 @@ impl Actor for Quit {
tokio::spawn(async move { tokio::spawn(async move {
let mut i = 0; let mut i = 0;
let mut rx = ConfirmProxy::show_rx(ConfirmCfg::quit(left, left_names)); let token = ConfirmProxy::show_sync(ConfirmCfg::quit(left, left_names));
loop { loop {
select! { select! {
_ = time::sleep(Duration::from_millis(50)) => { _ = time::sleep(Duration::from_millis(50)) => {
@ -44,8 +44,8 @@ impl Actor for Quit {
return; return;
} }
} }
b = &mut rx => { b = token.future() => {
if b.unwrap_or(false) { if b {
emit!(Quit(event)); emit!(Quit(event));
} }
return; return;
@ -53,7 +53,7 @@ impl Actor for Quit {
} }
} }
if rx.await.unwrap_or(false) { if token.future().await {
emit!(Quit(event)); emit!(Quit(event));
} }
}); });

View file

@ -1,4 +1,4 @@
use anyhow::{Result, anyhow}; use anyhow::Result;
use yazi_macro::{render, succ}; use yazi_macro::{render, succ};
use yazi_parser::pick::CloseOpt; use yazi_parser::pick::CloseOpt;
use yazi_shared::data::Data; use yazi_shared::data::Data;
@ -15,7 +15,7 @@ impl Actor for Close {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let pick = &mut cx.pick; let pick = &mut cx.pick;
if let Some(cb) = pick.callback.take() { if let Some(cb) = pick.callback.take() {
_ = cb.send(if opt.submit { Ok(pick.cursor) } else { Err(anyhow!("canceled")) }); _ = cb.send(if opt.submit { Some(pick.cursor) } else { None });
} }
pick.cursor = 0; pick.cursor = 0;

View file

@ -13,7 +13,7 @@ impl Actor for Callback {
const NAME: &str = "callback"; const NAME: &str = "callback";
fn act(_: &mut Ctx, opt: Self::Options) -> Result<Data> { fn act(_: &mut Ctx, opt: Self::Options) -> Result<Data> {
opt.tx.try_send(opt.idx)?; opt.tx.send(opt.idx)?;
succ!(); succ!();
} }
} }

View file

@ -9,7 +9,7 @@ use super::Key;
static RE: OnceLock<Regex> = OnceLock::new(); static RE: OnceLock<Regex> = OnceLock::new();
#[derive(Debug, Default, Deserialize)] #[derive(Clone, Debug, Default, Deserialize)]
pub struct Chord { pub struct Chord {
#[serde(deserialize_with = "super::deserialize_on")] #[serde(deserialize_with = "super::deserialize_on")]
pub on: Vec<Key>, pub on: Vec<Key>,

View file

@ -4,7 +4,7 @@ use yazi_shared::event::CmdCow;
use super::Chord; use super::Chord;
#[derive(Debug)] #[derive(Clone, Debug)]
pub enum ChordCow { pub enum ChordCow {
Owned(Chord), Owned(Chord),
Borrowed(&'static Chord), Borrowed(&'static Chord),

View file

@ -4,7 +4,7 @@ use yazi_shared::{scheme::Encode as EncodeScheme, strand::ToStrand, url::{Url, U
use super::{Offset, Position}; use super::{Offset, Position};
use crate::YAZI; use crate::YAZI;
#[derive(Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct InputCfg { pub struct InputCfg {
pub title: String, pub title: String,
pub value: String, pub value: String,
@ -15,14 +15,14 @@ pub struct InputCfg {
pub completion: bool, pub completion: bool,
} }
#[derive(Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct PickCfg { pub struct PickCfg {
pub title: String, pub title: String,
pub items: Vec<String>, pub items: Vec<String>,
pub position: Position, pub position: Position,
} }
#[derive(Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct ConfirmCfg { pub struct ConfirmCfg {
pub position: Position, pub position: Position,
pub title: Line<'static>, pub title: Line<'static>,

View file

@ -1,6 +1,6 @@
use ratatui::{text::Line, widgets::Paragraph}; use ratatui::{text::Line, widgets::Paragraph};
use tokio::sync::oneshot::Sender;
use yazi_config::popup::Position; use yazi_config::popup::Position;
use yazi_shared::CompletionToken;
#[derive(Default)] #[derive(Default)]
pub struct Confirm { pub struct Confirm {
@ -11,6 +11,6 @@ pub struct Confirm {
pub position: Position, pub position: Position,
pub offset: usize, pub offset: usize,
pub callback: Option<Sender<bool>>, pub token: CompletionToken,
pub visible: bool, pub visible: bool,
} }

View file

@ -1,5 +1,4 @@
use anyhow::Result; use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::oneshot::Sender;
use yazi_config::{YAZI, popup::Position}; use yazi_config::{YAZI, popup::Position};
use yazi_widgets::Scrollable; use yazi_widgets::Scrollable;
@ -11,7 +10,7 @@ pub struct Pick {
pub offset: usize, pub offset: usize,
pub cursor: usize, pub cursor: usize,
pub callback: Option<Sender<Result<usize>>>, pub callback: Option<UnboundedSender<Option<usize>>>,
pub visible: bool, pub visible: bool,
} }

View file

@ -24,7 +24,7 @@ pub struct Client {
pub(super) abilities: HashSet<String>, pub(super) abilities: HashSet<String>,
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Peer { pub struct Peer {
pub(super) abilities: HashSet<String>, pub(super) abilities: HashSet<String>,
} }

View file

@ -5,7 +5,7 @@ use yazi_shared::url::{Url, UrlCow};
use super::Ember; use super::Ember;
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberBulk<'a> { pub struct EmberBulk<'a> {
pub changes: HashMap<UrlCow<'a>, UrlCow<'a>>, pub changes: HashMap<UrlCow<'a>, UrlCow<'a>>,
} }

View file

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use super::Ember; use super::Ember;
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberBye; pub struct EmberBye;
impl EmberBye { impl EmberBye {

View file

@ -6,7 +6,7 @@ use yazi_shared::{Id, url::UrlBuf};
use super::Ember; use super::Ember;
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberCd<'a> { pub struct EmberCd<'a> {
pub tab: Id, pub tab: Id,
pub url: Cow<'a, UrlBuf>, pub url: Cow<'a, UrlBuf>,

View file

@ -5,7 +5,7 @@ use yazi_shared::data::Data;
use super::Ember; use super::Ember;
use crate::Sendable; use crate::Sendable;
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct EmberCustom { pub struct EmberCustom {
pub kind: String, pub kind: String,
pub data: Data, pub data: Data,

View file

@ -6,7 +6,7 @@ use yazi_shared::url::UrlBuf;
use super::Ember; use super::Ember;
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberDelete<'a> { pub struct EmberDelete<'a> {
pub urls: Cow<'a, Vec<UrlBuf>>, pub urls: Cow<'a, Vec<UrlBuf>>,
} }

View file

@ -6,7 +6,7 @@ use yazi_shared::url::UrlBuf;
use super::Ember; use super::Ember;
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberDuplicate<'a> { pub struct EmberDuplicate<'a> {
pub items: Cow<'a, Vec<BodyDuplicateItem>>, pub items: Cow<'a, Vec<BodyDuplicateItem>>,
} }

View file

@ -5,7 +5,7 @@ use yazi_shared::Id;
use super::{EmberBulk, EmberBye, EmberCd, EmberCustom, EmberDelete, EmberDuplicate, EmberHey, EmberHi, EmberHover, EmberLoad, EmberMount, EmberMove, EmberRename, EmberTab, EmberTrash, EmberYank}; use super::{EmberBulk, EmberBye, EmberCd, EmberCustom, EmberDelete, EmberDuplicate, EmberHey, EmberHi, EmberHover, EmberLoad, EmberMount, EmberMove, EmberRename, EmberTab, EmberTrash, EmberYank};
use crate::Payload; use crate::Payload;
#[derive(Debug)] #[derive(Clone, Debug)]
pub enum Ember<'a> { pub enum Ember<'a> {
Hi(EmberHi<'a>), Hi(EmberHi<'a>),
Hey(EmberHey), Hey(EmberHey),

View file

@ -7,7 +7,7 @@ use super::{Ember, EmberHi};
use crate::Peer; use crate::Peer;
/// Server handshake /// Server handshake
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberHey { pub struct EmberHey {
pub peers: HashMap<Id, Peer>, pub peers: HashMap<Id, Peer>,
pub version: SStr, pub version: SStr,

View file

@ -8,7 +8,7 @@ use yazi_shared::SStr;
use super::Ember; use super::Ember;
/// Client handshake /// Client handshake
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberHi<'a> { pub struct EmberHi<'a> {
/// Kinds of events the client can handle /// Kinds of events the client can handle
pub abilities: HashSet<Cow<'a, str>>, pub abilities: HashSet<Cow<'a, str>>,

View file

@ -6,7 +6,7 @@ use yazi_shared::{Id, url::UrlBuf};
use super::Ember; use super::Ember;
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberHover<'a> { pub struct EmberHover<'a> {
pub tab: Id, pub tab: Id,
pub url: Option<Cow<'a, UrlBuf>>, pub url: Option<Cow<'a, UrlBuf>>,

View file

@ -7,7 +7,7 @@ use yazi_shared::{Id, url::UrlBuf};
use super::Ember; use super::Ember;
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberLoad<'a> { pub struct EmberLoad<'a> {
pub tab: Id, pub tab: Id,
pub url: Cow<'a, UrlBuf>, pub url: Cow<'a, UrlBuf>,

View file

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use super::Ember; use super::Ember;
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberMount; pub struct EmberMount;
impl EmberMount { impl EmberMount {

View file

@ -6,7 +6,7 @@ use yazi_shared::url::UrlBuf;
use super::Ember; use super::Ember;
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberMove<'a> { pub struct EmberMove<'a> {
pub items: Cow<'a, Vec<BodyMoveItem>>, pub items: Cow<'a, Vec<BodyMoveItem>>,
} }

View file

@ -6,7 +6,7 @@ use yazi_shared::{Id, url::UrlBuf};
use super::Ember; use super::Ember;
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberRename<'a> { pub struct EmberRename<'a> {
pub tab: Id, pub tab: Id,
pub from: Cow<'a, UrlBuf>, pub from: Cow<'a, UrlBuf>,

View file

@ -4,7 +4,7 @@ use yazi_shared::Id;
use super::Ember; use super::Ember;
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberTab { pub struct EmberTab {
pub id: Id, pub id: Id,
} }

View file

@ -6,7 +6,7 @@ use yazi_shared::url::UrlBuf;
use super::Ember; use super::Ember;
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberTrash<'a> { pub struct EmberTrash<'a> {
pub urls: Cow<'a, Vec<UrlBuf>>, pub urls: Cow<'a, Vec<UrlBuf>>,
} }

View file

@ -8,7 +8,7 @@ use yazi_shared::url::UrlBufCov;
use super::Ember; use super::Ember;
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberYank<'a>(UpdateYankedOpt<'a>); pub struct EmberYank<'a>(UpdateYankedOpt<'a>);
impl<'a> EmberYank<'a> { impl<'a> EmberYank<'a> {

View file

@ -7,7 +7,7 @@ use yazi_shared::Id;
use crate::{ID, ember::Ember}; use crate::{ID, ember::Ember};
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct Payload<'a> { pub struct Payload<'a> {
pub receiver: Id, pub receiver: Id,
pub sender: Id, pub sender: Id,

View file

@ -84,14 +84,21 @@ impl Sendable {
} }
Data::Url(u) => yazi_binding::Url::new(u).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::Path(u) => yazi_binding::Path::new(u).into_lua(lua)?,
Data::Any(a) => { Data::Any(b) => {
if a.is::<yazi_fs::FilesOp>() { let mut b = b.into_any();
lua.create_any_userdata(*a.downcast::<yazi_fs::FilesOp>().unwrap())?.into_lua(lua)? macro_rules! try_cast {
} else if a.is::<yazi_parser::mgr::UpdateYankedOpt>() { ($f:expr) => {
a.downcast::<yazi_parser::mgr::UpdateYankedOpt>().unwrap().into_lua(lua)? match b.downcast() {
} else { Ok(v) => return $f(*v)?.into_lua(lua),
Err("unsupported Data::Any included".into_lua_err())? #[allow(unused_assignments)]
Err(e) => b = e,
}
};
} }
try_cast!(|v: yazi_fs::FilesOp| lua.create_any_userdata(v));
try_cast!(|v: yazi_parser::mgr::UpdateYankedOpt| v.into_lua(lua));
Err("unsupported DataAny included".into_lua_err())?
} }
data => Self::data_to_value_ref(lua, &data)?, data => Self::data_to_value_ref(lua, &data)?,
}) })
@ -124,12 +131,12 @@ impl Sendable {
Data::Path(u) => yazi_binding::Path::new(u).into_lua(lua)?, Data::Path(u) => yazi_binding::Path::new(u).into_lua(lua)?,
Data::Bytes(b) => Value::String(lua.create_string(b)?), Data::Bytes(b) => Value::String(lua.create_string(b)?),
Data::Any(a) => { Data::Any(a) => {
if let Some(t) = a.downcast_ref::<yazi_fs::FilesOp>() { if let Some(t) = a.as_any().downcast_ref::<yazi_fs::FilesOp>() {
lua.create_any_userdata(t.clone())?.into_lua(lua)? lua.create_any_userdata(t.clone())?.into_lua(lua)?
} else if let Some(t) = a.downcast_ref::<yazi_parser::mgr::UpdateYankedOpt>() { } else if let Some(t) = a.as_any().downcast_ref::<yazi_parser::mgr::UpdateYankedOpt>() {
t.clone().into_lua(lua)? t.clone().into_lua(lua)?
} else { } else {
Err("unsupported Data::Any included".into_lua_err())? Err("unsupported DataAny included".into_lua_err())?
} }
} }
}) })

View file

@ -60,7 +60,7 @@ impl App {
drop(loader); drop(loader);
let result = Lives::scope(&self.core, || { let result = Lives::scope(&self.core, || {
if let Some(cb) = opt.cb { if let Some(cb) = opt.callback {
cb(&LUA, plugin) cb(&LUA, plugin)
} else { } else {
let job = LUA.create_table_from([("args", Sendable::args_to_table(&LUA, opt.args)?)])?; let job = LUA.create_table_from([("args", Sendable::args_to_table(&LUA, opt.args)?)])?;

View file

@ -1,12 +1,12 @@
use anyhow::Result; use anyhow::Result;
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::VoidOpt; use yazi_parser::app::ResumeOpt;
use yazi_shared::data::Data; use yazi_shared::data::Data;
use crate::{Term, app::App}; use crate::{Term, app::App};
impl App { impl App {
pub(crate) fn resume(&mut self, _: VoidOpt) -> Result<Data> { pub(crate) fn resume(&mut self, opt: ResumeOpt) -> Result<Data> {
self.core.active_mut().preview.reset(); self.core.active_mut().preview.reset();
self.term = Some(Term::start().unwrap()); self.term = Some(Term::start().unwrap());
@ -14,7 +14,7 @@ impl App {
// We need to trigger a resize, and render the UI based on the resized area. // We need to trigger a resize, and render the UI based on the resized area.
act!(resize, self)?; act!(resize, self)?;
self.signals.resume(None); self.signals.resume(opt.token);
succ!(render!()); succ!(render!());
} }

View file

@ -14,7 +14,7 @@ impl App {
// while the app is being suspended. // while the app is being suspended.
self.term = None; self.term = None;
self.signals.stop(opt.tx); self.signals.stop(opt.token);
succ!(); succ!();
} }

View file

@ -1,12 +1,12 @@
use anyhow::Result; use anyhow::Result;
use crossterm::event::{Event as CrosstermEvent, EventStream, KeyEvent, KeyEventKind}; use crossterm::event::{Event as CrosstermEvent, EventStream, KeyEvent, KeyEventKind};
use futures::StreamExt; use futures::StreamExt;
use tokio::{select, sync::{mpsc, oneshot}}; use tokio::{select, sync::mpsc};
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_shared::event::Event; use yazi_shared::{CompletionToken, event::Event};
pub(super) struct Signals { pub(super) struct Signals {
tx: mpsc::UnboundedSender<(bool, Option<oneshot::Sender<()>>)>, tx: mpsc::UnboundedSender<(bool, CompletionToken)>,
} }
impl Signals { impl Signals {
@ -17,11 +17,9 @@ impl Signals {
Ok(Self { tx }) Ok(Self { tx })
} }
pub(super) fn stop(&mut self, cb: Option<oneshot::Sender<()>>) { self.tx.send((false, cb)).ok(); } pub(super) fn stop(&mut self, token: CompletionToken) { self.tx.send((false, token)).ok(); }
pub(super) fn resume(&mut self, cb: Option<oneshot::Sender<()>>) { pub(super) fn resume(&mut self, token: CompletionToken) { self.tx.send((true, token)).ok(); }
self.tx.send((true, cb)).ok();
}
#[cfg(unix)] #[cfg(unix)]
fn handle_sys(n: libc::c_int) -> bool { fn handle_sys(n: libc::c_int) -> bool {
@ -71,7 +69,7 @@ impl Signals {
} }
} }
fn spawn(mut rx: mpsc::UnboundedReceiver<(bool, Option<oneshot::Sender<()>>)>) -> Result<()> { fn spawn(mut rx: mpsc::UnboundedReceiver<(bool, CompletionToken)>) -> Result<()> {
#[cfg(unix)] #[cfg(unix)]
use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGTSTP}; use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGTSTP};
@ -96,9 +94,9 @@ impl Signals {
if let Some(t) = &mut term { if let Some(t) = &mut term {
select! { select! {
biased; biased;
Some((state, mut callback)) = rx.recv() => { Some((state, token)) = rx.recv() => {
term = term.filter(|_| state); term = term.filter(|_| state);
callback.take().map(|cb| cb.send(())); token.complete(true);
}, },
Some(n) = sys.next() => if !Self::handle_sys(n) { return }, Some(n) = sys.next() => if !Self::handle_sys(n) { return },
Some(Ok(e)) = t.next() => Self::handle_term(e) Some(Ok(e)) = t.next() => Self::handle_term(e)
@ -106,9 +104,9 @@ impl Signals {
} else { } else {
select! { select! {
biased; biased;
Some((state, mut callback)) = rx.recv() => { Some((state, token)) = rx.recv() => {
term = state.then(EventStream::new); term = state.then(EventStream::new);
callback.take().map(|cb| cb.send(())); token.complete(true);
}, },
Some(n) = sys.next() => if !Self::handle_sys(n) { return }, Some(n) = sys.next() => if !Self::handle_sys(n) { return },
} }

View file

@ -28,6 +28,7 @@ yazi-vfs = { path = "../yazi-vfs", version = "26.1.22" }
anyhow = { workspace = true } anyhow = { workspace = true }
bitflags = { workspace = true } bitflags = { workspace = true }
crossterm = { workspace = true } crossterm = { workspace = true }
dyn-clone = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
ordered-float = { workspace = true } ordered-float = { workspace = true }

View file

@ -6,6 +6,7 @@ use serde::Deserialize;
use yazi_config::{Style, THEME}; use yazi_config::{Style, THEME};
use yazi_shared::event::CmdCow; use yazi_shared::event::CmdCow;
#[derive(Clone)]
pub struct NotifyOpt { pub struct NotifyOpt {
pub title: String, pub title: String,
pub content: String, pub content: String,

View file

@ -1,19 +1,18 @@
use std::{borrow::Cow, fmt::Debug, str::FromStr}; use std::{borrow::Cow, fmt::{self, Debug}, str::FromStr};
use anyhow::bail; use anyhow::bail;
use dyn_clone::DynClone;
use hashbrown::HashMap; use hashbrown::HashMap;
use mlua::{Lua, Table}; use mlua::{Lua, Table};
use serde::Deserialize; use serde::Deserialize;
use yazi_shared::{SStr, data::{Data, DataKey}, event::{Cmd, CmdCow}}; use yazi_shared::{SStr, data::{Data, DataKey}, event::{Cmd, CmdCow}};
pub type PluginCallback = Box<dyn FnOnce(&Lua, Table) -> mlua::Result<()> + Send + Sync>; #[derive(Clone, Debug, Default)]
#[derive(Default)]
pub struct PluginOpt { pub struct PluginOpt {
pub id: SStr, pub id: SStr,
pub args: HashMap<DataKey, Data>, pub args: HashMap<DataKey, Data>,
pub mode: PluginMode, pub mode: PluginMode,
pub cb: Option<PluginCallback>, pub callback: Option<Box<dyn PluginCallback>>,
} }
impl TryFrom<CmdCow> for PluginOpt { impl TryFrom<CmdCow> for PluginOpt {
@ -36,27 +35,16 @@ impl TryFrom<CmdCow> for PluginOpt {
}; };
let mode = c.str("mode").parse().unwrap_or_default(); let mode = c.str("mode").parse().unwrap_or_default();
Ok(Self { id: Self::normalize_id(id), args, mode, cb: c.take_any("callback") }) Ok(Self { id: Self::normalize_id(id), args, mode, callback: c.take_any("callback") })
}
}
impl Debug for PluginOpt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PluginOpt")
.field("id", &self.id)
.field("args", &self.args)
.field("mode", &self.mode)
.field("cb", &self.cb.is_some())
.finish()
} }
} }
impl PluginOpt { impl PluginOpt {
pub fn new_callback(id: impl Into<SStr>, cb: PluginCallback) -> Self { pub fn new_callback(id: impl Into<SStr>, f: impl PluginCallback) -> Self {
Self { Self {
id: Self::normalize_id(id.into()), id: Self::normalize_id(id.into()),
mode: PluginMode::Sync, mode: PluginMode::Sync,
cb: Some(cb), callback: Some(Box::new(f)),
..Default::default() ..Default::default()
} }
} }
@ -98,3 +86,24 @@ impl PluginMode {
if sync { Self::Sync } else { Self::Async } if sync { Self::Sync } else { Self::Async }
} }
} }
// --- Callback
pub trait PluginCallback:
FnOnce(&Lua, Table) -> mlua::Result<()> + Send + Sync + DynClone + 'static
{
}
impl<T> PluginCallback for T where
T: FnOnce(&Lua, Table) -> mlua::Result<()> + Send + Sync + DynClone + 'static
{
}
impl Clone for Box<dyn PluginCallback> {
fn clone(&self) -> Self { dyn_clone::clone_box(&**self) }
}
impl fmt::Debug for dyn PluginCallback {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PluginCallback").finish_non_exhaustive()
}
}

View file

@ -1,10 +1,18 @@
use tokio::sync::oneshot; use anyhow::bail;
use yazi_shared::event::CmdCow; use yazi_shared::{CompletionToken, event::CmdCow};
pub struct ResumeOpt { pub struct ResumeOpt {
pub tx: Option<oneshot::Sender<()>>, pub token: CompletionToken,
} }
impl From<CmdCow> for ResumeOpt { impl TryFrom<CmdCow> for ResumeOpt {
fn from(mut c: CmdCow) -> Self { Self { tx: c.take_any("tx") } } type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
let Some(token) = c.take_any("token") else {
bail!("Invalid 'token' in ResumeOpt");
};
Ok(Self { token })
}
} }

View file

@ -1,10 +1,18 @@
use tokio::sync::oneshot; use anyhow::bail;
use yazi_shared::event::CmdCow; use yazi_shared::{CompletionToken, event::CmdCow};
pub struct StopOpt { pub struct StopOpt {
pub tx: Option<oneshot::Sender<()>>, pub token: CompletionToken,
} }
impl From<CmdCow> for StopOpt { impl TryFrom<CmdCow> for StopOpt {
fn from(mut c: CmdCow) -> Self { Self { tx: c.take_any("tx") } } type Error = anyhow::Error;
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
let Some(token) = c.take_any("token") else {
bail!("Invalid 'token' in StopOpt");
};
Ok(Self { token })
}
} }

View file

@ -14,7 +14,7 @@ impl TryFrom<CmdCow> for ArrowOpt {
fn try_from(c: CmdCow) -> Result<Self, Self::Error> { fn try_from(c: CmdCow) -> Result<Self, Self::Error> {
let Ok(step) = c.first() else { let Ok(step) = c.first() else {
bail!("'step' is required for ArrowOpt"); bail!("Invalid 'step' in ArrowOpt");
}; };
Ok(Self { step }) Ok(Self { step })

View file

@ -4,7 +4,7 @@ use anyhow::bail;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_shared::{Id, event::CmdCow, path::PathBufDyn, strand::{StrandBuf, StrandLike}, url::UrlBuf}; use yazi_shared::{Id, event::CmdCow, path::PathBufDyn, strand::{StrandBuf, StrandLike}, url::UrlBuf};
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct ShowOpt { pub struct ShowOpt {
pub cache: Vec<CmpItem>, pub cache: Vec<CmpItem>,
pub cache_name: UrlBuf, pub cache_name: UrlBuf,

View file

@ -1,13 +1,12 @@
use anyhow::bail; use anyhow::bail;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use tokio::sync::oneshot;
use yazi_config::popup::ConfirmCfg; use yazi_config::popup::ConfirmCfg;
use yazi_shared::event::CmdCow; use yazi_shared::{CompletionToken, event::CmdCow};
#[derive(Debug)] #[derive(Debug)]
pub struct ShowOpt { pub struct ShowOpt {
pub cfg: ConfirmCfg, pub cfg: ConfirmCfg,
pub tx: oneshot::Sender<bool>, pub token: CompletionToken,
} }
impl TryFrom<CmdCow> for ShowOpt { impl TryFrom<CmdCow> for ShowOpt {
@ -15,14 +14,14 @@ impl TryFrom<CmdCow> for ShowOpt {
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> { fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
let Some(cfg) = c.take_any("cfg") else { let Some(cfg) = c.take_any("cfg") else {
bail!("Invalid 'cfg' argument in ShowOpt"); bail!("Invalid 'cfg' in ShowOpt");
}; };
let Some(tx) = c.take_any("tx") else { let Some(token) = c.take_any("token") else {
bail!("Invalid 'tx' argument in ShowOpt"); bail!("Invalid 'token' in ShowOpt");
}; };
Ok(Self { cfg, tx }) Ok(Self { cfg, token })
} }
} }

View file

@ -15,11 +15,11 @@ impl TryFrom<CmdCow> for ShowOpt {
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> { fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
let Some(cfg) = c.take_any("cfg") else { let Some(cfg) = c.take_any("cfg") else {
bail!("Invalid 'cfg' argument in ShowOpt"); bail!("Invalid 'cfg' in ShowOpt");
}; };
let Some(tx) = c.take_any("tx") else { let Some(tx) = c.take_any("tx") else {
bail!("Invalid 'tx' argument in ShowOpt"); bail!("Invalid 'tx' in ShowOpt");
}; };
Ok(Self { cfg, tx }) Ok(Self { cfg, tx })

View file

@ -2,9 +2,9 @@ use anyhow::bail;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_shared::{event::CmdCow, url::UrlBuf}; use yazi_shared::{event::CmdCow, url::UrlBuf};
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct DisplaceDoOpt { pub struct DisplaceDoOpt {
pub to: std::io::Result<UrlBuf>, pub to: Result<UrlBuf, yazi_fs::error::Error>,
pub from: UrlBuf, pub from: UrlBuf,
} }
@ -15,7 +15,7 @@ impl TryFrom<CmdCow> for DisplaceDoOpt {
if let Some(opt) = c.take_any2("opt") { if let Some(opt) = c.take_any2("opt") {
opt opt
} else { } else {
bail!("'opt' is required for DisplaceDoOpt"); bail!("Invalid 'opt' in DisplaceDoOpt");
} }
} }
} }

View file

@ -2,7 +2,7 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_fs::FilterCase; use yazi_fs::FilterCase;
use yazi_shared::{SStr, event::CmdCow}; use yazi_shared::{SStr, event::CmdCow};
#[derive(Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct FilterOpt { pub struct FilterOpt {
pub query: SStr, pub query: SStr,
pub case: FilterCase, pub case: FilterCase,

View file

@ -3,7 +3,7 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_fs::FilterCase; use yazi_fs::FilterCase;
use yazi_shared::{SStr, event::CmdCow}; use yazi_shared::{SStr, event::CmdCow};
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct FindDoOpt { pub struct FindDoOpt {
pub query: SStr, pub query: SStr,
pub prev: bool, pub prev: bool,
@ -19,7 +19,7 @@ impl TryFrom<CmdCow> for FindDoOpt {
} }
let Ok(query) = c.take_first() else { let Ok(query) = c.take_first() else {
bail!("'query' is required for FindDoOpt"); bail!("Invalid 'query' in FindDoOpt");
}; };
Ok(Self { query, prev: c.bool("previous"), case: FilterCase::from(&*c) }) Ok(Self { query, prev: c.bool("previous"), case: FilterCase::from(&*c) })

View file

@ -1,7 +1,7 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_shared::{event::CmdCow, url::UrlCow}; use yazi_shared::{event::CmdCow, url::UrlCow};
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct OpenOpt { pub struct OpenOpt {
pub cwd: Option<UrlCow<'static>>, pub cwd: Option<UrlCow<'static>>,
pub targets: Vec<UrlCow<'static>>, pub targets: Vec<UrlCow<'static>>,

View file

@ -2,7 +2,7 @@ use anyhow::bail;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_shared::{event::CmdCow, url::UrlCow}; use yazi_shared::{event::CmdCow, url::UrlCow};
#[derive(Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct OpenDoOpt { pub struct OpenDoOpt {
pub cwd: UrlCow<'static>, pub cwd: UrlCow<'static>,
pub targets: Vec<UrlCow<'static>>, pub targets: Vec<UrlCow<'static>>,
@ -16,7 +16,7 @@ impl TryFrom<CmdCow> for OpenDoOpt {
if let Some(opt) = c.take_any2("opt") { if let Some(opt) = c.take_any2("opt") {
opt opt
} else { } else {
bail!("'opt' is required for OpenDoOpt"); bail!("Invalid 'opt' in OpenDoOpt");
} }
} }
} }

View file

@ -25,7 +25,7 @@ impl TryFrom<CmdCow> for SearchOpt {
}; };
let Ok(args) = yazi_shared::shell::unix::split(c.str("args"), false) else { let Ok(args) = yazi_shared::shell::unix::split(c.str("args"), false) else {
bail!("Invalid 'args' argument in SearchOpt"); bail!("Invalid 'args' in SearchOpt");
}; };
Ok(Self { Ok(Self {

View file

@ -13,7 +13,7 @@ impl TryFrom<CmdCow> for UpdateFilesOpt {
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> { fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
let Some(op) = c.take_any("op") else { let Some(op) = c.take_any("op") else {
bail!("Invalid 'op' argument in UpdateFilesOpt"); bail!("Invalid 'op' in UpdateFilesOpt");
}; };
Ok(Self { op }) Ok(Self { op })

View file

@ -13,7 +13,7 @@ impl TryFrom<CmdCow> for UpdateMimesOpt {
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> { fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
let Ok(updates) = c.take("updates") else { let Ok(updates) = c.take("updates") else {
bail!("Invalid 'updates' argument in UpdateMimesOpt"); bail!("Invalid 'updates' in UpdateMimesOpt");
}; };
Ok(Self { updates }) Ok(Self { updates })

View file

@ -3,7 +3,7 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};
use yazi_binding::{FileRef, elements::{Rect, Renderable}}; use yazi_binding::{FileRef, elements::{Rect, Renderable}};
use yazi_shared::event::CmdCow; use yazi_shared::event::CmdCow;
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct UpdatePeekedOpt { pub struct UpdatePeekedOpt {
pub lock: PreviewLock, pub lock: PreviewLock,
} }
@ -17,7 +17,7 @@ impl TryFrom<CmdCow> for UpdatePeekedOpt {
} }
let Some(lock) = c.take_any("lock") else { let Some(lock) = c.take_any("lock") else {
bail!("Invalid 'lock' argument in UpdatePeekedOpt"); bail!("Invalid 'lock' in UpdatePeekedOpt");
}; };
Ok(Self { lock }) Ok(Self { lock })
@ -33,7 +33,7 @@ impl IntoLua for UpdatePeekedOpt {
} }
// --- Lock // --- Lock
#[derive(Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct PreviewLock { pub struct PreviewLock {
pub url: yazi_shared::url::UrlBuf, pub url: yazi_shared::url::UrlBuf,
pub cha: yazi_fs::cha::Cha, pub cha: yazi_fs::cha::Cha,

View file

@ -3,7 +3,7 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};
use yazi_binding::{FileRef, elements::Renderable}; use yazi_binding::{FileRef, elements::Renderable};
use yazi_shared::{Id, event::CmdCow}; use yazi_shared::{Id, event::CmdCow};
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct UpdateSpottedOpt { pub struct UpdateSpottedOpt {
pub lock: SpotLock, pub lock: SpotLock,
} }
@ -17,7 +17,7 @@ impl TryFrom<CmdCow> for UpdateSpottedOpt {
} }
let Some(lock) = c.take_any("lock") else { let Some(lock) = c.take_any("lock") else {
bail!("Invalid 'lock' argument in UpdateSpottedOpt"); bail!("Invalid 'lock' in UpdateSpottedOpt");
}; };
Ok(Self { lock }) Ok(Self { lock })
@ -33,7 +33,7 @@ impl IntoLua for UpdateSpottedOpt {
} }
// --- Lock // --- Lock
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct SpotLock { pub struct SpotLock {
pub url: yazi_shared::url::UrlBuf, pub url: yazi_shared::url::UrlBuf,
pub cha: yazi_fs::cha::Cha, pub cha: yazi_fs::cha::Cha,

View file

@ -25,7 +25,7 @@ impl TryFrom<CmdCow> for UpdateYankedOpt<'_> {
if let Some(opt) = c.take_any2("opt") { if let Some(opt) = c.take_any2("opt") {
opt opt
} else { } else {
bail!("'opt' is required for UpdateYankedOpt"); bail!("Invalid 'opt' in UpdateYankedOpt");
} }
} }
} }

View file

@ -14,7 +14,7 @@ impl TryFrom<CmdCow> for TickOpt {
fn try_from(c: CmdCow) -> Result<Self, Self::Error> { fn try_from(c: CmdCow) -> Result<Self, Self::Error> {
let Ok(interval) = c.first() else { let Ok(interval) = c.first() else {
bail!("Invalid 'interval' argument in TickOpt"); bail!("Invalid 'interval' in TickOpt");
}; };
if interval < 0.0 { if interval < 0.0 {

View file

@ -1,13 +1,13 @@
use anyhow::bail; use anyhow::bail;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use tokio::sync::oneshot; use tokio::sync::mpsc;
use yazi_config::popup::PickCfg; use yazi_config::popup::PickCfg;
use yazi_shared::event::CmdCow; use yazi_shared::event::CmdCow;
#[derive(Debug)] #[derive(Debug)]
pub struct ShowOpt { pub struct ShowOpt {
pub cfg: PickCfg, pub cfg: PickCfg,
pub tx: oneshot::Sender<anyhow::Result<usize>>, pub tx: mpsc::UnboundedSender<Option<usize>>,
} }
impl TryFrom<CmdCow> for ShowOpt { impl TryFrom<CmdCow> for ShowOpt {
@ -15,11 +15,11 @@ impl TryFrom<CmdCow> for ShowOpt {
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> { fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
let Some(cfg) = c.take_any("cfg") else { let Some(cfg) = c.take_any("cfg") else {
bail!("Missing 'cfg' argument in ShowOpt"); bail!("Invalid 'cfg' in ShowOpt");
}; };
let Some(tx) = c.take_any("tx") else { let Some(tx) = c.take_any("tx") else {
bail!("Missing 'tx' argument in ShowOpt"); bail!("Invalid 'tx' in ShowOpt");
}; };
Ok(Self { cfg, tx }) Ok(Self { cfg, tx })

View file

@ -5,7 +5,7 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_shared::{CompletionToken, event::CmdCow, url::UrlCow}; use yazi_shared::{CompletionToken, event::CmdCow, url::UrlCow};
// --- Exec // --- Exec
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct ProcessOpenOpt { pub struct ProcessOpenOpt {
pub cwd: UrlCow<'static>, pub cwd: UrlCow<'static>,
pub cmd: OsString, pub cmd: OsString,

View file

@ -12,7 +12,7 @@ impl TryFrom<CmdCow> for UpdateSucceedOpt {
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> { fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
let Some(urls) = c.take_any("urls") else { let Some(urls) = c.take_any("urls") else {
bail!("Invalid 'urls' argument in UpdateSucceedOpt"); bail!("Invalid 'urls' in UpdateSucceedOpt");
}; };
Ok(Self { urls }) Ok(Self { urls })

View file

@ -5,7 +5,7 @@ use yazi_shared::event::CmdCow;
#[derive(Debug)] #[derive(Debug)]
pub struct CallbackOpt { pub struct CallbackOpt {
pub tx: mpsc::Sender<usize>, pub tx: mpsc::UnboundedSender<usize>,
pub idx: usize, pub idx: usize,
} }
@ -14,11 +14,11 @@ impl TryFrom<CmdCow> for CallbackOpt {
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> { fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
let Some(tx) = c.take_any("tx") else { let Some(tx) = c.take_any("tx") else {
bail!("Invalid 'tx' argument in CallbackOpt"); bail!("Invalid 'tx' in CallbackOpt");
}; };
let Ok(idx) = c.first() else { let Ok(idx) = c.first() else {
bail!("Invalid 'idx' argument in CallbackOpt"); bail!("Invalid 'idx' in CallbackOpt");
}; };
Ok(Self { tx, idx }) Ok(Self { tx, idx })

View file

@ -3,7 +3,7 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};
use yazi_config::{KEYMAP, keymap::{ChordCow, Key}}; use yazi_config::{KEYMAP, keymap::{ChordCow, Key}};
use yazi_shared::{Layer, event::CmdCow}; use yazi_shared::{Layer, event::CmdCow};
#[derive(Debug)] #[derive(Clone, Debug)]
pub struct ShowOpt { pub struct ShowOpt {
pub cands: Vec<ChordCow>, pub cands: Vec<ChordCow>,
pub silent: bool, pub silent: bool,

View file

@ -1,11 +1,11 @@
use mlua::{ExternalError, HookTriggers, IntoLua, ObjectLike, VmState}; use mlua::{ExternalError, HookTriggers, IntoLua, Lua, ObjectLike, Table, VmState};
use tokio::{runtime::Handle, select}; use tokio::{runtime::Handle, select};
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use tracing::error; use tracing::error;
use yazi_binding::{Error, File, elements::{Rect, Renderable}}; use yazi_binding::{Error, File, elements::{Rect, Renderable}};
use yazi_config::LAYOUT; use yazi_config::LAYOUT;
use yazi_dds::Sendable; use yazi_dds::Sendable;
use yazi_parser::{app::{PluginCallback, PluginOpt}, mgr::{PreviewLock, UpdatePeekedOpt}}; use yazi_parser::{app::PluginOpt, mgr::{PreviewLock, UpdatePeekedOpt}};
use yazi_proxy::{AppProxy, MgrProxy}; use yazi_proxy::{AppProxy, MgrProxy};
use yazi_shared::{event::Cmd, pool::Symbol}; use yazi_shared::{event::Cmd, pool::Symbol};
@ -52,7 +52,7 @@ pub fn peek(
} }
fn peek_sync(cmd: &'static Cmd, file: yazi_fs::File, mime: Symbol<str>, skip: usize) { fn peek_sync(cmd: &'static Cmd, file: yazi_fs::File, mime: Symbol<str>, skip: usize) {
let cb: PluginCallback = Box::new(move |lua, plugin| { let cb = move |lua: &Lua, plugin: Table| {
let job = lua.create_table_from([ let job = lua.create_table_from([
("area", Rect::from(LAYOUT.get().preview).into_lua(lua)?), ("area", Rect::from(LAYOUT.get().preview).into_lua(lua)?),
("args", Sendable::args_to_table_ref(lua, &cmd.args)?.into_lua(lua)?), ("args", Sendable::args_to_table_ref(lua, &cmd.args)?.into_lua(lua)?),
@ -62,7 +62,7 @@ fn peek_sync(cmd: &'static Cmd, file: yazi_fs::File, mime: Symbol<str>, skip: us
])?; ])?;
plugin.call_method("peek", job) plugin.call_method("peek", job)
}); };
AppProxy::plugin(PluginOpt::new_callback(&*cmd.name, cb)); AppProxy::plugin(PluginOpt::new_callback(&*cmd.name, cb));
} }

View file

@ -1,12 +1,12 @@
use mlua::{IntoLua, ObjectLike}; use mlua::{IntoLua, Lua, ObjectLike, Table};
use yazi_binding::{File, elements::Rect}; use yazi_binding::{File, elements::Rect};
use yazi_config::LAYOUT; use yazi_config::LAYOUT;
use yazi_parser::app::{PluginCallback, PluginOpt}; use yazi_parser::app::PluginOpt;
use yazi_proxy::AppProxy; use yazi_proxy::AppProxy;
use yazi_shared::event::Cmd; use yazi_shared::event::Cmd;
pub fn seek_sync(cmd: &'static Cmd, file: yazi_fs::File, units: i16) { pub fn seek_sync(cmd: &'static Cmd, file: yazi_fs::File, units: i16) {
let cb: PluginCallback = Box::new(move |lua, plugin| { let cb = move |lua: &Lua, plugin: Table| {
let job = lua.create_table_from([ let job = lua.create_table_from([
("file", File::new(file).into_lua(lua)?), ("file", File::new(file).into_lua(lua)?),
("area", Rect::from(LAYOUT.get().preview).into_lua(lua)?), ("area", Rect::from(LAYOUT.get().preview).into_lua(lua)?),
@ -14,7 +14,7 @@ pub fn seek_sync(cmd: &'static Cmd, file: yazi_fs::File, units: i16) {
])?; ])?;
plugin.call_method("seek", job) plugin.call_method("seek", job)
}); };
AppProxy::plugin(PluginOpt::new_callback(&*cmd.name, cb)); AppProxy::plugin(PluginOpt::new_callback(&*cmd.name, cb));
} }

View file

@ -20,7 +20,7 @@ impl Utils {
return Err("Cannot call `ya.which()` while main thread is blocked".into_lua_err()); return Err("Cannot call `ya.which()` while main thread is blocked".into_lua_err());
} }
let (tx, mut rx) = mpsc::channel::<usize>(1); let (tx, mut rx) = mpsc::unbounded_channel::<usize>();
let cands: Vec<_> = t let cands: Vec<_> = t
.raw_get::<Table>("cands")? .raw_get::<Table>("cands")?
.sequence_values::<Table>() .sequence_values::<Table>()

View file

@ -1,10 +1,10 @@
use anyhow::Context; use anyhow::Context;
use futures::future::join_all; use futures::future::join_all;
use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, MultiValue, Value, Variadic}; use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, MultiValue, Table, Value, Variadic};
use tokio::sync::oneshot; use tokio::sync::mpsc;
use yazi_binding::{Handle, runtime, runtime_mut}; use yazi_binding::{Handle, runtime, runtime_mut};
use yazi_dds::Sendable; use yazi_dds::Sendable;
use yazi_parser::app::{PluginCallback, PluginOpt}; use yazi_parser::app::PluginOpt;
use yazi_proxy::AppProxy; use yazi_proxy::AppProxy;
use yazi_shared::{LOCAL_SET, data::Data}; use yazi_shared::{LOCAL_SET, data::Data};
@ -115,27 +115,25 @@ impl Utils {
args: MultiValue, args: MultiValue,
) -> mlua::Result<Vec<Data>> { ) -> mlua::Result<Vec<Data>> {
let args = Sendable::values_to_list(lua, args)?; let args = Sendable::values_to_list(lua, args)?;
let (tx, rx) = oneshot::channel::<Vec<Data>>(); let (tx, mut rx) = mpsc::channel::<Vec<Data>>(1);
let callback: PluginCallback = { let id_ = id.to_owned();
let id = id.to_owned(); let callback = move |lua: &Lua, plugin: Table| {
Box::new(move |lua, plugin| { let Some(block) = runtime!(lua)?.get_block(&id_, calls) else {
let Some(block) = runtime!(lua)?.get_block(&id, calls) else { return Err("sync block not found".into_lua_err());
return Err("sync block not found".into_lua_err()); };
};
let args = [Ok(Value::Table(plugin))] let args = [Ok(Value::Table(plugin))]
.into_iter() .into_iter()
.chain(args.into_iter().map(|d| Sendable::data_to_value(lua, d))) .chain(args.into_iter().map(|d| Sendable::data_to_value(lua, d)))
.collect::<mlua::Result<MultiValue>>()?; .collect::<mlua::Result<MultiValue>>()?;
let values = Sendable::values_to_list(lua, block.call(args)?)?; let values = Sendable::values_to_list(lua, block.call(args)?)?;
tx.send(values).map_err(|_| "send failed".into_lua_err()) tx.try_send(values).map_err(|_| "send failed".into_lua_err())
})
}; };
AppProxy::plugin(PluginOpt::new_callback(id.to_owned(), callback)); AppProxy::plugin(PluginOpt::new_callback(id.to_owned(), callback));
rx.await.into_lua_err() rx.recv().await.ok_or("recv failed").into_lua_err()
} }
} }

View file

@ -1,22 +1,22 @@
use std::time::Duration; use std::time::Duration;
use tokio::sync::oneshot;
use yazi_macro::{emit, relay}; use yazi_macro::{emit, relay};
use yazi_parser::app::{NotifyLevel, NotifyOpt, PluginOpt, TaskSummary}; use yazi_parser::app::{NotifyLevel, NotifyOpt, PluginOpt, TaskSummary};
use yazi_shared::CompletionToken;
pub struct AppProxy; pub struct AppProxy;
impl AppProxy { impl AppProxy {
pub async fn stop() { pub async fn stop() {
let (tx, rx) = oneshot::channel::<()>(); let token = CompletionToken::default();
emit!(Call(relay!(app:stop).with_any("tx", tx))); emit!(Call(relay!(app:stop).with_any("token", token.clone())));
rx.await.ok(); token.future().await;
} }
pub async fn resume() { pub async fn resume() {
let (tx, rx) = oneshot::channel::<()>(); let token = CompletionToken::default();
emit!(Call(relay!(app:resume).with_any("tx", tx))); emit!(Call(relay!(app:resume).with_any("token", token.clone())));
rx.await.ok(); token.future().await;
} }
pub fn notify(opt: NotifyOpt) { pub fn notify(opt: NotifyOpt) {

View file

@ -1,15 +1,15 @@
use tokio::sync::oneshot;
use yazi_config::popup::ConfirmCfg; use yazi_config::popup::ConfirmCfg;
use yazi_macro::{emit, relay}; use yazi_macro::{emit, relay};
use yazi_shared::CompletionToken;
pub struct ConfirmProxy; pub struct ConfirmProxy;
impl ConfirmProxy { impl ConfirmProxy {
pub async fn show(cfg: ConfirmCfg) -> bool { Self::show_rx(cfg).await.unwrap_or(false) } pub async fn show(cfg: ConfirmCfg) -> bool { Self::show_sync(cfg).future().await }
pub fn show_rx(cfg: ConfirmCfg) -> oneshot::Receiver<bool> { pub fn show_sync(cfg: ConfirmCfg) -> CompletionToken {
let (tx, rx) = oneshot::channel(); let token = CompletionToken::default();
emit!(Call(relay!(confirm:show).with_any("tx", tx).with_any("cfg", cfg))); emit!(Call(relay!(confirm:show).with_any("cfg", cfg).with_any("token", token.clone())));
rx token
} }
} }

View file

@ -1,13 +1,13 @@
use tokio::sync::oneshot; use tokio::sync::mpsc;
use yazi_config::popup::PickCfg; use yazi_config::popup::PickCfg;
use yazi_macro::{emit, relay}; use yazi_macro::{emit, relay};
pub struct PickProxy; pub struct PickProxy;
impl PickProxy { impl PickProxy {
pub async fn show(cfg: PickCfg) -> anyhow::Result<usize> { pub async fn show(cfg: PickCfg) -> Option<usize> {
let (tx, rx) = oneshot::channel(); let (tx, mut rx) = mpsc::unbounded_channel::<Option<usize>>();
emit!(Call(relay!(pick:show).with_any("tx", tx).with_any("cfg", cfg))); emit!(Call(relay!(pick:show).with_any("tx", tx).with_any("cfg", cfg)));
rx.await? rx.recv().await?
} }
} }

View file

@ -18,6 +18,7 @@ yazi-macro = { path = "../yazi-macro", version = "26.1.22" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }
crossterm = { workspace = true } crossterm = { workspace = true }
dyn-clone = { workspace = true }
foldhash = { workspace = true } foldhash = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }

View file

@ -0,0 +1,28 @@
use std::{any::Any, fmt};
use dyn_clone::DynClone;
pub trait DataAny: Any + Send + Sync + DynClone {
fn as_any(&self) -> &dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any>;
}
impl<T> DataAny for T
where
T: Any + Send + Sync + DynClone,
{
fn as_any(&self) -> &dyn Any { self }
fn into_any(self: Box<Self>) -> Box<dyn Any> { self }
}
impl Clone for Box<dyn DataAny> {
fn clone(&self) -> Self { dyn_clone::clone_box(&**self) }
}
impl fmt::Debug for dyn DataAny {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DataAny").finish_non_exhaustive()
}
}

View file

@ -1,13 +1,13 @@
use std::{any::Any, borrow::Cow}; use std::borrow::Cow;
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{Id, SStr, data::DataKey, path::PathBufDyn, url::{UrlBuf, UrlCow}}; use crate::{Id, SStr, data::{DataAny, DataKey}, path::PathBufDyn, url::{UrlBuf, UrlCow}};
// --- Data // --- Data
#[derive(Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)] #[serde(untagged)]
pub enum Data { pub enum Data {
Nil, Nil,
@ -25,7 +25,7 @@ pub enum Data {
#[serde(skip)] #[serde(skip)]
Bytes(Vec<u8>), Bytes(Vec<u8>),
#[serde(skip)] #[serde(skip)]
Any(Box<dyn Any + Send + Sync>), Any(Box<dyn DataAny>),
} }
impl From<()> for Data { impl From<()> for Data {
@ -199,7 +199,7 @@ impl Data {
pub fn into_any<T: 'static>(self) -> Option<T> { pub fn into_any<T: 'static>(self) -> Option<T> {
match self { match self {
Self::Any(b) => b.downcast::<T>().ok().map(|b| *b), Self::Any(b) => b.into_any().downcast::<T>().ok().map(|b| *b),
_ => None, _ => None,
} }
} }
@ -207,7 +207,7 @@ impl Data {
// FIXME: find a better name // FIXME: find a better name
pub fn into_any2<T: 'static>(self) -> Result<T> { pub fn into_any2<T: 'static>(self) -> Result<T> {
if let Self::Any(b) = self if let Self::Any(b) = self
&& let Ok(t) = b.downcast::<T>() && let Ok(t) = b.into_any().downcast::<T>()
{ {
Ok(*t) Ok(*t)
} else { } else {

View file

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize, de};
use crate::{Id, SStr, path::PathBufDyn, url::{UrlBuf, UrlCow}}; use crate::{Id, SStr, path::PathBufDyn, url::{UrlBuf, UrlCow}};
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(untagged)] #[serde(untagged)]
pub enum DataKey { pub enum DataKey {
Nil, Nil,

View file

@ -1 +1 @@
yazi_macro::mod_flat!(data key); yazi_macro::mod_flat!(any data key);

View file

@ -1,12 +1,12 @@
use std::{any::Any, borrow::Cow, fmt::{self, Display}, mem, str::FromStr}; use std::{borrow::Cow, fmt::{self, Display}, mem, str::FromStr};
use anyhow::{Result, anyhow, bail}; use anyhow::{Result, anyhow, bail};
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, de}; use serde::{Deserialize, de};
use crate::{Layer, SStr, Source, data::{Data, DataKey}}; use crate::{Layer, SStr, Source, data::{Data, DataAny, DataKey}};
#[derive(Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Cmd { pub struct Cmd {
pub name: SStr, pub name: SStr,
pub args: HashMap<DataKey, Data>, pub args: HashMap<DataKey, Data>,
@ -76,7 +76,7 @@ impl Cmd {
self self
} }
pub fn with_any(mut self, name: impl Into<DataKey>, data: impl Any + Send + Sync) -> Self { pub fn with_any(mut self, name: impl Into<DataKey>, data: impl DataAny) -> Self {
self.args.insert(name.into(), Data::Any(Box::new(data))); self.args.insert(name.into(), Data::Any(Box::new(data)));
self self
} }