feat: calculate real-time directory size in spotter (#2695)

This commit is contained in:
三咲雅 · Misaki Masa 2025-04-29 22:17:06 +08:00 committed by GitHub
parent 54687181b3
commit eba9f1e4f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 170 additions and 26 deletions

View file

@ -1,3 +1,3 @@
mod macros;
yazi_macro::mod_flat!(error id stage url);
yazi_macro::mod_flat!(error id stage url urn);

View file

@ -2,7 +2,7 @@ use std::{ops::Deref, path::Path};
use mlua::{AnyUserData, ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value};
use crate::cached_field;
use crate::{Urn, cached_field};
pub type UrlRef = UserDataRef<Url>;
@ -12,6 +12,7 @@ pub struct Url {
v_name: Option<Value>,
v_stem: Option<Value>,
v_ext: Option<Value>,
v_urn: Option<Value>,
v_base: Option<Value>,
v_parent: Option<Value>,
v_frag: Option<Value>,
@ -39,6 +40,7 @@ impl Url {
v_name: None,
v_stem: None,
v_ext: None,
v_urn: None,
v_base: None,
v_parent: None,
v_frag: None,
@ -83,6 +85,7 @@ impl UserData for Url {
me.extension().map(|s| lua.create_string(s.as_encoded_bytes())).transpose()
});
cached_field!(fields, parent, |_, me| Ok(me.parent_url().map(Self::new)));
cached_field!(fields, urn, |_, me| Ok(Urn::new(me.urn_owned())));
cached_field!(fields, base, |_, me| {
Ok(if me.base().as_os_str().is_empty() { None } else { Some(Self::new(me.base())) })
});

32
yazi-binding/src/urn.rs Normal file
View file

@ -0,0 +1,32 @@
use std::ops::Deref;
use mlua::{ExternalError, FromLua, Lua, UserData, Value};
pub struct Urn {
inner: yazi_shared::url::UrnBuf,
}
impl Deref for Urn {
type Target = yazi_shared::url::UrnBuf;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl From<Urn> for yazi_shared::url::UrnBuf {
fn from(value: Urn) -> Self { value.inner }
}
impl Urn {
pub fn new(urn: yazi_shared::url::UrnBuf) -> Self { Self { inner: urn } }
}
impl FromLua for Urn {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
Ok(match value {
Value::UserData(ud) => ud.take()?,
_ => Err("Expected a Urn".into_lua_err())?,
})
}
}
impl UserData for Urn {}

View file

@ -20,7 +20,7 @@ impl TryFrom<CmdCow> for Opt {
impl Mgr {
pub fn update_mimes(&mut self, opt: impl TryInto<Opt>, tasks: &Tasks) {
let Ok(opt) = opt.try_into() else {
let Ok(opt): Result<Opt, _> = opt.try_into() else {
return error!("invalid arguments for update_mimes");
};

View file

@ -1,4 +1,3 @@
use yazi_macro::render;
use yazi_shared::event::CmdCow;
use crate::spot::Spot;
@ -14,8 +13,5 @@ impl From<()> for Opt {
impl Spot {
#[yazi_codegen::command]
pub fn close(&mut self, _: Opt) {
self.abort();
render!(self.lock.take().is_some());
}
pub fn close(&mut self, _: Opt) { self.reset(); }
}

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use tokio_util::sync::CancellationToken;
use yazi_config::YAZI;
use yazi_fs::File;
use yazi_macro::render;
use yazi_plugin::{isolate, utils::SpotLock};
use yazi_shared::url::Url;
@ -36,6 +37,12 @@ impl Spot {
#[inline]
pub fn abort(&mut self) { self.ct.take().map(|ct| ct.cancel()); }
#[inline]
pub fn reset(&mut self) {
self.abort();
render!(self.lock.take().is_some());
}
#[inline]
pub fn same_url(&self, url: &Url) -> bool { self.lock.as_ref().is_some_and(|l| *url == l.url) }

View file

@ -19,10 +19,10 @@ impl TryFrom<CmdCow> for Opt {
impl Tab {
pub fn update_spotted(&mut self, opt: impl TryInto<Opt>) {
let Some(hovered) = self.hovered().map(|h| &h.url) else {
return self.preview.reset();
return self.spot.reset();
};
let Ok(opt) = opt.try_into() else {
let Ok(mut opt): Result<Opt, _> = opt.try_into() else {
return;
};
@ -30,7 +30,14 @@ impl Tab {
return;
}
self.spot.skip = opt.lock.selected().unwrap_or_default();
if self.spot.lock.as_ref().is_none_or(|l| l.id != opt.lock.id) {
self.spot.skip = opt.lock.selected().unwrap_or_default();
} else if let Some(s) = opt.lock.selected() {
self.spot.skip = s;
} else {
opt.lock.select(Some(self.spot.skip));
}
self.spot.lock = Some(opt.lock);
render!();
}

View file

@ -43,6 +43,8 @@ impl Sendable {
Value::UserData(ud) => {
if let Ok(t) = ud.take::<yazi_binding::Url>() {
Data::Url(t.into())
} else if let Ok(t) = ud.take::<yazi_binding::Urn>() {
Data::Urn(t.into())
} else if let Ok(t) = ud.borrow::<yazi_binding::Id>() {
Data::Id(**t)
} else if let Ok(t) = ud.take::<yazi_fs::FilesOp>() {
@ -50,7 +52,7 @@ impl Sendable {
} else if let Ok(t) = ud.take::<super::body::BodyYankIter>() {
Data::Any(Box::new(t))
} else {
Err("unsupported userdata included".into_lua_err())?
Err(format!("unsupported userdata included: {ud:?}").into_lua_err())?
}
}
Value::Error(_) => Err("error is not supported".into_lua_err())?,
@ -76,12 +78,13 @@ impl Sendable {
Value::Table(tbl)
}
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 userdata included".into_lua_err())?
Err("unsupported Data::Any included".into_lua_err())?
}),
data => Self::data_to_value_ref(lua, &data)?,
})
@ -111,13 +114,14 @@ impl Sendable {
}
Data::Id(i) => yazi_binding::Id(*i).into_lua(lua)?,
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 userdata included".into_lua_err())?
Err("unsupported Data::Any included".into_lua_err())?
}),
})
}
@ -191,7 +195,17 @@ impl Sendable {
Value::Table(_) => Err("table is not supported".into_lua_err())?,
Value::Function(_) => Err("function is not supported".into_lua_err())?,
Value::Thread(_) => Err("thread is not supported".into_lua_err())?,
Value::UserData(_) => Err("userdata is not supported".into_lua_err())?,
Value::UserData(ud) => {
if let Ok(t) = ud.take::<yazi_binding::Url>() {
DataKey::Url(t.into())
} else if let Ok(t) = ud.take::<yazi_binding::Urn>() {
DataKey::Urn(t.into())
} else if let Ok(t) = ud.borrow::<yazi_binding::Id>() {
DataKey::Id(**t)
} else {
Err(format!("unsupported userdata included: {ud:?}").into_lua_err())?
}
}
Value::Error(_) => Err("error is not supported".into_lua_err())?,
Value::Other(..) => Err("unknown data is not supported".into_lua_err())?,
})
@ -199,7 +213,11 @@ impl Sendable {
#[inline]
fn key_to_value(lua: &Lua, key: DataKey) -> mlua::Result<Value> {
Self::key_to_value_ref(lua, &key)
match key {
DataKey::Url(u) => yazi_binding::Url::new(u).into_lua(lua),
DataKey::Urn(u) => yazi_binding::Urn::new(u).into_lua(lua),
_ => Self::key_to_value_ref(lua, &key),
}
}
fn key_to_value_ref(lua: &Lua, key: &DataKey) -> mlua::Result<Value> {
@ -209,6 +227,9 @@ impl Sendable {
DataKey::Integer(i) => Value::Integer(*i),
DataKey::Number(n) => Value::Number(n.get()),
DataKey::String(s) => Value::String(lua.create_string(s.as_ref())?),
DataKey::Id(i) => yazi_binding::Id(*i).into_lua(lua)?,
DataKey::Url(u) => yazi_binding::Url::new(u.clone()).into_lua(lua)?,
DataKey::Urn(u) => yazi_binding::Urn::new(u.clone()).into_lua(lua)?,
})
}
}

View file

@ -129,7 +129,11 @@ impl Files {
}
}
pub fn update_size(&mut self, sizes: HashMap<UrnBuf, u64>) {
pub fn update_size(&mut self, mut sizes: HashMap<UrnBuf, u64>) {
if sizes.len() <= 50 {
sizes.retain(|k, v| self.sizes.get(k) != Some(v));
}
if sizes.is_empty() {
return;
}

View file

@ -44,6 +44,50 @@ function M:seek(job)
end
end
function M:spot(job) require("file"):spot(job) end
function M:spot(job)
self.size, self.last = 0, 0
local url = job.file.url
local it = fs.calc_size(url)
while true do
local next = it:recv()
if next then
self.size = self.size + next
self:spot_multi(job, false)
else
break
end
end
local op = fs.op("size", { url = url.parent, sizes = { [url.urn] = self.size } })
ya.emit("update_files", { op = op })
self:spot_multi(job, true)
end
function M:spot_multi(job, force)
local now = ya.time()
if not force and now < self.last + 0.1 then
return
end
local rows = {
ui.Row({ "Folder" }):style(ui.Style():fg("green")),
ui.Row { " Size:", ya.readable_size(self.size) .. (force and "" or " (?)") },
ui.Row {},
}
ya.spot_table(
job,
ui.Table(ya.list_merge(rows, require("file"):spot_base(job)))
: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

View file

@ -33,7 +33,8 @@ function ya.readable_size(size)
size = size / 1024
i = i + 1
end
return string.format("%.1f%s", size, units[i]):gsub("[.,]0", "", 1)
local s = string.format("%.1f%s", size, units[i]):gsub("[.,]0", "", 1)
return s
end
function ya.readable_path(path)

View file

@ -30,6 +30,7 @@ fn op(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (name, t): (mlua::String, Table)| match name.as_bytes().as_ref() {
b"part" => super::FilesOp::part(lua, t),
b"done" => super::FilesOp::done(lua, t),
b"size" => super::FilesOp::size(lua, t),
_ => Err("Unknown operation".into_lua_err())?,
})
}

View file

@ -1,5 +1,5 @@
use mlua::{IntoLua, Lua, Table};
use yazi_binding::{Id, Url};
use yazi_binding::{Id, Url, Urn};
use crate::{bindings::Cha, file::File};
@ -28,6 +28,19 @@ impl FilesOp {
Ok(Self(yazi_fs::FilesOp::Done(url.into(), *cha, *id)))
}
pub(super) fn size(_: &Lua, t: Table) -> mlua::Result<Self> {
let url: Url = t.raw_get("url")?;
let sizes: Table = t.raw_get("sizes")?;
Ok(Self(yazi_fs::FilesOp::Size(
url.into(),
sizes
.pairs::<Urn, u64>()
.map(|r| r.map(|(urn, size)| (urn.into(), size)))
.collect::<mlua::Result<_>>()?,
)))
}
}
impl IntoLua for FilesOp {

View file

@ -34,9 +34,9 @@ pub fn peek(
_ = ct_.cancelled() => {},
Ok(b) = LOADER.ensure(&cmd.name, |c| c.sync_peek) => {
if b {
peek_sync(cmd, file, mime, skip);
peek_sync( cmd, file, mime, skip);
} else {
peek_async(cmd, file, mime, skip, ct_);
peek_async( cmd, file, mime, skip, ct_);
}
},
else => {}

View file

@ -4,12 +4,15 @@ use mlua::{ExternalError, ExternalResult, HookTriggers, IntoLua, ObjectLike, VmS
use tokio::{runtime::Handle, select};
use tokio_util::sync::CancellationToken;
use tracing::error;
use yazi_binding::Id;
use yazi_dds::Sendable;
use yazi_shared::event::Cmd;
use yazi_shared::{Ids, event::Cmd};
use super::slim_lua;
use crate::{file::File, loader::LOADER};
static IDS: Ids = Ids::new();
pub fn spot(
cmd: &'static Cmd,
file: yazi_fs::File,
@ -37,6 +40,7 @@ pub fn spot(
let plugin = LOADER.load_once(&lua, &cmd.name)?;
let job = lua.create_table_from([
("id", Id(IDS.next()).into_lua(&lua)?),
("args", Sendable::args_to_table_ref(&lua, &cmd.args)?.into_lua(&lua)?),
("file", File::new(file).into_lua(&lua)?),
("mime", mime.into_lua(&lua)?),

View file

@ -1,7 +1,7 @@
use mlua::{AnyUserData, Function, Lua, Table};
use yazi_config::THEME;
use yazi_macro::emit;
use yazi_shared::event::Cmd;
use yazi_shared::{Id, event::Cmd};
use super::Utils;
use crate::{elements::Renderable, file::FileRef};
@ -11,6 +11,7 @@ pub struct SpotLock {
pub cha: yazi_fs::cha::Cha,
pub mime: String,
pub id: Id,
pub skip: usize,
pub data: Vec<Renderable>,
}
@ -25,6 +26,7 @@ impl TryFrom<Table> for SpotLock {
cha: file.cha,
mime: t.raw_get("mime")?,
id: *t.raw_get::<yazi_binding::Id>("id")?,
skip: t.raw_get("skip")?,
data: Default::default(),
})

View file

@ -2,7 +2,7 @@ use std::{any::Any, borrow::Cow, collections::HashMap};
use serde::{Deserialize, Serialize, de};
use crate::{Id, OrderedFloat, url::Url};
use crate::{Id, OrderedFloat, url::{Url, UrnBuf}};
// --- Data
#[derive(Debug, Serialize, Deserialize)]
@ -18,6 +18,8 @@ pub enum Data {
Id(Id),
#[serde(skip_deserializing)]
Url(Url),
#[serde(skip_deserializing)]
Urn(UrnBuf),
#[serde(skip)]
Bytes(Vec<u8>),
#[serde(skip)]
@ -106,6 +108,11 @@ pub enum DataKey {
Integer(i64),
Number(OrderedFloat),
String(Cow<'static, str>),
Id(Id),
#[serde(skip_deserializing)]
Url(Url),
#[serde(skip_deserializing)]
Urn(UrnBuf),
}
impl DataKey {

View file

@ -1,5 +1,7 @@
use std::{borrow::{Borrow, Cow}, ffi::OsStr, ops::Deref, path::{Path, PathBuf}};
use serde::Serialize;
#[derive(Debug, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub struct Urn(Path);
@ -48,7 +50,7 @@ impl PartialEq<Cow<'_, OsStr>> for &Urn {
}
// --- UrnBuf
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)]
pub struct UrnBuf(PathBuf);
impl Borrow<Urn> for UrnBuf {