feat: multi-file spotter (#3733)

Co-authored-by: sxyazi <sxyazi@gmail.com>
This commit is contained in:
Daniel Vincent 2026-03-06 06:57:32 +05:30 committed by GitHub
parent be91b4111c
commit dbf81deeab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 88 additions and 3 deletions

View file

@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Custom tab name ([#3666])
- New `--in` for `search` action to set search directory ([#3696])
- Multi-file spotter ([#3733])
- New `hovered` condition specifying different icons for hovered files ([#3728])
- Allow using `ps.sub()` in `init.lua` directly without a plugin ([#3638])
- New `sort_fallback` option to control fallback sorting behavior ([#3077])
@ -1672,3 +1673,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#3708]: https://github.com/sxyazi/yazi/pull/3708
[#3725]: https://github.com/sxyazi/yazi/pull/3725
[#3728]: https://github.com/sxyazi/yazi/pull/3728
[#3733]: https://github.com/sxyazi/yazi/pull/3733

View file

@ -1,7 +1,7 @@
use anyhow::Result;
use yazi_macro::succ;
use yazi_macro::{act, succ};
use yazi_parser::mgr::SpotOpt;
use yazi_shared::data::Data;
use yazi_shared::{data::Data, pool::InternStr};
use crate::{Actor, Ctx};
@ -13,9 +13,15 @@ impl Actor for Spot {
const NAME: &str = "spot";
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
act!(mgr:escape_visual, cx)?;
let Some(hovered) = cx.hovered().cloned() else { succ!() };
let mime = cx.mgr.mimetype.owned(&hovered.url).unwrap_or_default();
let mime = if cx.tab().selected.is_empty() {
cx.mgr.mimetype.owned(&hovered.url).unwrap_or_default()
} else {
"multi/unknown".intern()
};
// if !self.active().spot.same_file(&hovered, &mime) {
// self.active_mut().spot.reset();
// }

View file

@ -105,6 +105,9 @@ fetchers = [
{ id = "mime", url = "remote://*", run = "mime.remote", prio = "high" },
]
spotters = [
# Multi-file
{ mime = "multi/*", run = "multi" },
# Folder
{ url = "*/", run = "folder" },
# Code
{ mime = "text/*", run = "code" },

View file

@ -0,0 +1,73 @@
--- @sync peek
local M = {}
local selected = ya.sync(function()
local urls = {}
for _, u in pairs(cx.active.selected) do
urls[#urls + 1] = u
end
return urls
end)
function M:spot(job)
self.sum, self.sizes, self.last, self.selected = 0, {}, 0, selected()
self:spot_multi(job, false)
for _, u in ipairs(self.selected) do
local it, size = fs.calc_size(u), 0
while true do
local next = it:recv()
if next then
size, self.sum = size + next, self.sum + next
self:spot_multi(job, false)
else
self.sizes[u], size = size, 0
break
end
end
end
self:spot_multi(job, true)
end
function M:spot_multi(job, comp)
local now = ya.time()
if not comp and now < self.last + 0.1 then
return
end
local rows = {
ui.Row({ "Multi" }):style(ui.Style():fg("green")),
ui.Row { " Count:", string.format("%d selected", #self.selected) },
ui.Row { " Size:", ya.readable_size(self.sum) .. (comp and "" or " (?)") },
}
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:update_sizes()
self.last = now
end
function M:update_sizes()
local parents = {}
for url, size in pairs(self.sizes) do
local p = url.parent
parents[p] = parents[p] or {}
parents[p][url.urn] = size
end
for parent, sizes in pairs(parents) do
ya.emit("update_files", { op = fs.op("size", { url = parent, sizes = sizes }) })
end
end
return M

View file

@ -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()),