feat: hover cursor over the new file after extracting (#3854)

This commit is contained in:
sxyazi 2026-04-06 15:32:18 +08:00
parent ec178fdb52
commit 354cdef907
No known key found for this signature in database
52 changed files with 979 additions and 321 deletions

View file

@ -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

9
Cargo.lock generated
View file

@ -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",

View file

@ -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);
}

View file

@ -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);

View file

@ -9,8 +9,8 @@ use super::{Lives, PtrCell};
pub(super) struct TaskSnap {
inner: PtrCell<yazi_scheduler::TaskSnap>,
v_name: Option<Value>,
v_prog: Option<Value>,
v_title: Option<Value>,
v_prog: Option<Value>,
}
impl Deref for TaskSnap {
@ -21,13 +21,13 @@ impl Deref for TaskSnap {
impl TaskSnap {
pub(super) fn make(inner: &yazi_scheduler::TaskSnap) -> mlua::Result<AnyUserData> {
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<F: UserDataFields<Self>>(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()));

View file

@ -20,9 +20,9 @@ impl Actor for Quit {
fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
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)) => {

View file

@ -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);

View file

@ -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<Data> {
succ!(match form.opt {
TaskOpt::Plugin(r#in) => cx.tasks.scheduler.plugin_entry(r#in),
})
}
}

View file

@ -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<ActionCow> for PluginOpt {
}
}
impl From<PluginOpt> 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<SStr>, f: impl PluginCallback) -> Self {
Self {

View file

@ -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;

View file

@ -0,0 +1,6 @@
use yazi_scheduler::plugin::PluginInEntry;
#[derive(Clone, Debug)]
pub enum TaskOpt {
Plugin(PluginInEntry),
}

View file

@ -175,6 +175,7 @@ impl<'a> Executor<'a> {
}
on!(update_succeed);
on!(spawn);
on!(show);
on!(close);

View file

@ -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" ] }

View file

@ -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<ActionCow> for ArrowForm {
type Error = anyhow::Error;
fn try_from(a: ActionCow) -> Result<Self, Self::Error> {
let Ok(step) = a.first() else {
bail!("Invalid 'step' in ArrowForm");
};
Ok(Self { step })
}
fn try_from(a: ActionCow) -> Result<Self, Self::Error> { Ok(a.deserialize()?) }
}
impl From<isize> for ArrowForm {

View file

@ -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<ActionCow> for HiddenForm {
type Error = anyhow::Error;
fn try_from(a: ActionCow) -> Result<Self, Self::Error> {
Ok(Self { state: a.str(0).parse().unwrap_or_default() })
}
fn try_from(a: ActionCow) -> Result<Self, Self::Error> { 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, Self::Err> {
Self::deserialize(serde::de::value::StrDeserializer::new(s))
}
}
impl HiddenFormState {
pub fn bool(self, old: bool) -> bool {
match self {

View file

@ -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<f64>")]
pub interval: Duration,
}
impl TryFrom<ActionCow> for TickForm {
type Error = anyhow::Error;
fn try_from(a: ActionCow) -> Result<Self, Self::Error> {
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<Self, Self::Error> { Ok(a.deserialize()?) }
}
impl FromLua for TickForm {

View file

@ -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);

View file

@ -1 +1 @@
yazi_macro::mod_flat!(process_open update_succeed);
yazi_macro::mod_flat!(process_open spawn update_succeed);

View file

@ -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<ActionCow> for SpawnForm {
type Error = anyhow::Error;
fn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {
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<Self> { Err("unsupported".into_lua_err()) }
}
impl IntoLua for SpawnForm {
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
}

View file

@ -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,

View file

@ -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

View file

@ -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);

View file

@ -0,0 +1 @@
yazi_macro::mod_flat!(option task);

View file

@ -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<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_async_method_once("spawn", |_, me, ()| async move {
Ok(Task { id: TasksProxy::spawn(me.0).await? })
});
}
}

View file

@ -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<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("id", |_, me| Ok(yazi_binding::Id(me.id)));
}
}

View file

@ -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
);

View file

@ -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<Function> {
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())?,
}))
})
}
}

