From cfc723863869ad526c2be10816195024e469365f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20misaki=20masa?= Date: Wed, 3 Sep 2025 22:25:16 +0800 Subject: [PATCH] feat: port task manager to Lua (#3134) --- Cargo.lock | 2 + Cargo.toml | 2 +- yazi-actor/Cargo.toml | 29 +++--- yazi-actor/src/lives/mod.rs | 2 +- yazi-actor/src/lives/task.rs | 37 ++++++++ yazi-actor/src/lives/tasks.rs | 20 ++++- yazi-actor/src/mgr/mod.rs | 1 - yazi-actor/src/tasks/mod.rs | 2 +- .../update_succeed.rs} | 10 +-- yazi-binding/src/elements/border.rs | 5 +- yazi-config/preset/theme-dark.toml | 2 +- yazi-config/preset/theme-light.toml | 2 +- yazi-config/src/layout.rs | 2 +- yazi-core/src/tab/folder.rs | 8 +- yazi-core/src/tasks/tasks.rs | 2 +- yazi-dds/src/spark/spark.rs | 6 +- yazi-fm/src/app/commands/mod.rs | 2 +- .../{update_summary.rs => update_progress.rs} | 4 +- yazi-fm/src/executor.rs | 5 +- yazi-fm/src/tasks/list.rs | 30 +++++++ yazi-fm/src/tasks/mod.rs | 2 +- yazi-fm/src/tasks/tasks.rs | 22 ++--- yazi-parser/src/app/mod.rs | 2 +- .../{update_summary.rs => update_progress.rs} | 4 +- yazi-parser/src/mgr/mod.rs | 1 - yazi-parser/src/tasks/mod.rs | 2 +- .../update_succeed.rs} | 10 +-- yazi-plugin/preset/components/progress.lua | 2 +- yazi-plugin/preset/components/tasks.lua | 22 +++++ yazi-plugin/src/lua.rs | 1 + yazi-proxy/src/app.rs | 4 +- yazi-proxy/src/mgr.rs | 4 - yazi-proxy/src/tasks.rs | 4 + yazi-scheduler/Cargo.toml | 1 + yazi-scheduler/src/file/progress.rs | 89 +++++++++---------- yazi-scheduler/src/plugin/progress.rs | 13 +-- yazi-scheduler/src/prework/progress.rs | 37 ++++---- yazi-scheduler/src/process/progress.rs | 37 ++++---- yazi-scheduler/src/progress.rs | 12 +-- yazi-scheduler/src/scheduler.rs | 6 +- yazi-scheduler/src/snap.rs | 9 +- 41 files changed, 283 insertions(+), 174 deletions(-) create mode 100644 yazi-actor/src/lives/task.rs rename yazi-actor/src/{mgr/update_tasks.rs => tasks/update_succeed.rs} (55%) rename yazi-fm/src/app/commands/{update_summary.rs => update_progress.rs} (85%) create mode 100644 yazi-fm/src/tasks/list.rs rename yazi-parser/src/app/{update_summary.rs => update_progress.rs} (88%) rename yazi-parser/src/{mgr/update_tasks.rs => tasks/update_succeed.rs} (71%) create mode 100644 yazi-plugin/preset/components/tasks.lua diff --git a/Cargo.lock b/Cargo.lock index 33447db7..a2e30974 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3510,6 +3510,7 @@ dependencies = [ "yazi-parser", "yazi-plugin", "yazi-proxy", + "yazi-scheduler", "yazi-shared", "yazi-term", "yazi-watcher", @@ -3870,6 +3871,7 @@ dependencies = [ "ordered-float 5.0.0", "parking_lot", "scopeguard", + "serde", "tokio", "tokio-util", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 98805d45..01503344 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ objc = "0.2.7" ordered-float = { version = "5.0.0", features = [ "serde" ] } parking_lot = "0.12.4" paste = "1.0.15" -ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info" ] } +ratatui = { version = "0.29.0", features = [ "unstable-rendered-line-info", "unstable-widget-ref" ] } regex = "1.11.2" scopeguard = "1.2.0" serde = { version = "1.0.219", features = [ "derive" ] } diff --git a/yazi-actor/Cargo.toml b/yazi-actor/Cargo.toml index 489dc72f..dacc343d 100644 --- a/yazi-actor/Cargo.toml +++ b/yazi-actor/Cargo.toml @@ -13,20 +13,21 @@ default = [ "vendored-lua" ] vendored-lua = [ "mlua/vendored" ] [dependencies] -yazi-binding = { path = "../yazi-binding", version = "25.6.11" } -yazi-boot = { path = "../yazi-boot", version = "25.6.11" } -yazi-config = { path = "../yazi-config", version = "25.6.11" } -yazi-core = { path = "../yazi-core", version = "25.6.11" } -yazi-dds = { path = "../yazi-dds", version = "25.6.11" } -yazi-fs = { path = "../yazi-fs", version = "25.6.11" } -yazi-macro = { path = "../yazi-macro", version = "25.6.11" } -yazi-parser = { path = "../yazi-parser", version = "25.6.11" } -yazi-plugin = { path = "../yazi-plugin", version = "25.6.11" } -yazi-proxy = { path = "../yazi-proxy", version = "25.6.11" } -yazi-shared = { path = "../yazi-shared", version = "25.6.11" } -yazi-term = { path = "../yazi-term", version = "25.6.11" } -yazi-watcher = { path = "../yazi-watcher", version = "25.6.11" } -yazi-widgets = { path = "../yazi-widgets", version = "25.6.11" } +yazi-binding = { path = "../yazi-binding", version = "25.6.11" } +yazi-boot = { path = "../yazi-boot", version = "25.6.11" } +yazi-config = { path = "../yazi-config", version = "25.6.11" } +yazi-core = { path = "../yazi-core", version = "25.6.11" } +yazi-dds = { path = "../yazi-dds", version = "25.6.11" } +yazi-fs = { path = "../yazi-fs", version = "25.6.11" } +yazi-macro = { path = "../yazi-macro", version = "25.6.11" } +yazi-parser = { path = "../yazi-parser", version = "25.6.11" } +yazi-plugin = { path = "../yazi-plugin", version = "25.6.11" } +yazi-proxy = { path = "../yazi-proxy", version = "25.6.11" } +yazi-scheduler = { path = "../yazi-scheduler", version = "25.6.11" } +yazi-shared = { path = "../yazi-shared", version = "25.6.11" } +yazi-term = { path = "../yazi-term", version = "25.6.11" } +yazi-watcher = { path = "../yazi-watcher", version = "25.6.11" } +yazi-widgets = { path = "../yazi-widgets", version = "25.6.11" } # External dependencies anyhow = { workspace = true } diff --git a/yazi-actor/src/lives/mod.rs b/yazi-actor/src/lives/mod.rs index aed480e1..50f66a95 100644 --- a/yazi-actor/src/lives/mod.rs +++ b/yazi-actor/src/lives/mod.rs @@ -1,3 +1,3 @@ #![allow(clippy::module_inception)] -yazi_macro::mod_flat!(core file files filter finder folder lives mode preference preview ptr selected tab tabs tasks yanked); +yazi_macro::mod_flat!(core file files filter finder folder lives mode preference preview ptr selected tab tabs task tasks yanked); diff --git a/yazi-actor/src/lives/task.rs b/yazi-actor/src/lives/task.rs new file mode 100644 index 00000000..12b5f560 --- /dev/null +++ b/yazi-actor/src/lives/task.rs @@ -0,0 +1,37 @@ +use std::ops::Deref; + +use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value}; +use yazi_binding::cached_field; +use yazi_plugin::runtime::SER_OPT; + +use super::{Lives, PtrCell}; + +pub(super) struct TaskSnap { + inner: PtrCell, + + v_name: Option, + v_prog: Option, +} + +impl Deref for TaskSnap { + type Target = yazi_scheduler::TaskSnap; + + fn deref(&self) -> &Self::Target { &self.inner } +} + +impl TaskSnap { + pub(super) fn make(inner: &yazi_scheduler::TaskSnap) -> mlua::Result { + Lives::scoped_userdata(Self { inner: inner.into(), v_name: None, v_prog: None }) + } +} + +impl UserData for TaskSnap { + fn add_fields>(fields: &mut F) { + cached_field!(fields, name, |lua, me| lua.create_string(&me.name)); + cached_field!(fields, prog, |lua, me| lua.to_value_with(&me.prog, SER_OPT)); + + fields.add_field_method_get("running", |_, me| Ok(me.prog.running())); + fields.add_field_method_get("success", |_, me| Ok(me.prog.success())); + fields.add_field_method_get("percent", |_, me| Ok(me.prog.percent())); + } +} diff --git a/yazi-actor/src/lives/tasks.rs b/yazi-actor/src/lives/tasks.rs index a5b39f41..9d264484 100644 --- a/yazi-actor/src/lives/tasks.rs +++ b/yazi-actor/src/lives/tasks.rs @@ -5,10 +5,12 @@ use yazi_binding::{cached_field, deprecate}; use yazi_plugin::runtime::SER_OPT; use super::{Lives, PtrCell}; +use crate::lives::TaskSnap; pub(super) struct Tasks { inner: PtrCell, + v_snaps: Option, v_summary: Option, v_progress: Option, } @@ -21,12 +23,28 @@ impl Deref for Tasks { impl Tasks { pub(super) fn make(inner: &yazi_core::tasks::Tasks) -> mlua::Result { - Lives::scoped_userdata(Self { inner: inner.into(), v_summary: None, v_progress: None }) + Lives::scoped_userdata(Self { + inner: inner.into(), + + v_snaps: None, + v_summary: None, + v_progress: None, + }) } } impl UserData for Tasks { fn add_fields>(fields: &mut F) { + fields.add_field_method_get("cursor", |_, me| Ok(me.cursor)); + + cached_field!(fields, snaps, |lua, me| { + let tbl = lua.create_table_with_capacity(me.snaps.len(), 0)?; + for snap in &me.snaps { + tbl.raw_push(TaskSnap::make(snap)?)?; + } + Ok(tbl) + }); + cached_field!(fields, summary, |lua, me| lua.to_value_with(&me.summary, SER_OPT)); cached_field!(fields, progress, |lua, me| { diff --git a/yazi-actor/src/mgr/mod.rs b/yazi-actor/src/mgr/mod.rs index 1499db7e..88ff46c4 100644 --- a/yazi-actor/src/mgr/mod.rs +++ b/yazi-actor/src/mgr/mod.rs @@ -47,7 +47,6 @@ yazi_macro::mod_flat!( update_paged update_peeked update_spotted - update_tasks update_yanked visual_mode watch diff --git a/yazi-actor/src/tasks/mod.rs b/yazi-actor/src/tasks/mod.rs index 18559820..83ce757d 100644 --- a/yazi-actor/src/tasks/mod.rs +++ b/yazi-actor/src/tasks/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(arrow cancel close inspect open_with process_exec show); +yazi_macro::mod_flat!(arrow cancel close inspect open_with process_exec show update_succeed); diff --git a/yazi-actor/src/mgr/update_tasks.rs b/yazi-actor/src/tasks/update_succeed.rs similarity index 55% rename from yazi-actor/src/mgr/update_tasks.rs rename to yazi-actor/src/tasks/update_succeed.rs index 6ea016d5..e10b7dcd 100644 --- a/yazi-actor/src/mgr/update_tasks.rs +++ b/yazi-actor/src/tasks/update_succeed.rs @@ -1,16 +1,16 @@ use anyhow::Result; use yazi_macro::succ; -use yazi_parser::mgr::UpdateTasksOpt; +use yazi_parser::tasks::UpdateSucceedOpt; use yazi_shared::event::Data; use crate::{Actor, Ctx}; -pub struct UpdateTasks; +pub struct UpdateSucceed; -impl Actor for UpdateTasks { - type Options = UpdateTasksOpt; +impl Actor for UpdateSucceed { + type Options = UpdateSucceedOpt; - const NAME: &str = "update_tasks"; + const NAME: &str = "update_succeed"; fn act(cx: &mut Ctx, opt: Self::Options) -> Result { cx.mgr.watcher.push_files(opt.urls); diff --git a/yazi-binding/src/elements/border.rs b/yazi-binding/src/elements/border.rs index c4a39421..0767fe49 100644 --- a/yazi-binding/src/elements/border.rs +++ b/yazi-binding/src/elements/border.rs @@ -25,8 +25,9 @@ pub struct Border { impl Border { pub fn compose(lua: &Lua) -> mlua::Result { - let new = lua - .create_function(|_, (_, edge): (Table, Edge)| Ok(Border { edge, ..Default::default() }))?; + let new = lua.create_function(|_, (_, edge): (Table, Edge)| { + Ok(Border { edge, r#type: ratatui::widgets::BorderType::Rounded, ..Default::default() }) + })?; let border = lua.create_table_from([ // Type diff --git a/yazi-config/preset/theme-dark.toml b/yazi-config/preset/theme-dark.toml index b82362db..b9871abc 100644 --- a/yazi-config/preset/theme-dark.toml +++ b/yazi-config/preset/theme-dark.toml @@ -100,7 +100,7 @@ perm_exec = { fg = "cyan" } # Progress progress_label = { bold = true } progress_normal = { fg = "blue", bg = "black" } -progress_error = { fg = "red", bg = "black" } +progress_error = { fg = "blue", bg = "red" } # : }}} diff --git a/yazi-config/preset/theme-light.toml b/yazi-config/preset/theme-light.toml index 60342931..9efe0e07 100644 --- a/yazi-config/preset/theme-light.toml +++ b/yazi-config/preset/theme-light.toml @@ -100,7 +100,7 @@ perm_exec = { fg = "cyan" } # Progress progress_label = { bold = true } progress_normal = { fg = "blue", bg = "black" } -progress_error = { fg = "red", bg = "black" } +progress_error = { fg = "blue", bg = "red" } # : }}} diff --git a/yazi-config/src/layout.rs b/yazi-config/src/layout.rs index 46f39d0c..b25d44a0 100644 --- a/yazi-config/src/layout.rs +++ b/yazi-config/src/layout.rs @@ -12,5 +12,5 @@ impl Layout { Self { current: Rect::ZERO, preview: Rect::ZERO, progress: Rect::ZERO } } - pub const fn limit(&self) -> usize { self.current.height as _ } + pub const fn folder_limit(self) -> usize { self.current.height as _ } } diff --git a/yazi-core/src/tab/folder.rs b/yazi-core/src/tab/folder.rs index 11d693d7..54d2f896 100644 --- a/yazi-core/src/tab/folder.rs +++ b/yazi-core/src/tab/folder.rs @@ -130,7 +130,7 @@ impl Folder { } pub fn sync_page(&mut self, force: bool) { - let limit = LAYOUT.get().limit(); + let limit = LAYOUT.get().folder_limit(); if limit == 0 { return; } @@ -145,7 +145,7 @@ impl Folder { let old = self.offset; let len = self.files.len(); - let limit = LAYOUT.get().limit(); + let limit = LAYOUT.get().folder_limit(); let scrolloff = (limit / 2).min(YAZI.mgr.scrolloff.get() as usize); self.offset = if self.cursor < (self.offset + limit).min(len).saturating_sub(scrolloff) { @@ -167,7 +167,7 @@ impl Folder { pub fn paginate(&self, page: usize) -> &[File] { let len = self.files.len(); - let limit = LAYOUT.get().limit(); + let limit = LAYOUT.get().folder_limit(); let start = (page.saturating_sub(1) * limit).min(len.saturating_sub(1)); let end = ((page + 2) * limit).min(len); @@ -178,7 +178,7 @@ impl Folder { impl Scrollable for Folder { fn total(&self) -> usize { self.files.len() } - fn limit(&self) -> usize { LAYOUT.get().limit() } + fn limit(&self) -> usize { LAYOUT.get().folder_limit() } fn scrolloff(&self) -> usize { (self.limit() / 2).min(YAZI.mgr.scrolloff.get() as usize) } diff --git a/yazi-core/src/tasks/tasks.rs b/yazi-core/src/tasks/tasks.rs index 79e6e0e8..1258f51d 100644 --- a/yazi-core/src/tasks/tasks.rs +++ b/yazi-core/src/tasks/tasks.rs @@ -32,7 +32,7 @@ impl Tasks { let new = ongoing.lock().summary(); if last != new { last = new; - AppProxy::update_summary(new); + AppProxy::update_progress(new); } } }); diff --git a/yazi-dds/src/spark/spark.rs b/yazi-dds/src/spark/spark.rs index 27f47b06..e5c6eb36 100644 --- a/yazi-dds/src/spark/spark.rs +++ b/yazi-dds/src/spark/spark.rs @@ -66,7 +66,6 @@ pub enum Spark<'a> { UpdatePaged(yazi_parser::mgr::UpdatePagedOpt), UpdatePeeked(yazi_parser::mgr::UpdatePeekedOpt), UpdateSpotted(yazi_parser::mgr::UpdateSpottedOpt), - UpdateTasks(yazi_parser::mgr::UpdateTasksOpt), UpdateYanked(yazi_parser::mgr::UpdateYankedOpt<'a>), VisualMode(yazi_parser::mgr::VisualModeOpt), Watch(yazi_parser::VoidOpt), @@ -109,6 +108,7 @@ pub enum Spark<'a> { // Tasks TasksProcessExec(yazi_parser::tasks::ProcessExecOpt), + TasksUpdateSucceed(yazi_parser::tasks::UpdateSucceedOpt), // Which WhichCallback(yazi_parser::which::CallbackOpt), @@ -188,7 +188,6 @@ impl<'a> IntoLua for Spark<'a> { Self::UpdatePaged(b) => b.into_lua(lua), Self::UpdatePeeked(b) => b.into_lua(lua), Self::UpdateSpotted(b) => b.into_lua(lua), - Self::UpdateTasks(b) => b.into_lua(lua), Self::UpdateYanked(b) => b.into_lua(lua), Self::VisualMode(b) => b.into_lua(lua), Self::Watch(b) => b.into_lua(lua), @@ -231,6 +230,7 @@ impl<'a> IntoLua for Spark<'a> { // Tasks Self::TasksProcessExec(b) => b.into_lua(lua), + Self::TasksUpdateSucceed(b) => b.into_lua(lua), // Which Self::WhichCallback(b) => b.into_lua(lua), @@ -314,7 +314,6 @@ try_from_spark!(mgr::UpdateMimesOpt, mgr:update_mimes); try_from_spark!(mgr::UpdatePagedOpt, mgr:update_paged); try_from_spark!(mgr::UpdatePeekedOpt, mgr:update_peeked); try_from_spark!(mgr::UpdateSpottedOpt, mgr:update_spotted); -try_from_spark!(mgr::UpdateTasksOpt, mgr:update_tasks); try_from_spark!(mgr::UpdateYankedOpt<'a>, mgr:update_yanked); try_from_spark!(mgr::VisualModeOpt, mgr:visual_mode); try_from_spark!(mgr::YankOpt, mgr:yank); @@ -323,5 +322,6 @@ try_from_spark!(pick::CloseOpt, pick:close); try_from_spark!(pick::ShowOpt, pick:show); try_from_spark!(spot::CopyOpt, spot:copy); try_from_spark!(tasks::ProcessExecOpt, tasks:process_exec); +try_from_spark!(tasks::UpdateSucceedOpt, tasks:update_succeed); try_from_spark!(which::CallbackOpt, which:callback); try_from_spark!(which::ShowOpt, which:show); diff --git a/yazi-fm/src/app/commands/mod.rs b/yazi-fm/src/app/commands/mod.rs index 95f461d1..0357cf32 100644 --- a/yazi-fm/src/app/commands/mod.rs +++ b/yazi-fm/src/app/commands/mod.rs @@ -12,5 +12,5 @@ yazi_macro::mod_flat!( resume stop update_notify - update_summary + update_progress ); diff --git a/yazi-fm/src/app/commands/update_summary.rs b/yazi-fm/src/app/commands/update_progress.rs similarity index 85% rename from yazi-fm/src/app/commands/update_summary.rs rename to yazi-fm/src/app/commands/update_progress.rs index 7da78fc0..b406fde8 100644 --- a/yazi-fm/src/app/commands/update_summary.rs +++ b/yazi-fm/src/app/commands/update_progress.rs @@ -1,13 +1,13 @@ use anyhow::Result; use yazi_actor::Ctx; use yazi_macro::{act, render, succ}; -use yazi_parser::app::UpdateSummaryOpt; +use yazi_parser::app::UpdateProgressOpt; use yazi_shared::event::Data; use crate::app::App; impl App { - pub(crate) fn update_summary(&mut self, opt: UpdateSummaryOpt) -> Result { + pub(crate) fn update_progress(&mut self, opt: UpdateProgressOpt) -> Result { // Update the progress of all tasks. let tasks = &mut self.core.tasks; let progressed = tasks.summary != opt.summary; diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 4247dc4d..5dfea864 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -44,7 +44,7 @@ impl<'a> Executor<'a> { on!(plugin); on!(plugin_do); on!(update_notify); - on!(update_summary); + on!(update_progress); on!(resize); on!(stop); on!(resume); @@ -65,7 +65,6 @@ impl<'a> Executor<'a> { } on!(cd); - on!(update_tasks); on!(update_yanked); on!(update_files); @@ -155,6 +154,8 @@ impl<'a> Executor<'a> { }; } + on!(update_succeed); + on!(show); on!(close); on!(arrow); diff --git a/yazi-fm/src/tasks/list.rs b/yazi-fm/src/tasks/list.rs new file mode 100644 index 00000000..b09c46be --- /dev/null +++ b/yazi-fm/src/tasks/list.rs @@ -0,0 +1,30 @@ +use mlua::{ObjectLike, Table}; +use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; +use tracing::error; +use yazi_binding::elements::render_once; +use yazi_core::Core; +use yazi_plugin::LUA; + +pub(crate) struct List<'a> { + core: &'a Core, +} + +impl<'a> List<'a> { + #[inline] + pub(crate) fn new(core: &'a Core) -> Self { Self { core } } +} + +impl Widget for List<'_> { + fn render(self, area: Rect, buf: &mut Buffer) { + let mut f = || { + let area = yazi_binding::elements::Rect::from(area); + let root = LUA.globals().raw_get::("Tasks")?.call_method::
("new", area)?; + + render_once(root.call_method("redraw", ())?, buf, |p| self.core.mgr.area(p)); + Ok::<_, mlua::Error>(()) + }; + if let Err(e) = f() { + error!("Failed to redraw the `Tasks` component:\n{e}"); + } + } +} diff --git a/yazi-fm/src/tasks/mod.rs b/yazi-fm/src/tasks/mod.rs index 52d57d37..4ebb289b 100644 --- a/yazi-fm/src/tasks/mod.rs +++ b/yazi-fm/src/tasks/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(progress tasks); +yazi_macro::mod_flat!(list progress tasks); diff --git a/yazi-fm/src/tasks/tasks.rs b/yazi-fm/src/tasks/tasks.rs index ca323cbb..d334065e 100644 --- a/yazi-fm/src/tasks/tasks.rs +++ b/yazi-fm/src/tasks/tasks.rs @@ -1,7 +1,9 @@ -use ratatui::{buffer::Buffer, layout::{self, Alignment, Constraint, Rect}, text::{Line, Text}, widgets::{Block, BorderType, List, Padding, Widget}}; +use ratatui::{buffer::Buffer, layout::{self, Alignment, Constraint, Rect}, text::Line, widgets::{Block, BorderType, Widget, WidgetRef}}; use yazi_config::THEME; use yazi_core::{Core, tasks::TASKS_PERCENT}; +use crate::tasks::List; + pub(crate) struct Tasks<'a> { core: &'a Core, } @@ -31,26 +33,14 @@ impl Widget for Tasks<'_> { let area = Self::area(area); yazi_binding::elements::Clear::default().render(area, buf); + let block = Block::bordered() .title(Line::styled("Tasks", THEME.tasks.title)) .title_alignment(Alignment::Center) - .padding(Padding::symmetric(1, 1)) .border_type(BorderType::Rounded) .border_style(THEME.tasks.border); + block.render_ref(area, buf); - let inner = block.inner(area); - block.render(area, buf); - - let tasks = &self.core.tasks; - let items = tasks.snaps.iter().take(inner.height as usize).enumerate().map(|(i, v)| { - let mut item = - Text::from_iter(textwrap::wrap(&v.name, inner.width as usize).into_iter().map(Line::from)); - if i == tasks.cursor { - item = item.style(THEME.tasks.hovered); - } - item - }); - - List::new(items).render(inner, buf); + List::new(self.core).render(block.inner(area), buf); } } diff --git a/yazi-parser/src/app/mod.rs b/yazi-parser/src/app/mod.rs index 030ea1e0..66ed1206 100644 --- a/yazi-parser/src/app/mod.rs +++ b/yazi-parser/src/app/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(deprecate mouse notify plugin stop update_summary); +yazi_macro::mod_flat!(deprecate mouse notify plugin stop update_progress); diff --git a/yazi-parser/src/app/update_summary.rs b/yazi-parser/src/app/update_progress.rs similarity index 88% rename from yazi-parser/src/app/update_summary.rs rename to yazi-parser/src/app/update_progress.rs index 822be4f3..5138dc80 100644 --- a/yazi-parser/src/app/update_summary.rs +++ b/yazi-parser/src/app/update_progress.rs @@ -3,11 +3,11 @@ use ordered_float::OrderedFloat; use serde::Serialize; use yazi_shared::event::CmdCow; -pub struct UpdateSummaryOpt { +pub struct UpdateProgressOpt { pub summary: TaskSummary, } -impl TryFrom for UpdateSummaryOpt { +impl TryFrom for UpdateProgressOpt { type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { diff --git a/yazi-parser/src/mgr/mod.rs b/yazi-parser/src/mgr/mod.rs index 19dcdf46..59e083df 100644 --- a/yazi-parser/src/mgr/mod.rs +++ b/yazi-parser/src/mgr/mod.rs @@ -37,7 +37,6 @@ yazi_macro::mod_flat!( update_paged update_peeked update_spotted - update_tasks update_yanked visual_mode yank diff --git a/yazi-parser/src/tasks/mod.rs b/yazi-parser/src/tasks/mod.rs index 330c538f..9a6a8e2e 100644 --- a/yazi-parser/src/tasks/mod.rs +++ b/yazi-parser/src/tasks/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(process_exec); +yazi_macro::mod_flat!(process_exec update_succeed); diff --git a/yazi-parser/src/mgr/update_tasks.rs b/yazi-parser/src/tasks/update_succeed.rs similarity index 71% rename from yazi-parser/src/mgr/update_tasks.rs rename to yazi-parser/src/tasks/update_succeed.rs index c2397d15..382923f0 100644 --- a/yazi-parser/src/mgr/update_tasks.rs +++ b/yazi-parser/src/tasks/update_succeed.rs @@ -3,26 +3,26 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use yazi_shared::{event::CmdCow, url::UrlBuf}; #[derive(Debug)] -pub struct UpdateTasksOpt { +pub struct UpdateSucceedOpt { pub urls: Vec, } -impl TryFrom for UpdateTasksOpt { +impl TryFrom for UpdateSucceedOpt { type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { let Some(urls) = c.take_any("urls") else { - bail!("Invalid 'urls' argument in UpdateTasksOpt"); + bail!("Invalid 'urls' argument in UpdateSucceedOpt"); }; Ok(Self { urls }) } } -impl FromLua for UpdateTasksOpt { +impl FromLua for UpdateSucceedOpt { fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } } -impl IntoLua for UpdateTasksOpt { +impl IntoLua for UpdateSucceedOpt { fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } } diff --git a/yazi-plugin/preset/components/progress.lua b/yazi-plugin/preset/components/progress.lua index 8c4b3fc1..59ffde1c 100644 --- a/yazi-plugin/preset/components/progress.lua +++ b/yazi-plugin/preset/components/progress.lua @@ -38,7 +38,7 @@ function Progress:redraw() if percent then label = string.format("%3d%%, ", math.floor(percent)) else - percent = 99 + percent = 0 end label = label .. string.format("%d left", summary.total - summary.success) diff --git a/yazi-plugin/preset/components/tasks.lua b/yazi-plugin/preset/components/tasks.lua new file mode 100644 index 00000000..120eca91 --- /dev/null +++ b/yazi-plugin/preset/components/tasks.lua @@ -0,0 +1,22 @@ +Tasks = { + _id = "tasks", +} + +function Tasks:new(area) return setmetatable({ _area = area }, { __index = self }) end + +function Tasks:reflow() return { self } end + +function Tasks:redraw() + local rows = {} + for _, snap in ipairs(cx.tasks.snaps) do + rows[#rows + 1] = ui.Row { snap.name } + end + + local tbl = ui.Table(rows) + :area(self._area:pad(ui.Pad.x(1))) + :row(cx.tasks.cursor) + :row_style(th.tasks.hovered) + :widths { ui.Constraint.Fill(1) } + + return { tbl } +end diff --git a/yazi-plugin/src/lua.rs b/yazi-plugin/src/lua.rs index f63f860e..44706033 100644 --- a/yazi-plugin/src/lua.rs +++ b/yazi-plugin/src/lua.rs @@ -52,6 +52,7 @@ fn stage_1(lua: &'static Lua) -> Result<()> { lua.load(preset!("components/status")).set_name("status.lua").exec()?; lua.load(preset!("components/tab")).set_name("tab.lua").exec()?; lua.load(preset!("components/tabs")).set_name("tabs.lua").exec()?; + lua.load(preset!("components/tasks")).set_name("tasks.lua").exec()?; Ok(()) } diff --git a/yazi-proxy/src/app.rs b/yazi-proxy/src/app.rs index b63c59a5..c2d6d7b1 100644 --- a/yazi-proxy/src/app.rs +++ b/yazi-proxy/src/app.rs @@ -51,7 +51,7 @@ impl AppProxy { emit!(Call(relay!(app:plugin_do).with_any("opt", opt))); } - pub fn update_summary(summary: TaskSummary) { - emit!(Call(relay!(app:update_summary).with_any("summary", summary))); + pub fn update_progress(summary: TaskSummary) { + emit!(Call(relay!(app:update_progress).with_any("summary", summary))); } } diff --git a/yazi-proxy/src/mgr.rs b/yazi-proxy/src/mgr.rs index f33e42b3..3ca21632 100644 --- a/yazi-proxy/src/mgr.rs +++ b/yazi-proxy/src/mgr.rs @@ -54,10 +54,6 @@ impl MgrProxy { emit!(Call(relay!(mgr:update_spotted).with_any("opt", opt))); } - pub fn update_tasks(url: &UrlBuf) { - emit!(Call(relay!(mgr:update_tasks).with_any("urls", vec![url.clone()]))); - } - pub fn update_paged_by(page: usize, only_if: &UrlBuf) { emit!(Call(relay!(mgr:update_paged, [page]).with_any("only-if", only_if.clone()))); } diff --git a/yazi-proxy/src/tasks.rs b/yazi-proxy/src/tasks.rs index 54ee8fba..a74e5af8 100644 --- a/yazi-proxy/src/tasks.rs +++ b/yazi-proxy/src/tasks.rs @@ -23,4 +23,8 @@ impl TasksProxy { }))); rx.await.ok(); } + + pub fn update_succeed(url: impl Into) { + emit!(Call(relay!(tasks:update_succeed).with_any("urls", vec![url.into()]))); + } } diff --git a/yazi-scheduler/Cargo.toml b/yazi-scheduler/Cargo.toml index 4459e31f..941c66b4 100644 --- a/yazi-scheduler/Cargo.toml +++ b/yazi-scheduler/Cargo.toml @@ -28,6 +28,7 @@ mlua = { workspace = true } ordered-float = { workspace = true } parking_lot = { workspace = true } scopeguard = { workspace = true } +serde = { workspace = true } tokio = { workspace = true } tokio-util = { workspace = true } tracing = { workspace = true } diff --git a/yazi-scheduler/src/file/progress.rs b/yazi-scheduler/src/file/progress.rs index 116069e7..e1055c68 100644 --- a/yazi-scheduler/src/file/progress.rs +++ b/yazi-scheduler/src/file/progress.rs @@ -1,14 +1,15 @@ +use serde::Serialize; use yazi_parser::app::TaskSummary; // --- Paste -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct FileProgPaste { - pub(crate) total_files: u32, - pub(crate) success_files: u32, - pub(crate) failed_files: u32, - pub(crate) total_bytes: u64, - pub(crate) processed_bytes: u64, - pub(crate) collected: bool, +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] +pub struct FileProgPaste { + pub total_files: u32, + pub success_files: u32, + pub failed_files: u32, + pub total_bytes: u64, + pub processed_bytes: u64, + pub collected: bool, } impl From for TaskSummary { @@ -23,13 +24,13 @@ impl From for TaskSummary { } impl FileProgPaste { - pub(crate) fn running(self) -> bool { + pub fn running(self) -> bool { !self.collected || self.success_files + self.failed_files != self.total_files } - pub(crate) fn success(self) -> bool { self.collected && self.success_files == self.total_files } + pub fn success(self) -> bool { self.collected && self.success_files == self.total_files } - pub(crate) fn percent(self) -> Option { + pub fn percent(self) -> Option { Some(if self.success() { 100.0 } else if self.total_bytes == 0 { @@ -41,9 +42,9 @@ impl FileProgPaste { } // --- Link -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct FileProgLink { - pub(crate) state: Option, +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] +pub struct FileProgLink { + pub state: Option, } impl From for TaskSummary { @@ -58,20 +59,20 @@ impl From for TaskSummary { } impl FileProgLink { - pub(crate) fn running(self) -> bool { self.state.is_none() } + pub fn running(self) -> bool { self.state.is_none() } - pub(crate) fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.state == Some(true) } - pub(crate) fn percent(self) -> Option { None } + pub fn percent(self) -> Option { None } } // --- Hardlink -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct FileProgHardlink { - pub(crate) total: u32, - pub(crate) success: u32, - pub(crate) failed: u32, - pub(crate) collected: bool, +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] +pub struct FileProgHardlink { + pub total: u32, + pub success: u32, + pub failed: u32, + pub collected: bool, } impl From for TaskSummary { @@ -86,24 +87,22 @@ impl From for TaskSummary { } impl FileProgHardlink { - pub(crate) fn running(self) -> bool { - !self.collected || self.success + self.failed != self.total - } + pub fn running(self) -> bool { !self.collected || self.success + self.failed != self.total } - pub(crate) fn success(self) -> bool { self.collected && self.success == self.total } + pub fn success(self) -> bool { self.collected && self.success == self.total } - pub(crate) fn percent(self) -> Option { None } + pub fn percent(self) -> Option { None } } // --- Delete -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct FileProgDelete { - pub(crate) total_files: u32, - pub(crate) success_files: u32, - pub(crate) failed_files: u32, - pub(crate) total_bytes: u64, - pub(crate) processed_bytes: u64, - pub(crate) collected: bool, +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] +pub struct FileProgDelete { + pub total_files: u32, + pub success_files: u32, + pub failed_files: u32, + pub total_bytes: u64, + pub processed_bytes: u64, + pub collected: bool, } impl From for TaskSummary { @@ -118,13 +117,13 @@ impl From for TaskSummary { } impl FileProgDelete { - pub(crate) fn running(self) -> bool { + pub fn running(self) -> bool { !self.collected || self.success_files + self.failed_files != self.total_files } - pub(crate) fn success(self) -> bool { self.collected && self.success_files == self.total_files } + pub fn success(self) -> bool { self.collected && self.success_files == self.total_files } - pub(crate) fn percent(self) -> Option { + pub fn percent(self) -> Option { Some(if self.success() { 100.0 } else if self.total_bytes == 0 { @@ -136,9 +135,9 @@ impl FileProgDelete { } // --- Trash -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct FileProgTrash { - pub(crate) state: Option, +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] +pub struct FileProgTrash { + pub state: Option, } impl From for TaskSummary { @@ -153,9 +152,9 @@ impl From for TaskSummary { } impl FileProgTrash { - pub(crate) fn running(self) -> bool { self.state.is_none() } + pub fn running(self) -> bool { self.state.is_none() } - pub(crate) fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.state == Some(true) } - pub(crate) fn percent(self) -> Option { None } + pub fn percent(self) -> Option { None } } diff --git a/yazi-scheduler/src/plugin/progress.rs b/yazi-scheduler/src/plugin/progress.rs index f303a469..ed33541d 100644 --- a/yazi-scheduler/src/plugin/progress.rs +++ b/yazi-scheduler/src/plugin/progress.rs @@ -1,9 +1,10 @@ +use serde::Serialize; use yazi_parser::app::TaskSummary; // --- Entry -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct PluginProgEntry { - pub(crate) state: Option, +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] +pub struct PluginProgEntry { + pub state: Option, } impl From for TaskSummary { @@ -18,9 +19,9 @@ impl From for TaskSummary { } impl PluginProgEntry { - pub(crate) fn running(self) -> bool { self.state.is_none() } + pub fn running(self) -> bool { self.state.is_none() } - pub(crate) fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.state == Some(true) } - pub(crate) fn percent(self) -> Option { None } + pub fn percent(self) -> Option { None } } diff --git a/yazi-scheduler/src/prework/progress.rs b/yazi-scheduler/src/prework/progress.rs index 5048c752..1c2a45a4 100644 --- a/yazi-scheduler/src/prework/progress.rs +++ b/yazi-scheduler/src/prework/progress.rs @@ -1,9 +1,10 @@ +use serde::Serialize; use yazi_parser::app::TaskSummary; // --- Fetch -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct PreworkProgFetch { - pub(crate) state: Option, +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] +pub struct PreworkProgFetch { + pub state: Option, } impl From for TaskSummary { @@ -18,17 +19,17 @@ impl From for TaskSummary { } impl PreworkProgFetch { - pub(crate) fn running(self) -> bool { self.state.is_none() } + pub fn running(self) -> bool { self.state.is_none() } - pub(crate) fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.state == Some(true) } - pub(crate) fn percent(self) -> Option { None } + pub fn percent(self) -> Option { None } } // --- Load -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct PreworkProgLoad { - pub(crate) state: Option, +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] +pub struct PreworkProgLoad { + pub state: Option, } impl From for TaskSummary { @@ -43,17 +44,17 @@ impl From for TaskSummary { } impl PreworkProgLoad { - pub(crate) fn running(self) -> bool { self.state.is_none() } + pub fn running(self) -> bool { self.state.is_none() } - pub(crate) fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.state == Some(true) } - pub(crate) fn percent(self) -> Option { None } + pub fn percent(self) -> Option { None } } // --- Size -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct PreworkProgSize { - pub(crate) done: bool, +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] +pub struct PreworkProgSize { + pub done: bool, } impl From for TaskSummary { @@ -68,9 +69,9 @@ impl From for TaskSummary { } impl PreworkProgSize { - pub(crate) fn running(self) -> bool { !self.done } + pub fn running(self) -> bool { !self.done } - pub(crate) fn success(self) -> bool { self.done } + pub fn success(self) -> bool { self.done } - pub(crate) fn percent(self) -> Option { None } + pub fn percent(self) -> Option { None } } diff --git a/yazi-scheduler/src/process/progress.rs b/yazi-scheduler/src/process/progress.rs index f44b208b..a17a5572 100644 --- a/yazi-scheduler/src/process/progress.rs +++ b/yazi-scheduler/src/process/progress.rs @@ -1,9 +1,10 @@ +use serde::Serialize; use yazi_parser::app::TaskSummary; // --- Block -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct ProcessProgBlock { - pub(crate) state: Option, +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] +pub struct ProcessProgBlock { + pub state: Option, } impl From for TaskSummary { @@ -18,17 +19,17 @@ impl From for TaskSummary { } impl ProcessProgBlock { - pub(crate) fn running(self) -> bool { self.state.is_none() } + pub fn running(self) -> bool { self.state.is_none() } - pub(crate) fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.state == Some(true) } - pub(crate) fn percent(self) -> Option { None } + pub fn percent(self) -> Option { None } } // --- Orphan -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct ProcessProgOrphan { - pub(crate) state: Option, +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] +pub struct ProcessProgOrphan { + pub state: Option, } impl From for TaskSummary { @@ -43,17 +44,17 @@ impl From for TaskSummary { } impl ProcessProgOrphan { - pub(crate) fn running(self) -> bool { self.state.is_none() } + pub fn running(self) -> bool { self.state.is_none() } - pub(crate) fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.state == Some(true) } - pub(crate) fn percent(self) -> Option { None } + pub fn percent(self) -> Option { None } } // --- Bg -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct ProcessProgBg { - pub(crate) state: Option, +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] +pub struct ProcessProgBg { + pub state: Option, } impl From for TaskSummary { @@ -68,9 +69,9 @@ impl From for TaskSummary { } impl ProcessProgBg { - pub(crate) fn running(self) -> bool { self.state.is_none() } + pub fn running(self) -> bool { self.state.is_none() } - pub(crate) fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.state == Some(true) } - pub(crate) fn percent(self) -> Option { None } + pub fn percent(self) -> Option { None } } diff --git a/yazi-scheduler/src/progress.rs b/yazi-scheduler/src/progress.rs index a45bed63..ac86239c 100644 --- a/yazi-scheduler/src/progress.rs +++ b/yazi-scheduler/src/progress.rs @@ -1,9 +1,11 @@ +use serde::Serialize; use yazi_parser::app::TaskSummary; use crate::{file::{FileProgDelete, FileProgHardlink, FileProgLink, FileProgPaste, FileProgTrash}, impl_from_prog, plugin::PluginProgEntry, prework::{PreworkProgFetch, PreworkProgLoad, PreworkProgSize}, process::{ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}}; -#[derive(Clone, Copy, Debug)] -pub(crate) enum TaskProg { +#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)] +#[serde(tag = "kind")] +pub enum TaskProg { // File FilePaste(FileProgPaste), FileLink(FileProgLink), @@ -57,7 +59,7 @@ impl From for TaskSummary { } impl TaskProg { - pub(crate) fn running(self) -> bool { + pub fn running(self) -> bool { match self { // File Self::FilePaste(p) => p.running(), @@ -78,7 +80,7 @@ impl TaskProg { } } - pub(crate) fn success(self) -> bool { + pub fn success(self) -> bool { match self { // File Self::FilePaste(p) => p.success(), @@ -99,7 +101,7 @@ impl TaskProg { } } - pub(crate) fn percent(self) -> Option { + pub fn percent(self) -> Option { match self { // File Self::FilePaste(p) => p.percent(), diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index ff8682b7..8ee64328 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -8,7 +8,7 @@ use yazi_config::{YAZI, plugin::{Fetcher, Preloader}}; use yazi_dds::Pump; use yazi_fs::{must_be_dir, path::unique_name, provider, remove_dir_clean}; use yazi_parser::{app::PluginOpt, tasks::ProcessExecOpt}; -use yazi_proxy::MgrProxy; +use yazi_proxy::TasksProxy; use yazi_shared::{Id, Throttle, url::UrlBuf}; use super::{Ongoing, TaskOp}; @@ -174,7 +174,7 @@ impl Scheduler { move |canceled: bool| async move { if !canceled { provider::remove_dir_all(&target).await.ok(); - MgrProxy::update_tasks(&target); + TasksProxy::update_succeed(&target); Pump::push_delete(target); } ongoing.lock().remove(id); @@ -199,7 +199,7 @@ impl Scheduler { move |canceled: bool| async move { if !canceled { - MgrProxy::update_tasks(&target); + TasksProxy::update_succeed(&target); Pump::push_trash(target); } ongoing.lock().remove(id); diff --git a/yazi-scheduler/src/snap.rs b/yazi-scheduler/src/snap.rs index 8955e3be..6580a890 100644 --- a/yazi-scheduler/src/snap.rs +++ b/yazi-scheduler/src/snap.rs @@ -1,10 +1,13 @@ -use crate::Task; +use serde::Serialize; -#[derive(Debug, PartialEq, Eq)] +use crate::{Task, TaskProg}; + +#[derive(Debug, PartialEq, Eq, Serialize)] pub struct TaskSnap { pub name: String, + pub prog: TaskProg, } impl From<&Task> for TaskSnap { - fn from(task: &Task) -> Self { TaskSnap { name: task.name.clone() } } + fn from(task: &Task) -> Self { TaskSnap { name: task.name.clone(), prog: task.prog } } }