diff --git a/yazi-config/preset/yazi.toml b/yazi-config/preset/yazi.toml index d3b9a2c6..a0772376 100644 --- a/yazi-config/preset/yazi.toml +++ b/yazi-config/preset/yazi.toml @@ -67,8 +67,8 @@ rules = [ ] [tasks] -micro_workers = 5 -macro_workers = 10 +micro_workers = 10 +macro_workers = 25 bizarre_retry = 5 image_alloc = 536870912 # 512MB image_bound = [ 0, 0 ] diff --git a/yazi-core/src/tasks/commands/mod.rs b/yazi-core/src/tasks/commands/mod.rs index 987a842b..f546135c 100644 --- a/yazi-core/src/tasks/commands/mod.rs +++ b/yazi-core/src/tasks/commands/mod.rs @@ -3,4 +3,3 @@ mod cancel; mod inspect; mod open; mod toggle; -mod update; diff --git a/yazi-core/src/tasks/commands/update.rs b/yazi-core/src/tasks/commands/update.rs deleted file mode 100644 index 1a0a1dee..00000000 --- a/yazi-core/src/tasks/commands/update.rs +++ /dev/null @@ -1,28 +0,0 @@ -use yazi_shared::{emit, event::Exec, render, Layer}; - -use crate::tasks::{Tasks, TasksProgress}; - -pub struct Opt { - progress: TasksProgress, -} - -impl TryFrom<&Exec> for Opt { - type Error = (); - - fn try_from(e: &Exec) -> Result { e.take_data().ok_or(()) } -} - -impl Tasks { - pub fn _update(progress: TasksProgress) { - emit!(Call(Exec::call("update", vec![]).with_data(Opt { progress }).vec(), Layer::Tasks)); - } - - pub fn update(&mut self, opt: impl TryInto) { - let Ok(opt) = opt.try_into() else { - return; - }; - - self.progress = opt.progress; - render!(); - } -} diff --git a/yazi-core/src/tasks/tasks.rs b/yazi-core/src/tasks/tasks.rs index 3830f4ad..3233c3a3 100644 --- a/yazi-core/src/tasks/tasks.rs +++ b/yazi-core/src/tasks/tasks.rs @@ -4,7 +4,7 @@ use tokio::time::sleep; use tracing::debug; use yazi_config::{manager::SortBy, open::Opener, plugin::{PluginRule, MAX_PRELOADERS}, popup::InputCfg, OPEN, PLUGIN}; use yazi_scheduler::{Scheduler, TaskSummary}; -use yazi_shared::{fs::{File, Url}, term::Term, MIME_DIR}; +use yazi_shared::{emit, event::Exec, fs::{File, Url}, term::Term, Layer, MIME_DIR}; use super::{TasksProgress, TASKS_PADDING, TASKS_PERCENT}; use crate::{folder::Files, input::Input}; @@ -35,7 +35,7 @@ impl Tasks { let new = TasksProgress::from(&*running.read()); if last != new { last = new; - Tasks::_update(new); + emit!(Call(Exec::call("update_progress", vec![]).with_data(new).vec(), Layer::App)); } } }); diff --git a/yazi-fm/src/app/commands/mod.rs b/yazi-fm/src/app/commands/mod.rs index 504f5186..556cb9a5 100644 --- a/yazi-fm/src/app/commands/mod.rs +++ b/yazi-fm/src/app/commands/mod.rs @@ -3,3 +3,4 @@ mod quit; mod render; mod resize; mod stop; +mod update_progress; diff --git a/yazi-fm/src/app/commands/render.rs b/yazi-fm/src/app/commands/render.rs index 38cb5921..aca7d710 100644 --- a/yazi-fm/src/app/commands/render.rs +++ b/yazi-fm/src/app/commands/render.rs @@ -29,17 +29,17 @@ impl App { return Ok(()); } - let mut patches = vec![]; + let mut patch = vec![]; for x in frame.area.left()..frame.area.right() { for y in frame.area.top()..frame.area.bottom() { let cell = frame.buffer.get(x, y); if cell.skip { - patches.push((x, y, cell.clone())); + patch.push((x, y, cell.clone())); } } } - term.backend_mut().draw(patches.iter().map(|(x, y, cell)| (*x, *y, cell)))?; + term.backend_mut().draw(patch.iter().map(|(x, y, cell)| (*x, *y, cell)))?; if let Some((x, y)) = self.cx.cursor() { term.show_cursor()?; term.set_cursor(x, y)?; diff --git a/yazi-fm/src/app/commands/update_progress.rs b/yazi-fm/src/app/commands/update_progress.rs new file mode 100644 index 00000000..448af77a --- /dev/null +++ b/yazi-fm/src/app/commands/update_progress.rs @@ -0,0 +1,41 @@ +use ratatui::backend::Backend; +use yazi_core::tasks::TasksProgress; +use yazi_shared::event::Exec; + +use crate::{app::App, components::Progress, lives::Lives}; + +pub struct Opt { + progress: TasksProgress, +} + +impl TryFrom<&Exec> for Opt { + type Error = (); + + fn try_from(e: &Exec) -> Result { + Ok(Self { progress: e.take_data().ok_or(())? }) + } +} + +impl App { + pub(crate) fn update_progress(&mut self, opt: impl TryInto) { + let Ok(opt) = opt.try_into() else { + return; + }; + + self.cx.tasks.progress = opt.progress; + let Some(term) = &mut self.term else { + return; + }; + + Lives::partial_scope(&self.cx, |_| { + for patch in Progress::partial_render(term.current_buffer_mut()) { + term.backend_mut().draw(patch.iter().map(|(x, y, cell)| (*x, *y, cell))).ok(); + if let Some((x, y)) = self.cx.cursor() { + term.show_cursor().ok(); + term.set_cursor(x, y).ok(); + } + term.backend_mut().flush().ok(); + } + }); + } +} diff --git a/yazi-fm/src/components/mod.rs b/yazi-fm/src/components/mod.rs index 95b2d4be..3549f1df 100644 --- a/yazi-fm/src/components/mod.rs +++ b/yazi-fm/src/components/mod.rs @@ -3,9 +3,11 @@ mod header; mod manager; mod preview; +mod progress; mod status; pub(super) use header::*; pub(super) use manager::*; pub(super) use preview::*; +pub(super) use progress::*; pub(super) use status::*; diff --git a/yazi-fm/src/components/progress.rs b/yazi-fm/src/components/progress.rs new file mode 100644 index 00000000..87dafe77 --- /dev/null +++ b/yazi-fm/src/components/progress.rs @@ -0,0 +1,43 @@ +use std::mem; + +use mlua::{AnyUserData, Table, TableExt}; +use tracing::error; +use yazi_plugin::{cast_to_renderable, LUA}; + +pub(crate) struct Progress; + +impl Progress { + pub(crate) fn partial_render( + buf: &mut ratatui::buffer::Buffer, + ) -> Vec> { + let mut patches = vec![]; + let mut f = || { + let comp: Table = LUA.globals().get("Progress")?; + for widget in comp.call_method::<_, Vec>("partial_render", ())? { + let Some(w) = cast_to_renderable(widget) else { + continue; + }; + + let area = w.area(); + w.render(buf); + + let mut patch = Vec::with_capacity(area.width as usize * area.height as usize); + for x in area.left()..area.right() { + for y in area.top()..area.bottom() { + patch.push((x, y, mem::take(buf.get_mut(x, y)))); + } + } + + buf.reset(); + patches.push(patch); + } + + Ok::<_, anyhow::Error>(()) + }; + + if let Err(e) = f() { + error!("{:?}", e); + } + patches + } +} diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 6afc5c17..2309e50e 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -84,6 +84,7 @@ impl<'a> Executor<'a> { on!(plugin); on!(plugin_do); + on!(update_progress); on!(stop); } @@ -192,7 +193,6 @@ impl<'a> Executor<'a> { }; } - on!(update); on!(open); on!(toggle, "close"); on!(arrow); diff --git a/yazi-fm/src/lives/lives.rs b/yazi-fm/src/lives/lives.rs index d7765d16..c5c6d2e6 100644 --- a/yazi-fm/src/lives/lives.rs +++ b/yazi-fm/src/lives/lives.rs @@ -54,4 +54,20 @@ impl Lives { error!("{e}"); } } + + pub(crate) fn partial_scope<'a>(cx: &'a Ctx, f: impl FnOnce(&Scope<'a, 'a>)) { + let result = LUA.scope(|scope| { + LUA.globals().set( + "cx", + LUA.create_table_from([("tasks", super::Tasks::new(scope, &cx.tasks).make()?)])?, + )?; + + f(scope); + Ok(()) + }); + + if let Err(e) = result { + error!("{e}"); + } + } } diff --git a/yazi-plugin/preset/components/progress.lua b/yazi-plugin/preset/components/progress.lua new file mode 100644 index 00000000..eca652df --- /dev/null +++ b/yazi-plugin/preset/components/progress.lua @@ -0,0 +1,45 @@ +Progress = { + area = ui.Rect.default, +} + +function Progress:render(area, offset) + self.area = ui.Rect { + x = math.max(0, area.w - offset - 21), + y = area.y, + w = math.max(0, math.min(20, area.w - offset - 1)), + h = 1, + } + return self:partial_render() +end + +-- Progress bars usually need frequent updates to report the latest task progress. +-- We use `partial_render()` to partially render it when there is progress change, +-- which has almost no cost compared to a full render by `render()`. +-- +-- However, at this time, we can only access `cx.tasks`. If you need certain data from the complete `cx`, +-- just cache it to `self` during `render()`, and read it in `partial_render()` - this process is referred to as "composition". +function Progress:partial_render() + local progress = cx.tasks.progress + if progress.total == 0 then + return { ui.Paragraph(self.area, {}) } + end + + local gauge = ui.Gauge(self.area) + if progress.fail == 0 then + gauge = gauge:gauge_style(THEME.status.progress_normal) + else + gauge = gauge:gauge_style(THEME.status.progress_error) + end + + local percent = 99 + if progress.found ~= 0 then + percent = math.min(99, ya.round(progress.processed * 100 / progress.found)) + end + + local left = progress.total - progress.succ + return { + gauge + :percent(percent) + :label(ui.Span(string.format("%3d%%, %d left", percent, left)):style(THEME.status.progress_label)), + } +end diff --git a/yazi-plugin/preset/components/status.lua b/yazi-plugin/preset/components/status.lua index e25c3d20..3fc9d5ca 100644 --- a/yazi-plugin/preset/components/status.lua +++ b/yazi-plugin/preset/components/status.lua @@ -108,47 +108,14 @@ function Status:position() } end -function Status:progress(area, offset) - local progress = cx.tasks.progress - local left = progress.total - progress.succ - if left == 0 then - return {} - end - - local gauge = ui.Gauge(ui.Rect { - x = math.max(0, area.w - offset - 21), - y = area.y, - w = math.max(0, math.min(20, area.w - offset - 1)), - h = 1, - }) - - if progress.fail == 0 then - gauge = gauge:gauge_style(THEME.status.progress_normal) - else - gauge = gauge:gauge_style(THEME.status.progress_error) - end - - local percent = 99 - if progress.found ~= 0 then - percent = math.min(99, progress.processed * 100 / progress.found) - end - - return { - gauge - :percent(percent) - :label(ui.Span(string.format("%3d%%, %d left", percent, left)):style(THEME.status.progress_label)), - } -end - function Status:render(area) self.area = area local left = ui.Line { self:mode(), self:size(), self:name() } local right = ui.Line { self:permissions(), self:percentage(), self:position() } - local progress = self:progress(area, right:width()) return { ui.Paragraph(area, { left }), ui.Paragraph(area, { right }):align(ui.Paragraph.RIGHT), - table.unpack(progress), + table.unpack(Progress:render(area, right:width())), } end diff --git a/yazi-plugin/preset/plugins/image.lua b/yazi-plugin/preset/plugins/image.lua index 2ebfcb33..1a6a7707 100644 --- a/yazi-plugin/preset/plugins/image.lua +++ b/yazi-plugin/preset/plugins/image.lua @@ -2,7 +2,7 @@ local M = {} function M:peek() local url = ya.file_cache(self) - if not url or not fs.symlink_metadata(url) then + if not url or not fs.cha(url) then url = self.file.url end @@ -14,7 +14,7 @@ function M:seek() end function M:preload() local cache = ya.file_cache(self) - if not cache or fs.symlink_metadata(cache) then + if not cache or fs.cha(cache) then return 1 end diff --git a/yazi-plugin/preset/plugins/pdf.lua b/yazi-plugin/preset/plugins/pdf.lua index 62fea107..e0f447d3 100644 --- a/yazi-plugin/preset/plugins/pdf.lua +++ b/yazi-plugin/preset/plugins/pdf.lua @@ -22,7 +22,7 @@ end function M:preload() local cache = ya.file_cache(self) - if not cache or fs.symlink_metadata(cache) then + if not cache or fs.cha(cache) then return 1 end diff --git a/yazi-plugin/preset/plugins/video.lua b/yazi-plugin/preset/plugins/video.lua index 04695a13..3ad0e170 100644 --- a/yazi-plugin/preset/plugins/video.lua +++ b/yazi-plugin/preset/plugins/video.lua @@ -30,7 +30,7 @@ function M:preload() end local cache = ya.file_cache(self) - if not cache or fs.symlink_metadata(cache) then + if not cache or fs.cha(cache) then return 1 end diff --git a/yazi-plugin/preset/ya.lua b/yazi-plugin/preset/ya.lua index f93568c9..0c2158a6 100644 --- a/yazi-plugin/preset/ya.lua +++ b/yazi-plugin/preset/ya.lua @@ -12,6 +12,8 @@ function ya.clamp(min, x, max) end end +function ya.round(x) return x >= 0 and math.floor(x + 0.5) or math.ceil(x - 0.5) end + function ya.flat(t) local r = {} for _, v in ipairs(t) do diff --git a/yazi-plugin/src/elements/bar.rs b/yazi-plugin/src/elements/bar.rs index 7a19f55f..81c83d5e 100644 --- a/yazi-plugin/src/elements/bar.rs +++ b/yazi-plugin/src/elements/bar.rs @@ -62,6 +62,8 @@ impl UserData for Bar { } impl Renderable for Bar { + fn area(&self) -> ratatui::layout::Rect { self.area } + fn render(self: Box, buf: &mut ratatui::buffer::Buffer) { if self.area.area() == 0 { return; diff --git a/yazi-plugin/src/elements/border.rs b/yazi-plugin/src/elements/border.rs index 8c8ddae5..bf370edb 100644 --- a/yazi-plugin/src/elements/border.rs +++ b/yazi-plugin/src/elements/border.rs @@ -82,6 +82,8 @@ impl UserData for Border { } impl Renderable for Border { + fn area(&self) -> ratatui::layout::Rect { self.area } + fn render(self: Box, buf: &mut ratatui::buffer::Buffer) { let mut block = ratatui::widgets::Block::default().borders(self.position).border_type(self.type_); diff --git a/yazi-plugin/src/elements/elements.rs b/yazi-plugin/src/elements/elements.rs index 48e3341d..fa90c362 100644 --- a/yazi-plugin/src/elements/elements.rs +++ b/yazi-plugin/src/elements/elements.rs @@ -29,6 +29,8 @@ pub fn init(lua: &Lua) -> mlua::Result<()> { } pub trait Renderable { + fn area(&self) -> ratatui::layout::Rect; + fn render(self: Box, buf: &mut ratatui::buffer::Buffer); fn clone_render(&self, buf: &mut ratatui::buffer::Buffer); diff --git a/yazi-plugin/src/elements/gauge.rs b/yazi-plugin/src/elements/gauge.rs index d5c678f6..b22c55a2 100644 --- a/yazi-plugin/src/elements/gauge.rs +++ b/yazi-plugin/src/elements/gauge.rs @@ -70,6 +70,8 @@ impl UserData for Gauge { } impl Renderable for Gauge { + fn area(&self) -> ratatui::layout::Rect { self.area } + fn render(self: Box, buf: &mut ratatui::buffer::Buffer) { let mut gauge = ratatui::widgets::Gauge::default(); diff --git a/yazi-plugin/src/elements/list.rs b/yazi-plugin/src/elements/list.rs index 7e177297..becca99e 100644 --- a/yazi-plugin/src/elements/list.rs +++ b/yazi-plugin/src/elements/list.rs @@ -25,6 +25,8 @@ impl List { impl UserData for List {} impl Renderable for List { + fn area(&self) -> ratatui::layout::Rect { self.area } + fn render(self: Box, buf: &mut ratatui::buffer::Buffer) { self.inner.render(self.area, buf); } diff --git a/yazi-plugin/src/elements/paragraph.rs b/yazi-plugin/src/elements/paragraph.rs index 4db171f3..19da0967 100644 --- a/yazi-plugin/src/elements/paragraph.rs +++ b/yazi-plugin/src/elements/paragraph.rs @@ -71,6 +71,8 @@ impl UserData for Paragraph { } impl Renderable for Paragraph { + fn area(&self) -> ratatui::layout::Rect { self.area } + fn render(self: Box, buf: &mut ratatui::buffer::Buffer) { let mut p = ratatui::widgets::Paragraph::new(self.text); if let Some(style) = self.style { diff --git a/yazi-plugin/src/fs/fs.rs b/yazi-plugin/src/fs/fs.rs index 2384937f..54ac9321 100644 --- a/yazi-plugin/src/fs/fs.rs +++ b/yazi-plugin/src/fs/fs.rs @@ -1,4 +1,4 @@ -use mlua::Lua; +use mlua::{IntoLua, Lua, Value}; use tokio::fs; use crate::bindings::{Cast, Cha, UrlRef}; @@ -9,20 +9,29 @@ pub fn install(lua: &Lua) -> mlua::Result<()> { lua.create_table_from([ ( "write", - lua.create_async_function(|_, (url, data): (UrlRef, mlua::String)| async move { - Ok(fs::write(&*url, data).await.is_ok()) + lua.create_async_function(|lua, (url, data): (UrlRef, mlua::String)| async move { + Ok(match fs::write(&*url, data).await { + Ok(_) => (Value::Boolean(true), Value::Nil), + Err(e) => (Value::Boolean(false), e.raw_os_error().into_lua(lua)?), + }) })?, ), ( - "metadata", + "cha", lua.create_async_function(|lua, url: UrlRef| async move { - fs::metadata(&*url).await.ok().map(|m| Cha::cast(lua, m)).transpose() + Ok(match fs::symlink_metadata(&*url).await { + Ok(m) => (Cha::cast(lua, m)?.into_lua(lua)?, Value::Nil), + Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?), + }) })?, ), ( - "symlink_metadata", + "cha_follow", lua.create_async_function(|lua, url: UrlRef| async move { - fs::symlink_metadata(&*url).await.ok().map(|m| Cha::cast(lua, m)).transpose() + Ok(match fs::metadata(&*url).await { + Ok(m) => (Cha::cast(lua, m)?.into_lua(lua)?, Value::Nil), + Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?), + }) })?, ), ])?, diff --git a/yazi-plugin/src/plugin.rs b/yazi-plugin/src/plugin.rs index e463cb28..b7940f18 100644 --- a/yazi-plugin/src/plugin.rs +++ b/yazi-plugin/src/plugin.rs @@ -25,6 +25,7 @@ pub fn init() { lua.load(include_str!("../preset/components/manager.lua")).exec()?; lua.load(include_str!("../preset/components/parent.lua")).exec()?; lua.load(include_str!("../preset/components/preview.lua")).exec()?; + lua.load(include_str!("../preset/components/progress.lua")).exec()?; lua.load(include_str!("../preset/components/status.lua")).exec()?; Ok(()) diff --git a/yazi-shared/src/fs/fns.rs b/yazi-shared/src/fs/fns.rs index cfd1f6d7..1abd194a 100644 --- a/yazi-shared/src/fs/fns.rs +++ b/yazi-shared/src/fs/fns.rs @@ -61,7 +61,7 @@ pub fn copy_with_progress(from: &Path, to: &Path) -> mpsc::Receiver exit = Some(res.unwrap()), _ = tx.closed() => break, - _ = time::sleep(time::Duration::from_secs(1)) => (), + _ = time::sleep(time::Duration::from_secs(3)) => (), } match exit {