mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: calculate real-time directory size in spotter (#2695)
This commit is contained in:
parent
54687181b3
commit
eba9f1e4f0
18 changed files with 170 additions and 26 deletions
|
|
@ -1,3 +1,3 @@
|
|||
mod macros;
|
||||
|
||||
yazi_macro::mod_flat!(error id stage url);
|
||||
yazi_macro::mod_flat!(error id stage url urn);
|
||||
|
|
|
|||
|
|
@ -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
32
yazi-binding/src/urn.rs
Normal 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 {}
|
||||
|
|
@ -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");
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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(); }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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!();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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())?,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 => {}
|
||||
|
|
|
|||
|
|
@ -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)?),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue