From 354cdef9072b251babe4dcb7cd154f5981ea468c Mon Sep 17 00:00:00 2001 From: sxyazi Date: Mon, 6 Apr 2026 15:32:18 +0800 Subject: [PATCH] feat: hover cursor over the new file after extracting (#3854) --- CHANGELOG.md | 3 +- Cargo.lock | 9 +- yazi-actor/src/app/plugin.rs | 2 +- yazi-actor/src/app/plugin_do.rs | 2 +- yazi-actor/src/lives/task.rs | 8 +- yazi-actor/src/mgr/quit.rs | 6 +- yazi-actor/src/tasks/mod.rs | 2 +- yazi-actor/src/tasks/spawn.rs | 21 +++ yazi-core/src/app/plugin.rs | 7 + yazi-core/src/tasks/mod.rs | 2 +- yazi-core/src/tasks/option.rs | 6 + yazi-fm/src/executor.rs | 1 + yazi-parser/Cargo.toml | 22 +-- yazi-parser/src/arrow.rs | 13 +- yazi-parser/src/mgr/hidden.rs | 15 +- yazi-parser/src/notify/tick.rs | 20 +-- yazi-parser/src/spark/spark.rs | 3 + yazi-parser/src/tasks/mod.rs | 2 +- yazi-parser/src/tasks/spawn.rs | 25 +++ yazi-plugin/preset/components/tasks.lua | 2 +- yazi-plugin/preset/plugins/extract.lua | 62 ++++--- yazi-plugin/src/lib.rs | 2 +- yazi-plugin/src/tasks/mod.rs | 1 + yazi-plugin/src/tasks/option.rs | 15 ++ yazi-plugin/src/tasks/task.rs | 13 ++ yazi-plugin/src/utils/mod.rs | 2 +- yazi-plugin/src/utils/tasks.rs | 16 ++ yazi-plugin/src/utils/utils.rs | 3 + yazi-proxy/Cargo.toml | 4 +- yazi-proxy/src/tasks.rs | 11 +- yazi-runner/Cargo.toml | 3 - yazi-scheduler/Cargo.toml | 1 - yazi-scheduler/src/fetch/fetch.rs | 2 +- yazi-scheduler/src/fetch/in.rs | 23 ++- yazi-scheduler/src/file/in.rs | 207 +++++++++++++++++++--- yazi-scheduler/src/hook/in.rs | 190 +++++++++++++++++--- yazi-scheduler/src/in.rs | 13 ++ yazi-scheduler/src/lib.rs | 2 +- yazi-scheduler/src/ongoing.rs | 15 +- yazi-scheduler/src/plugin/in.rs | 87 +++++++-- yazi-scheduler/src/plugin/plugin.rs | 2 +- yazi-scheduler/src/preload/in.rs | 17 ++ yazi-scheduler/src/preload/preload.rs | 2 +- yazi-scheduler/src/process/in.rs | 65 ++++++- yazi-scheduler/src/scheduler.rs | 223 ++++++++++++------------ yazi-scheduler/src/size/in.rs | 19 +- yazi-scheduler/src/snap.rs | 6 +- yazi-scheduler/src/task.rs | 13 +- yazi-scheduler/src/worker.rs | 2 +- yazi-shared/src/data/macros.rs | 45 +++-- yazi-widgets/Cargo.toml | 1 + yazi-widgets/src/step.rs | 62 +++++-- 52 files changed, 979 insertions(+), 321 deletions(-) create mode 100644 yazi-actor/src/tasks/spawn.rs create mode 100644 yazi-core/src/tasks/option.rs create mode 100644 yazi-parser/src/tasks/spawn.rs create mode 100644 yazi-plugin/src/tasks/mod.rs create mode 100644 yazi-plugin/src/tasks/option.rs create mode 100644 yazi-plugin/src/tasks/task.rs create mode 100644 yazi-plugin/src/utils/tasks.rs create mode 100644 yazi-scheduler/src/in.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e0d9d0a..a25b0096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +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]) -- Hover cursor over the new file after copying/cutting/linking/hardlinking ([#3846]) +- Hover cursor over the new file after copying/cutting/linking/hardlinking/extracting ([#3846], [#3854]) - Multi-file spotter ([#3733]) - Vim-like `lua` action that runs an inline Lua snippet ([#3813]) - Certificate authentication for SFTP VFS provider ([#3716]) @@ -1696,3 +1696,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/): [#3804]: https://github.com/sxyazi/yazi/pull/3804 [#3813]: https://github.com/sxyazi/yazi/pull/3813 [#3846]: https://github.com/sxyazi/yazi/pull/3846 +[#3854]: https://github.com/sxyazi/yazi/pull/3854 diff --git a/Cargo.lock b/Cargo.lock index acf8cea5..04c4d0ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6102,6 +6102,7 @@ dependencies = [ "paste", "ratatui", "serde", + "serde_with", "strum 0.28.0", "tokio", "yazi-binding", @@ -6111,7 +6112,6 @@ dependencies = [ "yazi-dds", "yazi-fs", "yazi-macro", - "yazi-runner", "yazi-scheduler", "yazi-shared", "yazi-vfs", @@ -6165,11 +6165,11 @@ checksum = "33232d9116df6415ddfcdf72701b1b7439ce87f240a14723e8dd5d17e7ed5f98" name = "yazi-proxy" version = "26.2.2" dependencies = [ + "anyhow", "tokio", "yazi-config", "yazi-core", "yazi-macro", - "yazi-runner", "yazi-scheduler", "yazi-shared", "yazi-widgets", @@ -6180,12 +6180,9 @@ name = "yazi-runner" version = "26.2.2" dependencies = [ "anyhow", - "dyn-clone", "hashbrown 0.16.1", "mlua", "parking_lot", - "serde", - "strum 0.28.0", "thiserror 2.0.18", "tokio", "tokio-util", @@ -6215,7 +6212,6 @@ dependencies = [ "serde", "strum 0.28.0", "tokio", - "tokio-util", "tracing", "yazi-binding", "yazi-config", @@ -6360,6 +6356,7 @@ dependencies = [ "mlua", "parking_lot", "ratatui", + "serde", "tokio", "unicode-width", "yazi-adapter", diff --git a/yazi-actor/src/app/plugin.rs b/yazi-actor/src/app/plugin.rs index 2699a128..9a481518 100644 --- a/yazi-actor/src/app/plugin.rs +++ b/yazi-actor/src/app/plugin.rs @@ -24,7 +24,7 @@ impl Actor for Plugin { } if opt.mode == PluginMode::Async { - succ!(cx.core.tasks.scheduler.plugin_entry(opt.id, opt.args)); + succ!(cx.core.tasks.scheduler.plugin_entry(opt.into())); } else if opt.mode == PluginMode::Sync && hits { return act!(app:plugin_do, cx, opt); } diff --git a/yazi-actor/src/app/plugin_do.rs b/yazi-actor/src/app/plugin_do.rs index 9a28e808..c62a390a 100644 --- a/yazi-actor/src/app/plugin_do.rs +++ b/yazi-actor/src/app/plugin_do.rs @@ -31,7 +31,7 @@ impl Actor for PluginDo { } if opt.mode.auto_then(chunk.sync_entry) != PluginMode::Sync { - succ!(cx.core.tasks.scheduler.plugin_entry(opt.id, opt.args)); + succ!(cx.core.tasks.scheduler.plugin_entry(opt.into())); } let blocking = runtime_mut!(LUA)?.critical_push(&opt.id, true); diff --git a/yazi-actor/src/lives/task.rs b/yazi-actor/src/lives/task.rs index e7e85905..7deaae8e 100644 --- a/yazi-actor/src/lives/task.rs +++ b/yazi-actor/src/lives/task.rs @@ -9,8 +9,8 @@ use super::{Lives, PtrCell}; pub(super) struct TaskSnap { inner: PtrCell, - v_name: Option, - v_prog: Option, + v_title: Option, + v_prog: Option, } impl Deref for TaskSnap { @@ -21,13 +21,13 @@ impl Deref for TaskSnap { 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 }) + Lives::scoped_userdata(Self { inner: inner.into(), v_title: 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, title, |lua, me| lua.create_string(&me.title)); 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())); diff --git a/yazi-actor/src/mgr/quit.rs b/yazi-actor/src/mgr/quit.rs index ff56727d..1bd63114 100644 --- a/yazi-actor/src/mgr/quit.rs +++ b/yazi-actor/src/mgr/quit.rs @@ -20,9 +20,9 @@ impl Actor for Quit { fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result { let ongoing = cx.tasks.scheduler.ongoing.clone(); - let (left, left_names) = { + let (left, left_titles) = { let ongoing = ongoing.lock(); - (ongoing.len(), ongoing.values().take(11).map(|t| t.name.clone()).collect()) + (ongoing.len(), ongoing.values().take(11).map(|t| t.title.clone()).collect()) }; if left == 0 { @@ -31,7 +31,7 @@ impl Actor for Quit { tokio::spawn(async move { let mut i = 0; - let token = ConfirmProxy::show_sync(ConfirmCfg::quit(left, left_names)); + let token = ConfirmProxy::show_sync(ConfirmCfg::quit(left, left_titles)); loop { select! { _ = time::sleep(Duration::from_millis(50)) => { diff --git a/yazi-actor/src/tasks/mod.rs b/yazi-actor/src/tasks/mod.rs index 1c71749b..542c41d2 100644 --- a/yazi-actor/src/tasks/mod.rs +++ b/yazi-actor/src/tasks/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(arrow cancel close open_shell_compat inspect process_open show update_succeed); +yazi_macro::mod_flat!(arrow cancel close inspect open_shell_compat process_open show spawn update_succeed); diff --git a/yazi-actor/src/tasks/spawn.rs b/yazi-actor/src/tasks/spawn.rs new file mode 100644 index 00000000..0928fe3c --- /dev/null +++ b/yazi-actor/src/tasks/spawn.rs @@ -0,0 +1,21 @@ +use anyhow::Result; +use yazi_core::tasks::TaskOpt; +use yazi_macro::succ; +use yazi_parser::tasks::SpawnForm; +use yazi_shared::data::Data; + +use crate::{Actor, Ctx}; + +pub struct Spawn; + +impl Actor for Spawn { + type Form = SpawnForm; + + const NAME: &str = "spawn"; + + fn act(cx: &mut Ctx, form: Self::Form) -> Result { + succ!(match form.opt { + TaskOpt::Plugin(r#in) => cx.tasks.scheduler.plugin_entry(r#in), + }) + } +} diff --git a/yazi-core/src/app/plugin.rs b/yazi-core/src/app/plugin.rs index 824080be..aeda63de 100644 --- a/yazi-core/src/app/plugin.rs +++ b/yazi-core/src/app/plugin.rs @@ -6,6 +6,7 @@ use hashbrown::HashMap; use mlua::{Lua, Table}; use serde::Deserialize; use strum::EnumString; +use yazi_scheduler::plugin::PluginInEntry; use yazi_shared::{SStr, data::{Data, DataKey}, event::{Action, ActionCow}}; #[derive(Clone, Debug, Default)] @@ -36,6 +37,12 @@ impl TryFrom for PluginOpt { } } +impl From for PluginInEntry { + fn from(value: PluginOpt) -> Self { + Self { plugin: value.id, args: value.args, ..Default::default() } + } +} + impl PluginOpt { pub fn new_callback(id: impl Into, f: impl PluginCallback) -> Self { Self { diff --git a/yazi-core/src/tasks/mod.rs b/yazi-core/src/tasks/mod.rs index 96e1b79a..888cc397 100644 --- a/yazi-core/src/tasks/mod.rs +++ b/yazi-core/src/tasks/mod.rs @@ -1,4 +1,4 @@ -yazi_macro::mod_flat!(file prework process tasks); +yazi_macro::mod_flat!(file option prework process tasks); pub const TASKS_BORDER: u16 = 2; pub const TASKS_PADDING: u16 = 2; diff --git a/yazi-core/src/tasks/option.rs b/yazi-core/src/tasks/option.rs new file mode 100644 index 00000000..5b63001a --- /dev/null +++ b/yazi-core/src/tasks/option.rs @@ -0,0 +1,6 @@ +use yazi_scheduler::plugin::PluginInEntry; + +#[derive(Clone, Debug)] +pub enum TaskOpt { + Plugin(PluginInEntry), +} diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 224a19f4..897d86da 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -175,6 +175,7 @@ impl<'a> Executor<'a> { } on!(update_succeed); + on!(spawn); on!(show); on!(close); diff --git a/yazi-parser/Cargo.toml b/yazi-parser/Cargo.toml index 0ed440d3..ffccd7d3 100644 --- a/yazi-parser/Cargo.toml +++ b/yazi-parser/Cargo.toml @@ -24,23 +24,23 @@ yazi-core = { path = "../yazi-core", version = "26.2.2" } yazi-dds = { path = "../yazi-dds", version = "26.2.2" } yazi-fs = { path = "../yazi-fs", version = "26.2.2" } yazi-macro = { path = "../yazi-macro", version = "26.2.2" } -yazi-runner = { path = "../yazi-runner", version = "26.2.2" } yazi-scheduler = { path = "../yazi-scheduler", version = "26.2.2" } yazi-shared = { path = "../yazi-shared", version = "26.2.2" } yazi-vfs = { path = "../yazi-vfs", version = "26.2.2" } yazi-widgets = { path = "../yazi-widgets", version = "26.2.2" } # External dependencies -anyhow = { workspace = true } -bitflags = { workspace = true } -crossterm = { workspace = true } -hashbrown = { workspace = true } -mlua = { workspace = true } -paste = { workspace = true } -ratatui = { workspace = true } -serde = { workspace = true } -strum = { workspace = true } -tokio = { workspace = true } +anyhow = { workspace = true } +bitflags = { workspace = true } +crossterm = { workspace = true } +hashbrown = { workspace = true } +mlua = { workspace = true } +paste = { workspace = true } +ratatui = { workspace = true } +serde = { workspace = true } +serde_with = { workspace = true } +strum = { workspace = true } +tokio = { workspace = true } [target.'cfg(target_os = "macos")'.dependencies] crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] } diff --git a/yazi-parser/src/arrow.rs b/yazi-parser/src/arrow.rs index f17edbdb..f580ceed 100644 --- a/yazi-parser/src/arrow.rs +++ b/yazi-parser/src/arrow.rs @@ -1,23 +1,18 @@ -use anyhow::bail; use mlua::{ExternalError, IntoLua, Lua, Value}; +use serde::Deserialize; use yazi_shared::event::ActionCow; use yazi_widgets::Step; -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, Deserialize)] pub struct ArrowForm { + #[serde(alias = "0")] pub step: Step, } impl TryFrom for ArrowForm { type Error = anyhow::Error; - fn try_from(a: ActionCow) -> Result { - let Ok(step) = a.first() else { - bail!("Invalid 'step' in ArrowForm"); - }; - - Ok(Self { step }) - } + fn try_from(a: ActionCow) -> Result { Ok(a.deserialize()?) } } impl From for ArrowForm { diff --git a/yazi-parser/src/mgr/hidden.rs b/yazi-parser/src/mgr/hidden.rs index 23c5429d..2b7a0431 100644 --- a/yazi-parser/src/mgr/hidden.rs +++ b/yazi-parser/src/mgr/hidden.rs @@ -1,5 +1,3 @@ -use std::str::FromStr; - use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value}; use serde::{Deserialize, Serialize}; use yazi_binding::SER_OPT; @@ -7,15 +5,14 @@ use yazi_shared::event::ActionCow; #[derive(Debug, Default, Deserialize, Serialize)] pub struct HiddenForm { + #[serde(alias = "0")] pub state: HiddenFormState, } impl TryFrom for HiddenForm { type Error = anyhow::Error; - fn try_from(a: ActionCow) -> Result { - Ok(Self { state: a.str(0).parse().unwrap_or_default() }) - } + fn try_from(a: ActionCow) -> Result { Ok(a.deserialize()?) } } impl FromLua for HiddenForm { @@ -37,14 +34,6 @@ pub enum HiddenFormState { Toggle, } -impl FromStr for HiddenFormState { - type Err = serde::de::value::Error; - - fn from_str(s: &str) -> Result { - Self::deserialize(serde::de::value::StrDeserializer::new(s)) - } -} - impl HiddenFormState { pub fn bool(self, old: bool) -> bool { match self { diff --git a/yazi-parser/src/notify/tick.rs b/yazi-parser/src/notify/tick.rs index e466c7d7..13d385fb 100644 --- a/yazi-parser/src/notify/tick.rs +++ b/yazi-parser/src/notify/tick.rs @@ -1,28 +1,22 @@ use std::time::Duration; -use anyhow::bail; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +use serde::Deserialize; +use serde_with::{DurationSecondsWithFrac, serde_as}; use yazi_shared::event::ActionCow; -#[derive(Debug, Default)] +#[serde_as] +#[derive(Debug, Default, Deserialize)] pub struct TickForm { + #[serde(alias = "0")] + #[serde_as(as = "DurationSecondsWithFrac")] pub interval: Duration, } impl TryFrom for TickForm { type Error = anyhow::Error; - fn try_from(a: ActionCow) -> Result { - let Ok(interval) = a.first() else { - bail!("Invalid 'interval' in TickForm"); - }; - - if interval < 0.0 { - bail!("'interval' must be non-negative in TickForm"); - } - - Ok(Self { interval: Duration::from_secs_f64(interval) }) - } + fn try_from(a: ActionCow) -> Result { Ok(a.deserialize()?) } } impl FromLua for TickForm { diff --git a/yazi-parser/src/spark/spark.rs b/yazi-parser/src/spark/spark.rs index d951ecfb..c0f45c04 100644 --- a/yazi-parser/src/spark/spark.rs +++ b/yazi-parser/src/spark/spark.rs @@ -148,6 +148,7 @@ pub enum Spark<'a> { TasksOpenShellCompat(crate::tasks::ProcessOpenForm), TasksProcessOpen(crate::tasks::ProcessOpenForm), TasksShow(crate::VoidForm), + TasksSpawn(crate::tasks::SpawnForm), TasksUpdateSucceed(crate::tasks::UpdateSucceedForm), // Which @@ -331,6 +332,7 @@ impl<'a> IntoLua for Spark<'a> { Self::TasksOpenShellCompat(b) => b.into_lua(lua), Self::TasksProcessOpen(b) => b.into_lua(lua), Self::TasksShow(b) => b.into_lua(lua), + Self::TasksSpawn(b) => b.into_lua(lua), Self::TasksUpdateSucceed(b) => b.into_lua(lua), // Which @@ -433,6 +435,7 @@ try_from_spark!(crate::pick::CloseForm, pick:close); try_from_spark!(crate::pick::ShowForm, pick:show); try_from_spark!(crate::spot::CopyForm, spot:copy); try_from_spark!(crate::tasks::ProcessOpenForm, tasks:process_open); +try_from_spark!(crate::tasks::SpawnForm, tasks:spawn); try_from_spark!(crate::tasks::UpdateSucceedForm, tasks:update_succeed); try_from_spark!(crate::which::ActivateForm, which:activate); try_from_spark!(yazi_dds::Payload<'a>, app:accept_payload); diff --git a/yazi-parser/src/tasks/mod.rs b/yazi-parser/src/tasks/mod.rs index f6810693..b4685e20 100644 --- a/yazi-parser/src/tasks/mod.rs +++ b/yazi-parser/src/tasks/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(process_open update_succeed); +yazi_macro::mod_flat!(process_open spawn update_succeed); diff --git a/yazi-parser/src/tasks/spawn.rs b/yazi-parser/src/tasks/spawn.rs new file mode 100644 index 00000000..5d183c2a --- /dev/null +++ b/yazi-parser/src/tasks/spawn.rs @@ -0,0 +1,25 @@ +use anyhow::anyhow; +use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +use yazi_core::tasks::TaskOpt; +use yazi_shared::event::ActionCow; + +#[derive(Clone, Debug)] +pub struct SpawnForm { + pub opt: TaskOpt, +} + +impl TryFrom for SpawnForm { + type Error = anyhow::Error; + + fn try_from(mut a: ActionCow) -> Result { + Ok(Self { opt: a.take_any("opt").ok_or_else(|| anyhow!("Invalid 'opt' in SpawnForm"))? }) + } +} + +impl FromLua for SpawnForm { + fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} + +impl IntoLua for SpawnForm { + fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} diff --git a/yazi-plugin/preset/components/tasks.lua b/yazi-plugin/preset/components/tasks.lua index f52891a5..f13b9129 100644 --- a/yazi-plugin/preset/components/tasks.lua +++ b/yazi-plugin/preset/components/tasks.lua @@ -29,7 +29,7 @@ function Tasks:redraw() break end - elements[#elements + 1] = ui.Line({ self:icon(snap), snap.name }):area(ui.Rect { + elements[#elements + 1] = ui.Line({ self:icon(snap), snap.title }):area(ui.Rect { x = self._area.x, y = y, w = self._area.w, diff --git a/yazi-plugin/preset/plugins/extract.lua b/yazi-plugin/preset/plugins/extract.lua index c7f7b9cd..0b7c2aa7 100644 --- a/yazi-plugin/preset/plugins/extract.lua +++ b/yazi-plugin/preset/plugins/extract.lua @@ -4,31 +4,48 @@ local M = {} function M:setup() ps.sub_remote("extract", function(args) - local noisy = #args == 1 and ' "" --noisy' or ' ""' - for _, arg in ipairs(args) do - ya.emit("plugin", { self._id, ya.quote(arg, true) .. noisy }) - end + ya.async(function() + for i, arg in ipairs(args) do + ya.task("plugin", { + self._id, + args = { arg, "", noisy = #args == 1 }, + title = "Extract " .. arg, + track = i == 1, + }):spawn() + end + end) end) end -function M:entry(job) +function M:init(job) local from = job.args[1] and Url(job.args[1]) - local to = job.args[2] ~= "" and Url(job.args[2]) or nil if not from then fail("No URL provided") end - local pwd = "" + local to = job.args[2] ~= "" and Url(job.args[2]) or from.parent + if not to then + fail("Failed to determine target directory for '%s'", from) + end + + self.job = { id = job.id, from = from, to = to } +end + +function M:entry(job) + self:init(job) + + local pwd, target, retry = "", nil, false while true do - if not M:try_with(from, pwd, to) then + target, retry = self:try_with(pwd) + if not retry then break elseif not job.args.noisy then - fail("'%s' is password-protected, please extract it individually and enter the password", from) + fail("'%s' is password-protected, please extract it individually and enter the password", self.job.from) end local value, event = ya.input { pos = { "top-center", y = 2, w = 50 }, - title = string.format('Password for "%s":', from.name), + title = string.format('Password for "%s":', self.job.from.name), obscure = true, } if event == 1 then @@ -37,14 +54,14 @@ function M:entry(job) break end end + + if target then + ya.emit("tasks:update_succeed", { job.id, urls = { target }, track = true }) + end end -function M:try_with(from, pwd, to) - to = to or from.parent - if not to then - fail("Invalid URL '%s'", from) - end - +function M:try_with(pwd) + local from, to = self.job.from, self.job.to local tmp = fs.unique("dir", to:join(self.tmp_name(from))) if not tmp then fail("Failed to determine a temporary directory for %s", from) @@ -59,18 +76,21 @@ function M:try_with(from, pwd, to) local output, err = child:wait_with_output() if output and output.status.code == 2 and archive.is_encrypted(output.stderr) then fs.remove("dir_all", tmp) - return true -- Need to retry + return nil, true -- Need to retry end - self:tidy(from, to, tmp) + local target = self:tidy(tmp) if not output then fail("7zip failed to output when extracting '%s', error: %s", from, err) elseif output.status.code ~= 0 then fail("7zip exited with error code %s when extracting '%s':\n%s", output.status.code, from, output.stderr) + else + return target, false end end -function M:tidy(from, to, tmp) +function M:tidy(tmp) + local from, to = self.job.from, self.job.to local outs = fs.read_dir(tmp, { limit = 2 }) if not outs then fail("Failed to read the temporary directory '%s' when extracting '%s'", tmp, from) @@ -81,7 +101,7 @@ function M:tidy(from, to, tmp) local only = #outs == 1 and outs[1] if only and not only.cha.is_dir and require("archive").is_tar(only.url) then - self:entry { args = { tostring(only.url), tostring(to) } } + self:entry { id = self.job.id, args = { tostring(only.url), tostring(to) } } fs.remove("file", only.url) fs.remove("dir", tmp) return @@ -98,7 +118,9 @@ function M:tidy(from, to, tmp) elseif not only and not fs.rename(tmp, target) then fail('Failed to move "%s" to "%s"', tmp, target) end + fs.remove("dir", tmp) + return target end function M.tmp_name(url) return ".tmp_" .. ya.hash(string.format("extract//%s//%.10f", url, ya.time())) end diff --git a/yazi-plugin/src/lib.rs b/yazi-plugin/src/lib.rs index 6fefa534..a716d0dc 100644 --- a/yazi-plugin/src/lib.rs +++ b/yazi-plugin/src/lib.rs @@ -1,4 +1,4 @@ -yazi_macro::mod_pub!(elements external fs pubsub runtime theme utils); +yazi_macro::mod_pub!(elements external fs pubsub runtime tasks theme utils); yazi_macro::mod_flat!(slim standard); diff --git a/yazi-plugin/src/tasks/mod.rs b/yazi-plugin/src/tasks/mod.rs new file mode 100644 index 00000000..2b1dbaee --- /dev/null +++ b/yazi-plugin/src/tasks/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(option task); diff --git a/yazi-plugin/src/tasks/option.rs b/yazi-plugin/src/tasks/option.rs new file mode 100644 index 00000000..c6fccf7f --- /dev/null +++ b/yazi-plugin/src/tasks/option.rs @@ -0,0 +1,15 @@ +use mlua::{UserData, UserDataMethods}; +use yazi_proxy::TasksProxy; + +use crate::tasks::Task; + +#[derive(Clone, Debug)] +pub(crate) struct TaskOpt(pub(crate) yazi_core::tasks::TaskOpt); + +impl UserData for TaskOpt { + fn add_methods>(methods: &mut M) { + methods.add_async_method_once("spawn", |_, me, ()| async move { + Ok(Task { id: TasksProxy::spawn(me.0).await? }) + }); + } +} diff --git a/yazi-plugin/src/tasks/task.rs b/yazi-plugin/src/tasks/task.rs new file mode 100644 index 00000000..0ab825a9 --- /dev/null +++ b/yazi-plugin/src/tasks/task.rs @@ -0,0 +1,13 @@ +use mlua::{UserData, UserDataFields}; +use yazi_shared::Id; + +#[derive(Clone, Debug)] +pub(crate) struct Task { + pub(super) id: Id, +} + +impl UserData for Task { + fn add_fields>(fields: &mut F) { + fields.add_field_method_get("id", |_, me| Ok(yazi_binding::Id(me.id))); + } +} diff --git a/yazi-plugin/src/utils/mod.rs b/yazi-plugin/src/utils/mod.rs index 13c186d8..30593ae2 100644 --- a/yazi-plugin/src/utils/mod.rs +++ b/yazi-plugin/src/utils/mod.rs @@ -1,3 +1,3 @@ yazi_macro::mod_flat!( - app cache call image json layer log preview process spot sync target text time user utils + app cache call image json layer log preview process spot sync target tasks text time user utils ); diff --git a/yazi-plugin/src/utils/tasks.rs b/yazi-plugin/src/utils/tasks.rs new file mode 100644 index 00000000..302fe1b6 --- /dev/null +++ b/yazi-plugin/src/utils/tasks.rs @@ -0,0 +1,16 @@ +use mlua::{ExternalError, FromLua, Function, Lua, Value}; +use yazi_core::tasks; + +use super::Utils; +use crate::tasks::TaskOpt; + +impl Utils { + pub(super) fn task(lua: &Lua) -> mlua::Result { + lua.create_function(|lua, (kind, value): (mlua::String, Value)| { + Ok(TaskOpt(match &*kind.as_bytes() { + b"plugin" => tasks::TaskOpt::Plugin(<_>::from_lua(value, lua)?), + _ => Err(format!("unsupported spawn kind: {}", kind.display()).into_lua_err())?, + })) + }) + } +} diff --git a/yazi-plugin/src/utils/utils.rs b/yazi-plugin/src/utils/utils.rs index 971661d5..1c886df0 100644 --- a/yazi-plugin/src/utils/utils.rs +++ b/yazi-plugin/src/utils/utils.rs @@ -82,6 +82,9 @@ pub fn compose( #[cfg(unix)] b"host_name" => Utils::host_name(lua)?, + // Task + b"task" => Utils::task(lua)?, + _ => return Ok(Value::Nil), } .into_lua(lua) diff --git a/yazi-proxy/Cargo.toml b/yazi-proxy/Cargo.toml index 536e799a..35aba306 100644 --- a/yazi-proxy/Cargo.toml +++ b/yazi-proxy/Cargo.toml @@ -16,10 +16,10 @@ workspace = true yazi-config = { path = "../yazi-config", version = "26.2.2" } yazi-core = { path = "../yazi-core", version = "26.2.2" } yazi-macro = { path = "../yazi-macro", version = "26.2.2" } -yazi-runner = { path = "../yazi-runner", version = "26.2.2" } yazi-scheduler = { path = "../yazi-scheduler", version = "26.2.2" } yazi-shared = { path = "../yazi-shared", version = "26.2.2" } yazi-widgets = { path = "../yazi-widgets", version = "26.2.2" } # External dependencies -tokio = { workspace = true } +anyhow = { workspace = true } +tokio = { workspace = true } diff --git a/yazi-proxy/src/tasks.rs b/yazi-proxy/src/tasks.rs index cb0f5519..1277bce2 100644 --- a/yazi-proxy/src/tasks.rs +++ b/yazi-proxy/src/tasks.rs @@ -1,13 +1,22 @@ use std::ffi::OsString; +use anyhow::{Result, anyhow}; use tokio::sync::mpsc; +use yazi_core::tasks::TaskOpt; use yazi_macro::{emit, relay}; use yazi_scheduler::process::ProcessOpt; -use yazi_shared::url::UrlCow; +use yazi_shared::{Id, url::UrlCow}; pub struct TasksProxy; impl TasksProxy { + pub async fn spawn(opt: TaskOpt) -> Result { + let (tx, mut rx) = mpsc::unbounded_channel(); + emit!(Call(relay!(tasks:spawn).with_any("opt", opt).with_replier(tx))); + + rx.recv().await.ok_or_else(|| anyhow!("channel closed"))??.try_into() + } + // TODO: remove pub fn open_shell_compat(opt: ProcessOpt) { emit!(Call(relay!(tasks:open_shell_compat).with_any("opt", opt))); diff --git a/yazi-runner/Cargo.toml b/yazi-runner/Cargo.toml index eafdb096..608b287e 100644 --- a/yazi-runner/Cargo.toml +++ b/yazi-runner/Cargo.toml @@ -23,12 +23,9 @@ yazi-shared = { path = "../yazi-shared", version = "26.2.2" } # External dependencies anyhow = { workspace = true } -dyn-clone = { workspace = true } hashbrown = { workspace = true } mlua = { workspace = true } parking_lot = { workspace = true } -serde = { workspace = true } -strum = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } tokio-util = { workspace = true } diff --git a/yazi-scheduler/Cargo.toml b/yazi-scheduler/Cargo.toml index 70806031..7075ca4d 100644 --- a/yazi-scheduler/Cargo.toml +++ b/yazi-scheduler/Cargo.toml @@ -35,7 +35,6 @@ parking_lot = { workspace = true } serde = { workspace = true } strum = { workspace = true } tokio = { workspace = true } -tokio-util = { workspace = true } tracing = { workspace = true } [target."cfg(unix)".dependencies] diff --git a/yazi-scheduler/src/fetch/fetch.rs b/yazi-scheduler/src/fetch/fetch.rs index 2c1c2a31..d3e4e3f2 100644 --- a/yazi-scheduler/src/fetch/fetch.rs +++ b/yazi-scheduler/src/fetch/fetch.rs @@ -39,7 +39,7 @@ impl Fetch { loaded.get_mut(&h).map(|x| *x &= !(1 << plugin.idx)); } if let Some(e) = err { - error!("Error when running fetcher `{}`:\n{e}", plugin.run.name); + error!("Error when running fetcher '{}':\n{e}", plugin.run.name); } Ok(self.ops.out(id, FetchOutFetch::Succ)) diff --git a/yazi-scheduler/src/fetch/in.rs b/yazi-scheduler/src/fetch/in.rs index d0ca06c4..845805d4 100644 --- a/yazi-scheduler/src/fetch/in.rs +++ b/yazi-scheduler/src/fetch/in.rs @@ -1,7 +1,11 @@ +use std::borrow::Cow; + use yazi_config::plugin::Fetcher; use yazi_runner::fetcher::FetchJob; use yazi_shared::Id; +use crate::{TaskIn, fetch::FetchProg}; + #[derive(Debug)] pub(crate) struct FetchIn { pub(crate) id: Id, @@ -9,10 +13,21 @@ pub(crate) struct FetchIn { pub(crate) targets: Vec, } +impl TaskIn for FetchIn { + type Prog = FetchProg; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { + format!("Run fetcher '{}' with {} target(s)", self.plugin.run.name, self.targets.len()).into() + } +} + impl From for FetchJob { fn from(value: FetchIn) -> Self { Self { action: &value.plugin.run, files: value.targets } } } - -impl FetchIn { - pub(crate) fn id(&self) -> Id { self.id } -} diff --git a/yazi-scheduler/src/file/in.rs b/yazi-scheduler/src/file/in.rs index a15c0fa7..36ceea15 100644 --- a/yazi-scheduler/src/file/in.rs +++ b/yazi-scheduler/src/file/in.rs @@ -1,8 +1,10 @@ -use std::{mem, path::PathBuf}; +use std::{borrow::Cow, mem, path::PathBuf}; use tokio::sync::mpsc; use yazi_fs::cha::Cha; -use yazi_shared::{Id, url::UrlBuf}; +use yazi_shared::{Id, url::{UrlBuf, UrlLike}}; + +use crate::{TaskIn, file::{FileProgCopy, FileProgCut, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgTrash, FileProgUpload}}; #[derive(Debug)] pub(crate) enum FileIn { @@ -24,6 +26,74 @@ pub(crate) enum FileIn { UploadDo(FileInUpload), } +impl TaskIn for FileIn { + type Prog = (); + + fn id(&self) -> Id { + match self { + Self::Copy(r#in) => r#in.id(), + Self::CopyDo(r#in) => r#in.id(), + Self::Cut(r#in) => r#in.id(), + Self::CutDo(r#in) => r#in.id(), + Self::Link(r#in) => r#in.id(), + Self::LinkDo(r#in) => r#in.id(), + Self::Hardlink(r#in) => r#in.id(), + Self::HardlinkDo(r#in) => r#in.id(), + Self::Delete(r#in) => r#in.id(), + Self::DeleteDo(r#in) => r#in.id(), + Self::Trash(r#in) => r#in.id(), + Self::TrashDo(r#in) => r#in.id(), + Self::Download(r#in) => r#in.id(), + Self::DownloadDo(r#in) => r#in.id(), + Self::Upload(r#in) => r#in.id(), + Self::UploadDo(r#in) => r#in.id(), + } + } + + fn with_id(&mut self, id: Id) -> &mut Self { + match self { + Self::Copy(r#in) => _ = r#in.with_id(id), + Self::CopyDo(r#in) => _ = r#in.with_id(id), + Self::Cut(r#in) => _ = r#in.with_id(id), + Self::CutDo(r#in) => _ = r#in.with_id(id), + Self::Link(r#in) => _ = r#in.with_id(id), + Self::LinkDo(r#in) => _ = r#in.with_id(id), + Self::Hardlink(r#in) => _ = r#in.with_id(id), + Self::HardlinkDo(r#in) => _ = r#in.with_id(id), + Self::Delete(r#in) => _ = r#in.with_id(id), + Self::DeleteDo(r#in) => _ = r#in.with_id(id), + Self::Trash(r#in) => _ = r#in.with_id(id), + Self::TrashDo(r#in) => _ = r#in.with_id(id), + Self::Download(r#in) => _ = r#in.with_id(id), + Self::DownloadDo(r#in) => _ = r#in.with_id(id), + Self::Upload(r#in) => _ = r#in.with_id(id), + Self::UploadDo(r#in) => _ = r#in.with_id(id), + } + self + } + + fn title(&self) -> Cow<'_, str> { + match self { + Self::Copy(r#in) => r#in.title(), + Self::CopyDo(r#in) => r#in.title(), + Self::Cut(r#in) => r#in.title(), + Self::CutDo(r#in) => r#in.title(), + Self::Link(r#in) => r#in.title(), + Self::LinkDo(r#in) => r#in.title(), + Self::Hardlink(r#in) => r#in.title(), + Self::HardlinkDo(r#in) => r#in.title(), + Self::Delete(r#in) => r#in.title(), + Self::DeleteDo(r#in) => r#in.title(), + Self::Trash(r#in) => r#in.title(), + Self::TrashDo(r#in) => r#in.title(), + Self::Download(r#in) => r#in.title(), + Self::DownloadDo(r#in) => r#in.title(), + Self::Upload(r#in) => r#in.title(), + Self::UploadDo(r#in) => r#in.title(), + } + } +} + impl_from_in! { Copy(FileInCopy), Cut(FileInCut), @@ -36,27 +106,6 @@ impl_from_in! { } impl FileIn { - pub(crate) fn id(&self) -> Id { - match self { - Self::Copy(r#in) => r#in.id, - Self::CopyDo(r#in) => r#in.id, - Self::Cut(r#in) => r#in.id, - Self::CutDo(r#in) => r#in.id, - Self::Link(r#in) => r#in.id, - Self::LinkDo(r#in) => r#in.id, - Self::Hardlink(r#in) => r#in.id, - Self::HardlinkDo(r#in) => r#in.id, - Self::Delete(r#in) => r#in.id, - Self::DeleteDo(r#in) => r#in.id, - Self::Trash(r#in) => r#in.id, - Self::TrashDo(r#in) => r#in.id, - Self::Download(r#in) => r#in.id, - Self::DownloadDo(r#in) => r#in.id, - Self::Upload(r#in) => r#in.id, - Self::UploadDo(r#in) => r#in.id, - } - } - pub(crate) fn into_doable(self) -> Self { match self { Self::Copy(r#in) => Self::CopyDo(r#in), @@ -91,6 +140,21 @@ pub(crate) struct FileInCopy { pub(crate) retry: u8, } +impl TaskIn for FileInCopy { + type Prog = FileProgCopy; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { + format!("Copy {} to {}", self.from.display(), self.to.display()).into() + } +} + impl FileInCopy { pub(super) fn into_link(self) -> FileInLink { FileInLink { @@ -119,6 +183,21 @@ pub(crate) struct FileInCut { pub(crate) drop: Option>, } +impl TaskIn for FileInCut { + type Prog = FileProgCut; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { + format!("Cut {} to {}", self.from.display(), self.to.display()).into() + } +} + impl Drop for FileInCut { fn drop(&mut self) { _ = self.drop.take(); } } @@ -156,6 +235,21 @@ pub(crate) struct FileInLink { pub(crate) delete: bool, } +impl TaskIn for FileInLink { + type Prog = FileProgLink; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { + format!("Link {} to {}", self.from.display(), self.to.display()).into() + } +} + // --- Hardlink #[derive(Clone, Debug)] pub(crate) struct FileInHardlink { @@ -167,6 +261,21 @@ pub(crate) struct FileInHardlink { pub(crate) follow: bool, } +impl TaskIn for FileInHardlink { + type Prog = FileProgHardlink; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { + format!("Hardlink {} to {}", self.from.display(), self.to.display()).into() + } +} + // --- Delete #[derive(Clone, Debug)] pub(crate) struct FileInDelete { @@ -175,6 +284,19 @@ pub(crate) struct FileInDelete { pub(crate) cha: Option, } +impl TaskIn for FileInDelete { + type Prog = FileProgDelete; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { format!("Delete {}", self.target.display()).into() } +} + // --- Trash #[derive(Clone, Debug)] pub(crate) struct FileInTrash { @@ -182,6 +304,19 @@ pub(crate) struct FileInTrash { pub(crate) target: UrlBuf, } +impl TaskIn for FileInTrash { + type Prog = FileProgTrash; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { format!("Trash {}", self.target.display()).into() } +} + // --- Download #[derive(Clone, Debug)] pub(crate) struct FileInDownload { @@ -191,6 +326,19 @@ pub(crate) struct FileInDownload { pub(crate) retry: u8, } +impl TaskIn for FileInDownload { + type Prog = FileProgDownload; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { format!("Download {}", self.target.display()).into() } +} + // --- Upload #[derive(Clone, Debug)] pub(crate) struct FileInUpload { @@ -199,3 +347,16 @@ pub(crate) struct FileInUpload { pub(crate) cha: Option, pub(crate) cache: Option, } + +impl TaskIn for FileInUpload { + type Prog = FileProgUpload; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { format!("Upload {}", self.target.display()).into() } +} diff --git a/yazi-scheduler/src/hook/in.rs b/yazi-scheduler/src/hook/in.rs index 31a5259b..799a3cba 100644 --- a/yazi-scheduler/src/hook/in.rs +++ b/yazi-scheduler/src/hook/in.rs @@ -1,6 +1,8 @@ -use yazi_shared::{Id, url::UrlBuf}; +use std::borrow::Cow; -use crate::{Task, TaskProg}; +use yazi_shared::{Id, url::{UrlBuf, UrlLike}}; + +use crate::{Task, TaskIn, TaskProg}; #[derive(Debug)] pub(crate) enum HookIn { @@ -27,32 +29,49 @@ impl_from_in!( Preload(HookInPreload), ); -impl HookIn { - pub(crate) fn id(&self) -> Id { +impl TaskIn for HookIn { + type Prog = (); + + fn id(&self) -> Id { match self { - Self::Copy(r#in) => r#in.id, - Self::Cut(r#in) => r#in.id, - Self::Delete(r#in) => r#in.id, - Self::Trash(r#in) => r#in.id, - Self::Link(r#in) => r#in.id, - Self::Hardlink(r#in) => r#in.id, - Self::Download(r#in) => r#in.id, - Self::Upload(r#in) => r#in.id, - Self::Preload(r#in) => r#in.id, + Self::Copy(r#in) => r#in.id(), + Self::Cut(r#in) => r#in.id(), + Self::Delete(r#in) => r#in.id(), + Self::Trash(r#in) => r#in.id(), + Self::Link(r#in) => r#in.id(), + Self::Hardlink(r#in) => r#in.id(), + Self::Download(r#in) => r#in.id(), + Self::Upload(r#in) => r#in.id(), + Self::Preload(r#in) => r#in.id(), } } - pub(crate) fn with_id(self, id: Id) -> Self { + fn with_id(&mut self, id: Id) -> &mut Self { match self { - Self::Copy(r#in) => Self::Copy(HookInOutCopy { id, ..r#in }), - Self::Cut(r#in) => Self::Cut(HookInOutCut { id, ..r#in }), - Self::Delete(r#in) => Self::Delete(HookInDelete { id, ..r#in }), - Self::Trash(r#in) => Self::Trash(HookInTrash { id, ..r#in }), - Self::Link(r#in) => Self::Link(HookInOutLink { id, ..r#in }), - Self::Hardlink(r#in) => Self::Hardlink(HookInOutHardlink { id, ..r#in }), - Self::Download(r#in) => Self::Download(HookInDownload { id, ..r#in }), - Self::Upload(r#in) => Self::Upload(HookInUpload { id, ..r#in }), - Self::Preload(r#in) => Self::Preload(HookInPreload { id, ..r#in }), + Self::Copy(r#in) => r#in.id = id, + Self::Cut(r#in) => r#in.id = id, + Self::Delete(r#in) => r#in.id = id, + Self::Trash(r#in) => r#in.id = id, + Self::Link(r#in) => r#in.id = id, + Self::Hardlink(r#in) => r#in.id = id, + Self::Download(r#in) => r#in.id = id, + Self::Upload(r#in) => r#in.id = id, + Self::Preload(r#in) => r#in.id = id, + } + self + } + + fn title(&self) -> Cow<'_, str> { + match self { + Self::Copy(r#in) => r#in.title(), + Self::Cut(r#in) => r#in.title(), + Self::Delete(r#in) => r#in.title(), + Self::Trash(r#in) => r#in.title(), + Self::Link(r#in) => r#in.title(), + Self::Hardlink(r#in) => r#in.title(), + Self::Download(r#in) => r#in.title(), + Self::Upload(r#in) => r#in.title(), + Self::Preload(r#in) => r#in.title(), } } } @@ -65,6 +84,21 @@ pub(crate) struct HookInOutCopy { pub(crate) to: UrlBuf, } +impl TaskIn for HookInOutCopy { + type Prog = (); + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { + format!("Hook: copy {} to {}", self.from.display(), self.to.display()).into() + } +} + impl HookInOutCopy { pub(crate) fn new(from: U, to: U) -> Self where @@ -88,6 +122,21 @@ pub(crate) struct HookInOutCut { pub(crate) to: UrlBuf, } +impl TaskIn for HookInOutCut { + type Prog = (); + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { + format!("Hook: cut {} to {}", self.from.display(), self.to.display()).into() + } +} + impl HookInOutCut { pub(crate) fn new(from: U, to: U) -> Self where @@ -110,6 +159,19 @@ pub(crate) struct HookInDelete { pub(crate) target: UrlBuf, } +impl TaskIn for HookInDelete { + type Prog = (); + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { format!("Hook: delete {}", self.target.display()).into() } +} + impl HookInDelete { pub(crate) fn new(target: U) -> Self where @@ -126,6 +188,19 @@ pub(crate) struct HookInTrash { pub(crate) target: UrlBuf, } +impl TaskIn for HookInTrash { + type Prog = (); + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { format!("Hook: trash {}", self.target.display()).into() } +} + impl HookInTrash { pub(crate) fn new(target: U) -> Self where @@ -144,6 +219,21 @@ pub(crate) struct HookInOutLink { pub(crate) to: UrlBuf, } +impl TaskIn for HookInOutLink { + type Prog = (); + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { + format!("Hook: link {} to {}", self.from.display(), self.to.display()).into() + } +} + impl HookInOutLink { pub(crate) fn new(from: U, to: U) -> Self where @@ -168,6 +258,21 @@ pub(crate) struct HookInOutHardlink { pub(crate) to: UrlBuf, } +impl TaskIn for HookInOutHardlink { + type Prog = (); + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { + format!("Hook: hardlink {} to {}", self.from.display(), self.to.display()).into() + } +} + impl HookInOutHardlink { pub(crate) fn new(from: U, to: U) -> Self where @@ -190,6 +295,19 @@ pub(crate) struct HookInDownload { pub(crate) target: UrlBuf, } +impl TaskIn for HookInDownload { + type Prog = (); + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { format!("Hook: download {}", self.target.display()).into() } +} + impl HookInDownload { pub(crate) fn new(target: U) -> Self where @@ -206,6 +324,19 @@ pub(crate) struct HookInUpload { pub(crate) target: UrlBuf, } +impl TaskIn for HookInUpload { + type Prog = (); + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { format!("Hook: upload {}", self.target.display()).into() } +} + impl HookInUpload { pub(crate) fn new(target: U) -> Self where @@ -223,6 +354,19 @@ pub(crate) struct HookInPreload { pub(crate) hash: u64, } +impl TaskIn for HookInPreload { + type Prog = (); + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { format!("Hook: run {}-th preloader", self.idx).into() } +} + impl HookInPreload { pub(crate) fn new(idx: u8, hash: u64) -> Self { Self { id: Id::ZERO, idx, hash } } } diff --git a/yazi-scheduler/src/in.rs b/yazi-scheduler/src/in.rs new file mode 100644 index 00000000..bf43304d --- /dev/null +++ b/yazi-scheduler/src/in.rs @@ -0,0 +1,13 @@ +use std::borrow::Cow; + +use yazi_shared::Id; + +pub(crate) trait TaskIn { + type Prog; + + fn id(&self) -> Id; + + fn with_id(&mut self, id: Id) -> &mut Self; + + fn title(&self) -> Cow<'_, str>; +} diff --git a/yazi-scheduler/src/lib.rs b/yazi-scheduler/src/lib.rs index 8dd69f54..fcad349c 100644 --- a/yazi-scheduler/src/lib.rs +++ b/yazi-scheduler/src/lib.rs @@ -2,7 +2,7 @@ mod macros; yazi_macro::mod_pub!(fetch file hook plugin preload process size); -yazi_macro::mod_flat!(behavior cleanup ongoing op out progress proxy scheduler snap summary task worker); +yazi_macro::mod_flat!(behavior cleanup ongoing op out progress proxy r#in scheduler snap summary task worker); const LOW: u8 = yazi_config::Priority::Low as u8; const NORMAL: u8 = yazi_config::Priority::Normal as u8; diff --git a/yazi-scheduler/src/ongoing.rs b/yazi-scheduler/src/ongoing.rs index fd8e1622..36429b45 100644 --- a/yazi-scheduler/src/ongoing.rs +++ b/yazi-scheduler/src/ongoing.rs @@ -3,7 +3,7 @@ use yazi_config::YAZI; use yazi_shared::{CompletionToken, Id, Ids}; use super::Task; -use crate::{TaskProg, hook::HookIn}; +use crate::{TaskIn, TaskProg, hook::HookIn}; #[derive(Default)] pub struct Ongoing { @@ -11,11 +11,18 @@ pub struct Ongoing { } impl Ongoing { - pub(super) fn add(&mut self, name: String, prog: TaskProg) -> &mut Task { + pub(super) fn add(&mut self, r#in: &mut T) -> &mut Task + where + T: TaskIn, + T::Prog: Into + Default, + { static IDS: Ids = Ids::new(); - let id = IDS.next(); - self.inner.entry(id).insert(Task::new(id, name, prog)).into_mut() + + let title = r#in.with_id(id).title().into_owned(); + let prog = T::Prog::default().into(); + + self.inner.entry(id).insert(Task::new(id, title, prog)).into_mut() } pub(super) fn cancel(&mut self, id: Id) -> Option { diff --git a/yazi-scheduler/src/plugin/in.rs b/yazi-scheduler/src/plugin/in.rs index 6bf3db8a..0175ee0c 100644 --- a/yazi-scheduler/src/plugin/in.rs +++ b/yazi-scheduler/src/plugin/in.rs @@ -1,33 +1,90 @@ -use std::ops::{Deref, DerefMut}; +use std::borrow::Cow; +use hashbrown::HashMap; +use mlua::{ExternalError, FromLua, Lua, Value}; +use yazi_dds::Sendable; use yazi_runner::entry::EntryJob; -use yazi_shared::Id; +use yazi_shared::{Id, SStr, data::{Data, DataKey}}; + +use crate::{TaskIn, plugin::PluginProgEntry}; #[derive(Debug)] pub(crate) enum PluginIn { Entry(PluginInEntry), } -impl_from_in!(Entry(PluginInEntry)); +impl TaskIn for PluginIn { + type Prog = (); -impl PluginIn { - pub(crate) fn id(&self) -> Id { + fn id(&self) -> Id { match self { - Self::Entry(r#in) => r#in.id, + Self::Entry(r#in) => r#in.id(), + } + } + + fn with_id(&mut self, id: Id) -> &mut Self { + match self { + Self::Entry(r#in) => _ = r#in.with_id(id), + } + self + } + + fn title(&self) -> Cow<'_, str> { + match self { + Self::Entry(r#in) => r#in.title(), } } } +impl_from_in!(Entry(PluginInEntry)); + // --- Entry -#[derive(Debug)] -pub(crate) struct PluginInEntry(pub(crate) EntryJob); - -impl Deref for PluginInEntry { - type Target = EntryJob; - - fn deref(&self) -> &Self::Target { &self.0 } +#[derive(Clone, Debug, Default)] +pub struct PluginInEntry { + pub id: Id, + pub plugin: SStr, + pub args: HashMap, + pub title: SStr, + pub track: bool, } -impl DerefMut for PluginInEntry { - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +impl TaskIn for PluginInEntry { + type Prog = PluginProgEntry; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { + if self.title.is_empty() { + format!("Run plugin '{}'", self.plugin).into() + } else { + Cow::Borrowed(&self.title) + } + } +} + +impl PluginInEntry { + pub(crate) fn into_job(self) -> EntryJob { + EntryJob { id: self.id, args: self.args, plugin: self.plugin } + } +} + +impl FromLua for PluginInEntry { + fn from_lua(value: Value, lua: &Lua) -> mlua::Result { + let Value::Table(t) = value else { + return Err("constructing PluginInEntry from non-table value".into_lua_err()); + }; + + Ok(Self { + id: Id::ZERO, + plugin: t.raw_get::(1)?.into(), + args: Sendable::table_to_args(lua, t.raw_get("args")?)?, + title: t.raw_get::>("title")?.unwrap_or_default().into(), + track: t.raw_get("track")?, + }) + } } diff --git a/yazi-scheduler/src/plugin/plugin.rs b/yazi-scheduler/src/plugin/plugin.rs index 0a793fb0..43ee7f1b 100644 --- a/yazi-scheduler/src/plugin/plugin.rs +++ b/yazi-scheduler/src/plugin/plugin.rs @@ -20,7 +20,7 @@ impl Plugin { pub(crate) async fn entry(&self, task: PluginInEntry) -> Result<(), PluginOutEntry> { let id = task.id; - RUNNER.entry(task.0).await?; + RUNNER.entry(task.into_job()).await?; Ok(self.ops.out(id, PluginOutEntry::Succ)) } } diff --git a/yazi-scheduler/src/preload/in.rs b/yazi-scheduler/src/preload/in.rs index ab15fcd0..bc52dfc7 100644 --- a/yazi-scheduler/src/preload/in.rs +++ b/yazi-scheduler/src/preload/in.rs @@ -1,9 +1,26 @@ +use std::borrow::Cow; + use yazi_config::plugin::Preloader; use yazi_shared::Id; +use crate::{TaskIn, preload::PreloadProg}; + #[derive(Clone, Debug)] pub(crate) struct PreloadIn { pub(crate) id: Id, pub(crate) plugin: &'static Preloader, pub(crate) target: yazi_fs::File, } + +impl TaskIn for PreloadIn { + type Prog = PreloadProg; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { format!("Run preloader '{}'", self.plugin.run.name).into() } +} diff --git a/yazi-scheduler/src/preload/preload.rs b/yazi-scheduler/src/preload/preload.rs index bc0c2ee7..5c148079 100644 --- a/yazi-scheduler/src/preload/preload.rs +++ b/yazi-scheduler/src/preload/preload.rs @@ -47,7 +47,7 @@ impl Preload { self.loaded.lock().get_mut(&hash).map(|x| *x &= !(1 << task.plugin.idx)); } if let Some(e) = state.error { - error!("Error when running preloader `{}`:\n{e}", task.plugin.run.name); + error!("Error when running preloader '{}':\n{e}", task.plugin.run.name); } Ok(self.ops.out(task.id, PreloadOut::Succ)) diff --git a/yazi-scheduler/src/process/in.rs b/yazi-scheduler/src/process/in.rs index 35635cb5..981ebaca 100644 --- a/yazi-scheduler/src/process/in.rs +++ b/yazi-scheduler/src/process/in.rs @@ -1,8 +1,9 @@ -use std::ffi::OsString; +use std::{borrow::Cow, ffi::OsString}; use yazi_shared::{CompletionToken, Id, url::UrlCow}; use super::ShellOpt; +use crate::{TaskIn, process::{ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}}; #[derive(Debug)] pub(crate) enum ProcessIn { @@ -13,14 +14,33 @@ pub(crate) enum ProcessIn { impl_from_in!(Block(ProcessInBlock), Orphan(ProcessInOrphan), Bg(ProcessInBg)); -impl ProcessIn { - pub(crate) fn id(&self) -> Id { +impl TaskIn for ProcessIn { + type Prog = (); + + fn id(&self) -> Id { match self { Self::Block(r#in) => r#in.id, Self::Orphan(r#in) => r#in.id, Self::Bg(r#in) => r#in.id, } } + + fn with_id(&mut self, id: Id) -> &mut Self { + match self { + Self::Block(r#in) => _ = r#in.with_id(id), + Self::Orphan(r#in) => _ = r#in.with_id(id), + Self::Bg(r#in) => _ = r#in.with_id(id), + }; + self + } + + fn title(&self) -> Cow<'_, str> { + match self { + Self::Block(r#in) => r#in.title(), + Self::Orphan(r#in) => r#in.title(), + Self::Bg(r#in) => r#in.title(), + } + } } // --- Block @@ -32,6 +52,19 @@ pub(crate) struct ProcessInBlock { pub(crate) args: Vec>, } +impl TaskIn for ProcessInBlock { + type Prog = ProcessProgBlock; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { format!("Blocking command: {}", self.cmd.display()).into() } +} + impl From for ShellOpt { fn from(r#in: ProcessInBlock) -> Self { Self { cwd: r#in.cwd, cmd: r#in.cmd, args: r#in.args, piped: false, orphan: false } @@ -47,6 +80,19 @@ pub(crate) struct ProcessInOrphan { pub(crate) args: Vec>, } +impl TaskIn for ProcessInOrphan { + type Prog = ProcessProgOrphan; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { format!("Orphan command: {}", self.cmd.display()).into() } +} + impl From for ShellOpt { fn from(r#in: ProcessInOrphan) -> Self { Self { cwd: r#in.cwd, cmd: r#in.cmd, args: r#in.args, piped: false, orphan: true } @@ -63,6 +109,19 @@ pub(crate) struct ProcessInBg { pub(crate) done: CompletionToken, } +impl TaskIn for ProcessInBg { + type Prog = ProcessProgBg; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { format!("Background command: {}", self.cmd.display()).into() } +} + impl From for ShellOpt { fn from(r#in: ProcessInBg) -> Self { Self { cwd: r#in.cwd, cmd: r#in.cmd, args: r#in.args, piped: true, orphan: false } diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index be0800f0..5f3892e1 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -1,13 +1,11 @@ use std::{ops::Deref, sync::Arc, time::Duration}; -use hashbrown::HashMap; use tokio::task::JoinHandle; use yazi_config::{YAZI, plugin::{Fetcher, Preloader}}; use yazi_fs::FsHash64; -use yazi_runner::entry::EntryJob; -use yazi_shared::{CompletionToken, Id, SStr, Throttle, data::{Data, DataKey}, url::{UrlBuf, UrlLike}}; +use yazi_shared::{CompletionToken, Id, Throttle, url::{UrlBuf, UrlLike}}; -use crate::{Behavior, HIGH, LOW, NORMAL, Task, TaskProg, Worker, fetch::{FetchIn, FetchProg}, file::{FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInTrash, FileInUpload, FileOutCopy, FileOutCut, FileOutDownload, FileOutHardlink, FileOutUpload, FileProgCopy, FileProgCut, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgTrash, FileProgUpload}, hook::{HookIn, HookInDelete, HookInDownload, HookInPreload, HookInTrash, HookInUpload}, plugin::{PluginInEntry, PluginProgEntry}, preload::{PreloadIn, PreloadProg}, process::{ProcessInBg, ProcessInBlock, ProcessInOrphan, ProcessOpt, ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}, size::{SizeIn, SizeProg}}; +use crate::{Behavior, HIGH, LOW, NORMAL, Task, TaskIn, TaskProg, Worker, fetch::FetchIn, file::{FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInTrash, FileInUpload, FileOutCopy, FileOutCut, FileOutDownload, FileOutHardlink, FileOutUpload}, hook::{HookIn, HookInDelete, HookInDownload, HookInPreload, HookInTrash, HookInUpload}, plugin::PluginInEntry, preload::PreloadIn, process::{ProcessIn, ProcessInBg, ProcessInBlock, ProcessInOrphan, ProcessOpt}, size::SizeIn}; pub struct Scheduler { pub worker: Worker, @@ -27,14 +25,13 @@ impl Scheduler { Self { worker, behavior: Behavior::new(), handles } } - fn add(&self, name: String, map: impl FnOnce(&mut Task) -> R) -> R + fn add(&self, r#in: &mut T, map: impl FnOnce(&mut Task) -> R) -> R where - T: Into + Default, + T: TaskIn, + T::Prog: Into + Default, { - let prog = T::default().into(); - let mut ongoing = self.ongoing.lock(); - let task = ongoing.add(name, prog); + let task = ongoing.add(r#in); self.behavior.update(task.id); map(task) @@ -42,14 +39,15 @@ impl Scheduler { fn add_hooked( &self, - name: String, + r#in: &mut T, hook: impl Into, map: impl FnOnce(&mut Task) -> R, ) -> R where - T: Into + Default, + T: TaskIn, + T::Prog: Into + Default, { - self.add::(name, |t| map(t.with_hook(hook))) + self.add(r#in, |t| map(t.with_hook(hook))) } pub fn cancel(&self, id: Id) -> bool { @@ -68,111 +66,115 @@ impl Scheduler { } pub fn file_cut(&self, from: UrlBuf, to: UrlBuf, force: bool) { - let name = format!("Cut {} to {}", from.display(), to.display()); - let id = self.add::(name, |t| t.id); - - if to.try_starts_with(&from).unwrap_or(false) && !to.covariant(&from) { - return self.ops.out(id, FileOutCut::Fail("Cannot cut directory into itself".to_owned())); - } - let follow = !from.scheme().covariant(to.scheme()); - self - .file - .submit(FileInCut { id, from, to, force, cha: None, follow, retry: 0, drop: None }, LOW); + let mut r#in = + FileInCut { id: Id::ZERO, from, to, force, cha: None, follow, retry: 0, drop: None }; + + self.add(&mut r#in, |_| ()); + if r#in.to.try_starts_with(&r#in.from).unwrap_or(false) && !r#in.to.covariant(&r#in.from) { + self.ops.out(r#in.id, FileOutCut::Fail("Cannot cut directory into itself".to_owned())); + } else { + self.file.submit(r#in, LOW); + } } pub fn file_copy(&self, from: UrlBuf, to: UrlBuf, force: bool, follow: bool) { - let name = format!("Copy {} to {}", from.display(), to.display()); - let id = self.add::(name, |t| t.id); - - if to.try_starts_with(&from).unwrap_or(false) && !to.covariant(&from) { - return self.ops.out(id, FileOutCopy::Fail("Cannot copy directory into itself".to_owned())); - } - let follow = follow || !from.scheme().covariant(to.scheme()); - self.file.submit(FileInCopy { id, from, to, force, cha: None, follow, retry: 0 }, LOW); + let mut r#in = FileInCopy { id: Id::ZERO, from, to, force, cha: None, follow, retry: 0 }; + + self.add(&mut r#in, |_| ()); + if r#in.to.try_starts_with(&r#in.from).unwrap_or(false) && !r#in.to.covariant(&r#in.from) { + self.ops.out(r#in.id, FileOutCopy::Fail("Cannot copy directory into itself".to_owned())); + } else { + self.file.submit(r#in, LOW); + } } pub fn file_link(&self, from: UrlBuf, to: UrlBuf, relative: bool, force: bool) { - let name = format!("Link {} to {}", from.display(), to.display()); - let id = self.add::(name, |t| t.id); + let mut r#in = FileInLink { + id: Id::ZERO, + from, + to, + force, + cha: None, + resolve: false, + relative, + delete: false, + }; - self.file.submit( - FileInLink { id, from, to, force, cha: None, resolve: false, relative, delete: false }, - LOW, - ); + self.add(&mut r#in, |_| ()); + self.file.submit(r#in, LOW); } pub fn file_hardlink(&self, from: UrlBuf, to: UrlBuf, force: bool, follow: bool) { - let name = format!("Hardlink {} to {}", from.display(), to.display()); - let id = self.add::(name, |t| t.id); + let mut r#in = FileInHardlink { id: Id::ZERO, from, to, force, cha: None, follow }; + self.add(&mut r#in, |_| ()); - if !from.scheme().covariant(to.scheme()) { + if !r#in.from.scheme().covariant(r#in.to.scheme()) { return self .ops - .out(id, FileOutHardlink::Fail("Cannot hardlink cross filesystem".to_owned())); + .out(r#in.id, FileOutHardlink::Fail("Cannot hardlink cross filesystem".to_owned())); } - if to.try_starts_with(&from).unwrap_or(false) && !to.covariant(&from) { + if r#in.to.try_starts_with(&r#in.from).unwrap_or(false) && !r#in.to.covariant(&r#in.from) { return self .ops - .out(id, FileOutHardlink::Fail("Cannot hardlink directory into itself".to_owned())); + .out(r#in.id, FileOutHardlink::Fail("Cannot hardlink directory into itself".to_owned())); } - self.file.submit(FileInHardlink { id, from, to, force, cha: None, follow }, LOW); + self.file.submit(r#in, LOW); } pub fn file_delete(&self, target: UrlBuf) { - let name = format!("Delete {}", target.display()); + let mut r#in = FileInDelete { id: Id::ZERO, target, cha: None }; + let hook = HookInDelete::new(&r#in.target); - let hook = HookInDelete::new(&target); - let id = self.add_hooked::(name, hook, |t| t.id); - - self.file.submit(FileInDelete { id, target, cha: None }, LOW); + self.add_hooked(&mut r#in, hook, |_| ()); + self.file.submit(r#in, LOW); } pub fn file_trash(&self, target: UrlBuf) { - let name = format!("Trash {}", target.display()); + let mut r#in = FileInTrash { id: Id::ZERO, target }; + let hook = HookInTrash::new(&r#in.target); - let hook = HookInTrash::new(&target); - let id = self.add_hooked::(name, hook, |t| t.id); - - self.file.submit(FileInTrash { id, target }, LOW); + self.add_hooked(&mut r#in, hook, |_| ()); + self.file.submit(r#in, LOW); } pub fn file_download(&self, target: UrlBuf) -> CompletionToken { - let name = format!("Download {}", target.display()); + let mut r#in = FileInDownload { id: Id::ZERO, target, cha: None, retry: 0 }; + let hook = HookInDownload::new(&r#in.target); + let done = self.add_hooked(&mut r#in, hook, |t| t.done.clone()); - let hook = HookInDownload::new(&target); - let (id, done) = self.add_hooked::(name, hook, |t| (t.id, t.done.clone())); - - if !target.kind().is_remote() { - self.ops.out(id, FileOutDownload::Fail("Cannot download non-remote file".to_owned())); - return done; + if r#in.target.kind().is_remote() { + self.file.submit(r#in, LOW); + } else { + self.ops.out(r#in.id, FileOutDownload::Fail("Cannot download non-remote file".to_owned())); } - - self.file.submit(FileInDownload { id, target, cha: None, retry: 0 }, LOW); done } pub fn file_upload(&self, target: UrlBuf) { - let name = format!("Upload {}", target.display()); + let mut r#in = FileInUpload { id: Id::ZERO, target, cha: None, cache: None }; + let hook = HookInUpload::new(&r#in.target); + self.add_hooked(&mut r#in, hook, |_| ()); - let hook = HookInUpload::new(&target); - let id = self.add_hooked::(name, hook, |t| t.id); - - if !target.kind().is_remote() { - return self.ops.out(id, FileOutUpload::Fail("Cannot upload non-remote file".to_owned())); + if r#in.target.kind().is_remote() { + self.file.submit(r#in, LOW); + } else { + self.ops.out(r#in.id, FileOutUpload::Fail("Cannot upload non-remote file".to_owned())); } - - self.file.submit(FileInUpload { id, target, cha: None, cache: None }, LOW); } - pub fn plugin_entry(&self, plugin: SStr, args: HashMap) { - let name = format!("Run micro plugin `{plugin}`"); - let id = self.add::(name, |t| t.id); + pub fn plugin_entry(&self, mut r#in: PluginInEntry) -> Id { + if r#in.track { + self.behavior.reset(); + } - self.plugin.submit(PluginInEntry(EntryJob { id, args, plugin }), NORMAL); + let id = self.add(&mut r#in, |t| t.id); + self.plugin.submit(r#in, NORMAL); + + id } pub fn fetch_paged( @@ -180,10 +182,11 @@ impl Scheduler { fetcher: &'static Fetcher, targets: Vec, ) -> CompletionToken { - let name = format!("Run fetcher `{}` with {} target(s)", fetcher.run.name, targets.len()); - let (id, done) = self.add::(name, |t| (t.id, t.done.clone())); + let mut r#in = FetchIn { id: Id::ZERO, plugin: fetcher, targets }; + + let done = self.add(&mut r#in, |t| t.done.clone()); + self.fetch.submit(r#in); - self.fetch.submit(FetchIn { id, plugin: fetcher, targets }); done } @@ -202,61 +205,55 @@ impl Scheduler { } pub fn preload_paged(&self, preloader: &'static Preloader, target: &yazi_fs::File) { - let name = format!("Run preloader `{}`", preloader.run.name); + let mut r#in = PreloadIn { id: Id::ZERO, plugin: preloader, target: target.clone() }; let hook = HookInPreload::new(preloader.idx, target.hash_u64()); - let id = self.add_hooked::(name, hook, |t| t.id); - if let Some(prev) = self.preload.loading.lock().put(target.url.hash_u64(), id) { + self.add_hooked(&mut r#in, hook, |_| ()); + if let Some(prev) = self.preload.loading.lock().put(target.url.hash_u64(), r#in.id) { self.cancel(prev); } - self.preload.submit(PreloadIn { id, plugin: preloader, target: target.clone() }); + self.preload.submit(r#in); } pub fn prework_size(&self, targets: Vec<&UrlBuf>) { let throttle = Arc::new(Throttle::new(targets.len(), Duration::from_millis(300))); for target in targets { - let name = format!("Calculate the size of {}", target.display()); - let id = self.add::(name, |t| t.id); + let mut r#in = + SizeIn { id: Id::ZERO, target: target.clone(), throttle: throttle.clone() }; - self.size.submit(SizeIn { id, target: target.clone(), throttle: throttle.clone() }, NORMAL); + self.add(&mut r#in, |_| ()); + self.size.submit(r#in, NORMAL); } } pub fn process_open(&self, opt: ProcessOpt) -> CompletionToken { - let name = { - let args = opt.args.iter().map(|a| a.display().to_string()).collect::>().join(" "); - if args.is_empty() { - format!("Run {:?}", opt.cmd) - } else { - format!("Run {:?} with `{args}`", opt.cmd) + let mut r#in: ProcessIn = if opt.block { + ProcessInBlock { id: Id::ZERO, cwd: opt.cwd, cmd: opt.cmd, args: opt.args }.into() + } else if opt.orphan { + ProcessInOrphan { id: Id::ZERO, cwd: opt.cwd, cmd: opt.cmd, args: opt.args }.into() + } else { + ProcessInBg { + id: Id::ZERO, + cwd: opt.cwd, + cmd: opt.cmd, + args: opt.args, + done: CompletionToken::default(), + } + .into() + }; + + let done = match &mut r#in { + ProcessIn::Block(r#in) => self.add(r#in, |t| t.done.clone()), + ProcessIn::Orphan(r#in) => self.add(r#in, |t| t.done.clone()), + ProcessIn::Bg(r#in) => { + r#in.done = self.add(r#in, |t| t.done.clone()); + r#in.done.clone() } }; - let (id, done) = if opt.block { - self.add::(name, |t| (t.id, t.done.clone())) - } else if opt.orphan { - self.add::(name, |t| (t.id, t.done.clone())) - } else { - self.add::(name, |t| (t.id, t.done.clone())) - }; - - if opt.block { - self - .process - .submit(ProcessInBlock { id, cwd: opt.cwd, cmd: opt.cmd, args: opt.args }, NORMAL); - } else if opt.orphan { - self - .process - .submit(ProcessInOrphan { id, cwd: opt.cwd, cmd: opt.cmd, args: opt.args }, NORMAL); - } else { - self.process.submit( - ProcessInBg { id, cwd: opt.cwd, cmd: opt.cmd, args: opt.args, done: done.clone() }, - NORMAL, - ); - }; - + self.process.submit(r#in, NORMAL); done } } diff --git a/yazi-scheduler/src/size/in.rs b/yazi-scheduler/src/size/in.rs index 7b9a5ee4..8fd576a9 100644 --- a/yazi-scheduler/src/size/in.rs +++ b/yazi-scheduler/src/size/in.rs @@ -1,6 +1,8 @@ -use std::sync::Arc; +use std::{borrow::Cow, sync::Arc}; -use yazi_shared::{Id, Throttle, url::UrlBuf}; +use yazi_shared::{Id, Throttle, url::{UrlBuf, UrlLike}}; + +use crate::{TaskIn, size::SizeProg}; #[derive(Debug)] pub(crate) struct SizeIn { @@ -9,6 +11,15 @@ pub(crate) struct SizeIn { pub(crate) throttle: Arc>, } -impl SizeIn { - pub(crate) fn id(&self) -> Id { self.id } +impl TaskIn for SizeIn { + type Prog = SizeProg; + + fn id(&self) -> Id { self.id } + + fn with_id(&mut self, id: Id) -> &mut Self { + self.id = id; + self + } + + fn title(&self) -> Cow<'_, str> { format!("Size '{}'", self.target.display()).into() } } diff --git a/yazi-scheduler/src/snap.rs b/yazi-scheduler/src/snap.rs index 96e9e10d..23a9f21c 100644 --- a/yazi-scheduler/src/snap.rs +++ b/yazi-scheduler/src/snap.rs @@ -4,10 +4,10 @@ use crate::{Task, TaskProg}; #[derive(Debug, PartialEq, Eq, Serialize)] pub struct TaskSnap { - pub name: String, - pub prog: TaskProg, + pub title: String, + pub prog: TaskProg, } impl From<&Task> for TaskSnap { - fn from(task: &Task) -> Self { Self { name: task.name.clone(), prog: task.prog } } + fn from(task: &Task) -> Self { Self { title: task.title.clone(), prog: task.prog } } } diff --git a/yazi-scheduler/src/task.rs b/yazi-scheduler/src/task.rs index a4037d62..26557fcb 100644 --- a/yazi-scheduler/src/task.rs +++ b/yazi-scheduler/src/task.rs @@ -1,12 +1,12 @@ use tokio::sync::mpsc; use yazi_shared::{CompletionToken, Id}; -use crate::{TaskProg, hook::HookIn}; +use crate::{TaskIn, TaskProg, hook::HookIn}; #[derive(Debug)] pub struct Task { pub id: Id, - pub name: String, + pub title: String, pub(crate) prog: TaskProg, pub(crate) hook: Option, pub done: CompletionToken, @@ -16,10 +16,10 @@ pub struct Task { } impl Task { - pub(super) fn new(id: Id, name: String, prog: TaskProg) -> Self { + pub(super) fn new(id: Id, title: String, prog: TaskProg) -> Self { Self { id, - name, + title, prog, hook: None, done: Default::default(), @@ -39,7 +39,10 @@ impl Task { } pub(super) fn with_hook(&mut self, hook: impl Into) -> &mut Self { - self.hook = Some(hook.into().with_id(self.id)); + let mut hook = hook.into(); + hook.with_id(self.id); + + self.hook = Some(hook); self } } diff --git a/yazi-scheduler/src/worker.rs b/yazi-scheduler/src/worker.rs index ff89ebe5..50835f8e 100644 --- a/yazi-scheduler/src/worker.rs +++ b/yazi-scheduler/src/worker.rs @@ -4,7 +4,7 @@ use parking_lot::Mutex; use tokio::{select, sync::mpsc, task::JoinHandle}; use yazi_config::YAZI; -use crate::{CleanupState, LOW, Ongoing, Progress, TaskOp, TaskOps, TaskOut, fetch::{Fetch, FetchIn}, file::{File, FileIn}, hook::{Hook, HookIn}, plugin::{Plugin, PluginIn}, preload::{Preload, PreloadIn}, process::{Process, ProcessIn}, size::{Size, SizeIn}}; +use crate::{CleanupState, LOW, Ongoing, Progress, TaskIn, TaskOp, TaskOps, TaskOut, fetch::{Fetch, FetchIn}, file::{File, FileIn}, hook::{Hook, HookIn}, plugin::{Plugin, PluginIn}, preload::{Preload, PreloadIn}, process::{Process, ProcessIn}, size::{Size, SizeIn}}; #[derive(Clone)] pub struct Worker { diff --git a/yazi-shared/src/data/macros.rs b/yazi-shared/src/data/macros.rs index 25806a12..cdbda856 100644 --- a/yazi-shared/src/data/macros.rs +++ b/yazi-shared/src/data/macros.rs @@ -17,19 +17,32 @@ where Ok(integer) } +macro_rules! ref_or_owned { + (ref, $value:expr) => { + &$value + }; + (owned, $value:expr) => { + $value + }; +} + macro_rules! impl_into_integer { ($a:ty, $($b:ty),+ $(,)?) => { + impl_into_integer!(@impl ref $a, $a, $($b),+); + impl_into_integer!(@impl owned $a, &$a, $($b),+); + }; + (@impl $kind:ident $t:ty, $a:ty, $($b:ty),+ $(,)?) => { $( - impl TryFrom<&$a> for $b { + impl TryFrom<$a> for $b { type Error = anyhow::Error; - fn try_from(value: &$a) -> Result { + fn try_from(value: $a) -> Result { paste::paste! { - Ok(match value { - $a::Integer(i) => <$b>::try_from(*i)?, - $a::Number(n) => <$b>::try_from($crate::data::macros::float_to_i64(*n)?)?, - $a::String(s) => s.parse()?, - $a::Id(i) => <$b>::try_from(i.get())?, + Ok(match ref_or_owned!($kind, value) { + $t::Integer(i) => <$b>::try_from(*i)?, + $t::Number(n) => <$b>::try_from($crate::data::macros::float_to_i64(*n)?)?, + $t::String(s) => s.parse()?, + $t::Id(i) => <$b>::try_from(i.get())?, _ => anyhow::bail!("not an integer"), }) } @@ -41,17 +54,21 @@ macro_rules! impl_into_integer { macro_rules! impl_into_number { ($a:ty, $($b:ty),+ $(,)?) => { + impl_into_number!(@impl ref $a, $a, $($b),+); + impl_into_number!(@impl owned $a, &$a, $($b),+); + }; + (@impl $kind:ident $t:ty, $a:ty, $($b:ty),+ $(,)?) => { $( - impl TryFrom<&$a> for $b { + impl TryFrom<$a> for $b { type Error = anyhow::Error; - fn try_from(value: &$a) -> Result { + fn try_from(value: $a) -> Result { paste::paste! { - Ok(match value { - $a::Integer(i) if *i == (*i as $b as _) => *i as $b, - $a::Number(n) if f64::from(*n) == (f64::from(*n) as $b as _) => f64::from(*n) as $b, - $a::String(s) => s.parse()?, - $a::Id(i) if i.get() == (i.get() as $b as _) => i.get() as $b, + Ok(match ref_or_owned!($kind, value) { + $t::Integer(i) if *i == (*i as $b as _) => *i as $b, + $t::Number(n) if f64::from(*n) == (f64::from(*n) as $b as _) => f64::from(*n) as $b, + $t::String(s) => s.parse()?, + $t::Id(i) if i.get() == (i.get() as $b as _) => i.get() as $b, _ => anyhow::bail!("not a number"), }) } diff --git a/yazi-widgets/Cargo.toml b/yazi-widgets/Cargo.toml index 669de788..3d6f6013 100644 --- a/yazi-widgets/Cargo.toml +++ b/yazi-widgets/Cargo.toml @@ -27,6 +27,7 @@ futures = { workspace = true } mlua = { workspace = true } parking_lot = { workspace = true } ratatui = { workspace = true } +serde = { workspace = true } tokio = { workspace = true } unicode-width = { workspace = true } diff --git a/yazi-widgets/src/step.rs b/yazi-widgets/src/step.rs index a0c195fd..9ac5c1b9 100644 --- a/yazi-widgets/src/step.rs +++ b/yazi-widgets/src/step.rs @@ -1,6 +1,6 @@ -use std::{num::ParseIntError, str::FromStr}; +use std::{fmt, num::ParseIntError, str::FromStr}; -use yazi_shared::data::Data; +use serde::{Deserialize, Deserializer, de}; #[derive(Clone, Copy, Debug)] pub enum Step { @@ -35,15 +35,57 @@ impl FromStr for Step { } } -impl TryFrom<&Data> for Step { - type Error = ParseIntError; +impl<'de> Deserialize<'de> for Step { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct Visitor; - fn try_from(value: &Data) -> Result { - Ok(match value { - Data::Integer(i) => Self::from(*i as isize), - Data::String(s) => s.parse()?, - _ => "".parse()?, - }) + impl de::Visitor<'_> for Visitor { + type Value = Step; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a step string or integer offset") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse().map_err(E::custom) + } + + fn visit_borrowed_str(self, value: &'_ str) -> Result + where + E: de::Error, + { + self.visit_str(value) + } + + fn visit_string(self, value: String) -> Result + where + E: de::Error, + { + self.visit_str(&value) + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + isize::try_from(value).map(Self::Value::from).map_err(E::custom) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + isize::try_from(value).map(Self::Value::from).map_err(E::custom) + } + } + + deserializer.deserialize_any(Visitor) } }