diff --git a/yazi-actor/src/mgr/spot.rs b/yazi-actor/src/mgr/spot.rs index eafe6e69..31cc58ee 100644 --- a/yazi-actor/src/mgr/spot.rs +++ b/yazi-actor/src/mgr/spot.rs @@ -1,7 +1,7 @@ use anyhow::Result; use yazi_macro::succ; use yazi_parser::mgr::SpotOpt; -use yazi_shared::data::Data; +use yazi_shared::{data::Data, pool::InternStr}; use crate::{Actor, Ctx}; @@ -15,7 +15,12 @@ impl Actor for Spot { fn act(cx: &mut Ctx, opt: Self::Options) -> Result { let Some(hovered) = cx.hovered().cloned() else { succ!() }; - let mime = cx.mgr.mimetype.owned(&hovered.url).unwrap_or_default(); + let (mime, urls) = if cx.tab().selected.len() >= 2 { + ("multi/selected".intern(), Some(cx.tab().selected.values().cloned().collect())) + } else { + (cx.mgr.mimetype.owned(&hovered.url).unwrap_or_default(), None) + }; + // if !self.active().spot.same_file(&hovered, &mime) { // self.active_mut().spot.reset(); // } @@ -26,7 +31,7 @@ impl Actor for Spot { cx.tab_mut().spot.skip = 0; } - cx.tab_mut().spot.go(hovered, mime); + cx.tab_mut().spot.go(hovered, mime, urls); succ!(); } } diff --git a/yazi-config/preset/yazi-default.toml b/yazi-config/preset/yazi-default.toml index 319338cf..fbb1b967 100644 --- a/yazi-config/preset/yazi-default.toml +++ b/yazi-config/preset/yazi-default.toml @@ -105,6 +105,7 @@ fetchers = [ { id = "mime", url = "remote://*", run = "mime.remote", prio = "high" }, ] spotters = [ + { mime = "multi/*", run = "multi" }, { url = "*/", run = "folder" }, # Code { mime = "text/*", run = "code" }, diff --git a/yazi-core/src/spot/spot.rs b/yazi-core/src/spot/spot.rs index 34034168..715a3b64 100644 --- a/yazi-core/src/spot/spot.rs +++ b/yazi-core/src/spot/spot.rs @@ -15,10 +15,10 @@ pub struct Spot { } impl Spot { - pub fn go(&mut self, file: File, mime: Symbol) { + pub fn go(&mut self, file: File, mime: Symbol, urls: Option>) { if mime.is_empty() { return; // Wait till mimetype is resolved to avoid flickering - } else if self.same_lock(&file, &mime) { + } else if urls.is_none() && self.same_lock(&file, &mime) { return; } @@ -27,7 +27,7 @@ impl Spot { }; self.abort(); - self.ct = Some(isolate::spot(&spotter.run, file, mime, self.skip)); + self.ct = Some(isolate::spot(&spotter.run, file, mime, self.skip, urls)); } pub fn visible(&self) -> bool { self.lock.is_some() } diff --git a/yazi-plugin/preset/plugins/multi.lua b/yazi-plugin/preset/plugins/multi.lua new file mode 100644 index 00000000..670688f8 --- /dev/null +++ b/yazi-plugin/preset/plugins/multi.lua @@ -0,0 +1,74 @@ +local M = {} + +function M:spot(job) + self.size = 0 + self.done = 0 + self.total = #job.files + self.last = 0 + self:spot_render(job, false) + local sizes = {} + for _, url in ipairs(job.files) do + local cha = fs.cha(url) + if cha and not cha.is_dir then + self.size = self.size + cha.len + self.done = self.done + 1 + self:spot_render(job, false) + else + local it = fs.calc_size(url) + local sub = 0 + while true do + local next = it:recv() + if next then + sub = sub + next + self.size = self.size + next + self:spot_render(job, false) + else + break + end + end + self.done = self.done + 1 + if cha and cha.is_dir then + sizes[url.urn] = sub + end + end + end + + if next(sizes) then + local first = job.files[1] + local parent = first.parent + if parent then + local op = fs.op("size", { url = parent, sizes = sizes }) + ya.emit("update_files", { op = op }) + end + end + self:spot_render(job, true) +end + +function M:spot_render(job, comp) + local now = ya.time() + if not comp and now < self.last + 0.1 then + return + end + + local progress = string.format("%d/%d", self.done, self.total) + local rows = { + ui.Row({ "Selected" }):style(ui.Style():fg("green")), + ui.Row { " Count:", tostring(self.total) }, + ui.Row { " Size:", ya.readable_size(self.size) .. (comp and "" or " (?)") }, + ui.Row { " Progress:", comp and "Done" or progress }, + } + + ya.spot_table( + job, + ui.Table(rows) + :area(ui.Pos { "center", w = 60, h = 20 }) + :row(self.last == 0 and 1 or nil) + :col(1) + :col_style(th.spot.tbl_col) + :cell_style(th.spot.tbl_cell) + :widths { ui.Constraint.Length(14), ui.Constraint.Fill(1) } + ) + self.last = now +end + +return M diff --git a/yazi-plugin/src/isolate/spot.rs b/yazi-plugin/src/isolate/spot.rs index 183a2ae9..e5649e7c 100644 --- a/yazi-plugin/src/isolate/spot.rs +++ b/yazi-plugin/src/isolate/spot.rs @@ -2,9 +2,9 @@ use mlua::{ExternalError, ExternalResult, HookTriggers, IntoLua, ObjectLike, VmS use tokio::{runtime::Handle, select}; use tokio_util::sync::CancellationToken; use tracing::error; -use yazi_binding::{File, Id}; +use yazi_binding::{File, Id, Url}; use yazi_dds::Sendable; -use yazi_shared::{Ids, event::Action, pool::Symbol}; +use yazi_shared::{Ids, event::Action, pool::Symbol, url::UrlBuf}; use super::slim_lua; use crate::loader::LOADER; @@ -16,6 +16,7 @@ pub fn spot( file: yazi_fs::File, mime: Symbol, skip: usize, + urls: Option>, ) -> CancellationToken { let ct = CancellationToken::new(); let (ct1, ct2) = (ct.clone(), ct.clone()); @@ -44,6 +45,13 @@ pub fn spot( ("mime", mime.into_lua(&lua)?), ("skip", skip.into_lua(&lua)?), ])?; + if let Some(urls) = urls { + let files = lua.create_table_with_capacity(urls.len(), 0)?; + for (i, url) in urls.into_iter().enumerate() { + files.raw_set(i + 1, Url::new(url))?; + } + job.raw_set("files", files)?; + } if ct2.is_cancelled() { Ok(()) } else { plugin.call_async_method("spot", job).await } }; diff --git a/yazi-plugin/src/loader/loader.rs b/yazi-plugin/src/loader/loader.rs index 481b9743..e6748c2c 100644 --- a/yazi-plugin/src/loader/loader.rs +++ b/yazi-plugin/src/loader/loader.rs @@ -44,6 +44,7 @@ impl Default for Loader { ("mime.dir".to_owned(), preset!("plugins/mime-dir").into()), ("mime.local".to_owned(), preset!("plugins/mime-local").into()), ("mime.remote".to_owned(), preset!("plugins/mime-remote").into()), + ("multi".to_owned(), preset!("plugins/multi").into()), ("noop".to_owned(), preset!("plugins/noop").into()), ("null".to_owned(), preset!("plugins/null").into()), ("pdf".to_owned(), preset!("plugins/pdf").into()),