diff --git a/yazi-actor/src/mgr/bulk_exit.rs b/yazi-actor/src/mgr/bulk_exit.rs new file mode 100644 index 00000000..05271e2a --- /dev/null +++ b/yazi-actor/src/mgr/bulk_exit.rs @@ -0,0 +1,19 @@ +use anyhow::Result; +use yazi_macro::succ; +use yazi_parser::mgr::BulkExitOpt; +use yazi_shared::data::Data; + +use crate::{Actor, Ctx}; + +pub struct BulkExit; + +impl Actor for BulkExit { + type Options = BulkExitOpt; + + const NAME: &str = "bulk_exit"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + cx.mgr.batcher.decide(opt.target, opt.accept); + succ!(); + } +} diff --git a/yazi-actor/src/mgr/bulk_rename.rs b/yazi-actor/src/mgr/bulk_rename.rs index 856b3f46..6e106413 100644 --- a/yazi-actor/src/mgr/bulk_rename.rs +++ b/yazi-actor/src/mgr/bulk_rename.rs @@ -42,8 +42,11 @@ impl Actor for BulkRename { selected.iter().enumerate().map(|(i, u)| Tuple::new(i, skip_url(u, root))).collect(); let cwd = cx.cwd().clone(); + let batcher = cx.core.mgr.batcher.clone(); tokio::spawn(async move { let tmp = YAZI.preview.tmpfile("bulk"); + + batcher.prime(&tmp); Gate::default() .write(true) .create_new(true) @@ -54,6 +57,7 @@ impl Actor for BulkRename { defer! { let tmp = tmp.clone(); + batcher.drain(&tmp); tokio::spawn(async move { Local::regular(&tmp).remove_file().await }); @@ -79,7 +83,8 @@ impl Actor for BulkRename { .map(|(i, s)| Tuple::new(i, s)) .collect(); - Self::r#do(root, old, new, selected).await + let decision = batcher.drain(&tmp); + Self::r#do(root, old, new, selected, decision).await }); succ!(); } @@ -91,6 +96,7 @@ impl BulkRename { old: Vec, new: Vec, selected: Vec, + decision: Option, ) -> Result<()> { terminal_clear(TTY.writer())?; if old.len() != new.len() { @@ -108,18 +114,7 @@ impl BulkRename { return Ok(()); } - { - let mut w = TTY.lockout(); - for (old, new) in &todo { - writeln!(w, "{} -> {}", old.display(), new.display())?; - } - write!(w, "Continue to rename? (y/N): ")?; - w.flush()?; - } - - let mut buf = [0; 10]; - _ = TTY.reader().read(&mut buf)?; - if buf[0] != b'y' && buf[0] != b'Y' { + if !Self::ask_continue(&todo, decision)? { return Ok(()); } @@ -165,6 +160,25 @@ impl BulkRename { Ok(url.try_replace(take, PathDyn::with(url.kind(), rep)?)?.into_owned()) } + fn ask_continue(todo: &[(Tuple, Tuple)], decision: Option) -> Result { + if let Some(decision) = decision { + return Ok(decision); + } + + { + let mut w = TTY.lockout(); + for (old, new) in todo { + writeln!(w, "{} -> {}", old.display(), new.display())?; + } + write!(w, "Continue to rename? (y/N): ")?; + w.flush()?; + } + + let mut buf = [0; 10]; + _ = TTY.reader().read(&mut buf)?; + Ok(buf[0] == b'y' || buf[0] == b'Y') + } + async fn output_failed(failed: Vec<(Tuple, Tuple, anyhow::Error)>) -> Result<()> { let mut stdout = TTY.lockout(); terminal_clear(&mut *stdout)?; diff --git a/yazi-actor/src/mgr/mod.rs b/yazi-actor/src/mgr/mod.rs index e85db21a..5276e8c0 100644 --- a/yazi-actor/src/mgr/mod.rs +++ b/yazi-actor/src/mgr/mod.rs @@ -1,6 +1,7 @@ yazi_macro::mod_flat!( arrow back + bulk_exit bulk_rename cd close diff --git a/yazi-core/src/mgr/batcher.rs b/yazi-core/src/mgr/batcher.rs new file mode 100644 index 00000000..1e1463ef --- /dev/null +++ b/yazi-core/src/mgr/batcher.rs @@ -0,0 +1,38 @@ +use std::{path::{Path, PathBuf}, sync::Arc}; + +use parking_lot::Mutex; +use yazi_shared::url::AsUrl; + +#[derive(Clone, Default)] +pub struct Batcher { + pending: Arc)>>, +} + +impl Batcher { + pub fn prime(&self, target: T) + where + T: Into, + { + *self.pending.lock() = (target.into(), None); + } + + pub fn drain(&self, target: &Path) -> Option { + let mut pending = self.pending.lock(); + if pending.0 != target { + return None; + } + + pending.0 = PathBuf::default(); + pending.1.take() + } + + pub fn decide(&self, target: T, decision: bool) + where + T: AsUrl, + { + let mut pending = self.pending.lock(); + if target.as_url() == pending.0.as_url() { + pending.1 = Some(decision); + } + } +} diff --git a/yazi-core/src/mgr/mgr.rs b/yazi-core/src/mgr/mgr.rs index 42e2a28b..ad0cd876 100644 --- a/yazi-core/src/mgr/mgr.rs +++ b/yazi-core/src/mgr/mgr.rs @@ -7,13 +7,14 @@ use yazi_fs::Splatable; use yazi_shared::url::{AsUrl, Url, UrlBuf}; use yazi_watcher::Watcher; -use super::{Mimetype, Tabs, Yanked}; +use super::{Batcher, Mimetype, Tabs, Yanked}; use crate::tab::{Folder, Tab}; pub struct Mgr { pub tabs: Tabs, pub yanked: Yanked, + pub batcher: Batcher, pub watcher: Watcher, pub mimetype: Mimetype, } @@ -24,6 +25,7 @@ impl Mgr { tabs: Default::default(), yanked: Default::default(), + batcher: Default::default(), watcher: Watcher::serve(), mimetype: Default::default(), } diff --git a/yazi-core/src/mgr/mod.rs b/yazi-core/src/mgr/mod.rs index fee53c44..19f0aa52 100644 --- a/yazi-core/src/mgr/mod.rs +++ b/yazi-core/src/mgr/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(mgr mimetype tabs yanked); +yazi_macro::mod_flat!(batcher mgr mimetype tabs yanked); diff --git a/yazi-dds/src/spark/spark.rs b/yazi-dds/src/spark/spark.rs index 2a0441d0..a7a1ba9e 100644 --- a/yazi-dds/src/spark/spark.rs +++ b/yazi-dds/src/spark/spark.rs @@ -26,6 +26,7 @@ pub enum Spark<'a> { // Mgr Arrow(yazi_parser::ArrowOpt), Back(yazi_parser::VoidOpt), + BulkExit(yazi_parser::mgr::BulkExitOpt), BulkRename(yazi_parser::VoidOpt), Cd(yazi_parser::mgr::CdOpt), Close(yazi_parser::mgr::CloseOpt), @@ -207,6 +208,7 @@ impl<'a> IntoLua for Spark<'a> { // Mgr Self::Arrow(b) => b.into_lua(lua), Self::Back(b) => b.into_lua(lua), + Self::BulkExit(b) => b.into_lua(lua), Self::BulkRename(b) => b.into_lua(lua), Self::Cd(b) => b.into_lua(lua), Self::Close(b) => b.into_lua(lua), @@ -378,6 +380,7 @@ try_from_spark!(yazi_parser::confirm::ShowOpt, confirm:show); try_from_spark!(yazi_parser::help::ToggleOpt, help:toggle); try_from_spark!(yazi_parser::input::CloseOpt, input:close); try_from_spark!(yazi_widgets::input::InputOpt, input:show); +try_from_spark!(yazi_parser::mgr::BulkExitOpt, mgr:bulk_exit); try_from_spark!(yazi_parser::mgr::CdOpt, mgr:cd); try_from_spark!(yazi_parser::mgr::CloseOpt, mgr:close); try_from_spark!(yazi_parser::mgr::CopyOpt, mgr:copy); diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 2f8396f2..05205532 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -124,6 +124,7 @@ impl<'a> Executor<'a> { on!(linemode); on!(search); on!(search_do); + on!(bulk_exit); on!(bulk_rename); // Filter diff --git a/yazi-parser/src/mgr/bulk_exit.rs b/yazi-parser/src/mgr/bulk_exit.rs new file mode 100644 index 00000000..3372b902 --- /dev/null +++ b/yazi-parser/src/mgr/bulk_exit.rs @@ -0,0 +1,29 @@ +use anyhow::bail; +use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +use yazi_shared::{event::ActionCow, url::UrlCow}; + +#[derive(Debug)] +pub struct BulkExitOpt { + pub target: UrlCow<'static>, + pub accept: bool, +} + +impl TryFrom for BulkExitOpt { + type Error = anyhow::Error; + + fn try_from(mut a: ActionCow) -> Result { + let Ok(target) = a.take_first::() else { + bail!("invalid target in BulkExitOpt"); + }; + + Ok(Self { target, accept: a.bool("accept") }) + } +} + +impl FromLua for BulkExitOpt { + fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} + +impl IntoLua for BulkExitOpt { + fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} diff --git a/yazi-parser/src/mgr/mod.rs b/yazi-parser/src/mgr/mod.rs index ff9c10e9..60bfeab5 100644 --- a/yazi-parser/src/mgr/mod.rs +++ b/yazi-parser/src/mgr/mod.rs @@ -1,4 +1,5 @@ yazi_macro::mod_flat!( + bulk_exit cd close copy