View file

@ -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)

View file

@ -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 }

View file

@ -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<Id> {
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)));

View file

@ -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 }

View file

@ -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]

View file

@ -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))

View file

@ -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<yazi_fs::File>,
}
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<FetchIn> 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 }
}

View file

@ -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<mpsc::Sender<()>>,
}
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<Cha>,
}
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<Cha>,
pub(crate) cache: Option<PathBuf>,
}
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() }
}

View file

@ -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<U>(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<U>(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<U>(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<U>(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<U>(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<U>(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<U>(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<U>(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 } }
}

13
yazi-scheduler/src/in.rs Normal file
View file

@ -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>;
}

View file

@ -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;

View file

@ -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<T>(&mut self, r#in: &mut T) -> &mut Task
where
T: TaskIn,
T::Prog: Into<TaskProg> + 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<HookIn> {

View file

@ -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<DataKey, Data>,
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<Self> {
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::<String>(1)?.into(),
args: Sendable::table_to_args(lua, t.raw_get("args")?)?,
title: t.raw_get::<Option<String>>("title")?.unwrap_or_default().into(),
track: t.raw_get("track")?,
})
}
}

View file

@ -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))
}
}

View file

@ -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() }
}

View file

@ -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))

View file

@ -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<UrlCow<'static>>,
}
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<ProcessInBlock> 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<UrlCow<'static>>,
}
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<ProcessInOrphan> 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<ProcessInBg> 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 }

View file

@ -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<T, R>(&self, name: String, map: impl FnOnce(&mut Task) -> R) -> R
fn add<T, R>(&self, r#in: &mut T, map: impl FnOnce(&mut Task) -> R) -> R
where
T: Into<TaskProg> + Default,
T: TaskIn,
T::Prog: Into<TaskProg> + 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<T, R>(
&self,
name: String,
r#in: &mut T,
hook: impl Into<HookIn>,
map: impl FnOnce(&mut Task) -> R,
) -> R
where
T: Into<TaskProg> + Default,
T: TaskIn,
T::Prog: Into<TaskProg> + Default,
{
self.add::<T, R>(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::<FileProgCut, _>(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::<FileProgCopy, _>(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::<FileProgLink, _>(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::<FileProgHardlink, _>(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::<FileProgDelete, _>(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::<FileProgTrash, _>(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::<FileProgDownload, _>(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::<FileProgUpload, _>(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<DataKey, Data>) {
let name = format!("Run micro plugin `{plugin}`");
let id = self.add::<PluginProgEntry, _>(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<yazi_fs::File>,
) -> CompletionToken {
let name = format!("Run fetcher `{}` with {} target(s)", fetcher.run.name, targets.len());
let (id, done) = self.add::<FetchProg, _>(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::<PreloadProg, _>(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::<SizeProg, _>(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::<Vec<_>>().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::<ProcessProgBlock, _>(name, |t| (t.id, t.done.clone()))
} else if opt.orphan {
self.add::<ProcessProgOrphan, _>(name, |t| (t.id, t.done.clone()))
} else {
self.add::<ProcessProgBg, _>(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
}
}

View file

@ -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<Throttle<(UrlBuf, u64)>>,
}
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() }
}

View file

@ -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 } }
}

View file

@ -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<HookIn>,
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<HookIn>) -> &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
}
}

View file

@ -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 {

View file

@ -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<Self, Self::Error> {
fn try_from(value: $a) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
fn try_from(value: $a) -> Result<Self, Self::Error> {
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"),
})
}

View file

@ -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 }

View file

@ -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<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct Visitor;
fn try_from(value: &Data) -> Result<Self, Self::Error> {
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<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
value.parse().map_err(E::custom)
}
fn visit_borrowed_str<E>(self, value: &'_ str) -> Result<Self::Value, E>
where
E: de::Error,
{
self.visit_str(value)
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: de::Error,
{
self.visit_str(&value)
}
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
isize::try_from(value).map(Self::Value::from).map_err(E::custom)
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
isize::try_from(value).map(Self::Value::from).map_err(E::custom)
}
}
deserializer.deserialize_any(Visitor)
}
}