From 2358439448b26ddad911bbbd95d417f9fa72d2cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Fri, 29 Nov 2024 21:48:52 +0800 Subject: [PATCH] feat!: support passing arguments to previewer/preloader/spotter/fetcher (#1966) --- Cargo.lock | 40 +++--- Cargo.toml | 4 +- README.md | 1 - yazi-boot/Cargo.toml | 2 +- yazi-cli/Cargo.toml | 2 +- yazi-config/src/keymap/cow.rs | 8 +- yazi-config/src/plugin/fetcher.rs | 13 -- yazi-config/src/plugin/preloader.rs | 13 -- yazi-core/src/manager/commands/open.rs | 4 +- .../src/manager/commands/update_mimes.rs | 6 +- yazi-core/src/manager/watcher.rs | 4 +- yazi-core/src/tab/commands/escape.rs | 2 +- yazi-core/src/tasks/plugin.rs | 10 +- yazi-core/src/tasks/preload.rs | 1 + yazi-dds/Cargo.toml | 2 +- yazi-dds/src/body/yank.rs | 1 + yazi-dds/src/sendable.rs | 98 ++++++++++---- yazi-fm/src/app/app.rs | 8 +- yazi-fm/src/app/commands/plugin.rs | 11 +- yazi-fm/src/input/input.rs | 2 +- yazi-plugin/preset/plugins/archive.lua | 22 ++-- yazi-plugin/preset/plugins/code.lua | 20 +-- yazi-plugin/preset/plugins/empty.lua | 24 ++-- yazi-plugin/preset/plugins/extract.lua | 14 +- yazi-plugin/preset/plugins/file.lua | 8 +- yazi-plugin/preset/plugins/folder.lua | 30 ++--- yazi-plugin/preset/plugins/font.lua | 16 +-- yazi-plugin/preset/plugins/image.lua | 16 +-- yazi-plugin/preset/plugins/json.lua | 22 ++-- yazi-plugin/preset/plugins/magick.lua | 16 +-- yazi-plugin/preset/plugins/pdf.lua | 28 ++-- yazi-plugin/preset/plugins/video.lua | 30 ++--- yazi-plugin/src/config/plugin.rs | 7 +- yazi-plugin/src/elements/paragraph.rs | 3 +- yazi-plugin/src/external/highlighter.rs | 29 ++--- yazi-plugin/src/isolate/entry.rs | 68 ++++++++-- yazi-plugin/src/isolate/fetch.rs | 14 +- yazi-plugin/src/isolate/peek.rs | 116 +++++++++++++---- yazi-plugin/src/isolate/preload.rs | 29 +++-- yazi-plugin/src/isolate/seek.rs | 16 ++- yazi-plugin/src/isolate/spot.rs | 17 +-- yazi-plugin/src/utils/call.rs | 13 +- yazi-plugin/src/utils/preview.rs | 11 +- yazi-plugin/src/utils/sync.rs | 4 +- yazi-proxy/src/options/plugin.rs | 31 ++++- yazi-scheduler/src/plugin/op.rs | 8 +- yazi-scheduler/src/plugin/plugin.rs | 4 +- yazi-scheduler/src/prework/op.rs | 8 +- yazi-scheduler/src/prework/prework.rs | 18 +-- yazi-scheduler/src/scheduler.rs | 32 ++--- yazi-shared/src/event/cmd.rs | 123 ++++++++++-------- yazi-shared/src/event/cow.rs | 10 +- yazi-shared/src/event/data.rs | 26 +++- yazi-shared/src/event/event.rs | 4 +- 54 files changed, 631 insertions(+), 438 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5193a749b..c18485dd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1008,9 +1008,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", @@ -1340,9 +1340,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" @@ -1396,9 +1396,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.164" +version = "0.2.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" [[package]] name = "libfuzzer-sys" @@ -2430,9 +2430,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2733,9 +2733,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -2756,9 +2756,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", @@ -2767,9 +2767,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -2962,9 +2962,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "vergen" -version = "9.0.1" +version = "9.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349ed9e45296a581f455bc18039878f409992999bc1d5da12a6800eb18c8752f" +checksum = "31f25fc8f8f05df455c7941e87f093ad22522a9ff33d7a027774815acf6f0639" dependencies = [ "anyhow", "derive_builder", @@ -2976,9 +2976,9 @@ dependencies = [ [[package]] name = "vergen-gitcl" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a3a7f91caabecefc3c249fd864b11d4abe315c166fbdb568964421bccfd2b7a" +checksum = "0227006d09f98ab00ea69e9a5e055e676a813cfbed4232986176c86a6080b997" dependencies = [ "anyhow", "derive_builder", @@ -2990,9 +2990,9 @@ dependencies = [ [[package]] name = "vergen-lib" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229eaddb0050920816cf051e619affaf18caa3dd512de8de5839ccbc8e53abb0" +checksum = "c0c767e6751c09fc85cde58722cf2f1007e80e4c8d5a4321fc90d83dc54ca147" dependencies = [ "anyhow", "derive_builder", diff --git a/Cargo.toml b/Cargo.toml index 43f737bf7..624047721 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ crossterm = { version = "0.28.1", features = [ "event-stream" ] } dirs = "5.0.1" futures = "0.3.31" globset = "0.4.15" -libc = "0.2.164" +libc = "0.2.166" md-5 = "0.10.6" mlua = { version = "0.10.1", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serialize" ] } parking_lot = "0.12.3" @@ -32,6 +32,6 @@ shell-words = "1.1.0" tokio = { version = "1.41.1", features = [ "full" ] } tokio-stream = "0.1.16" tokio-util = "0.7.12" -tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_debug" ] } +tracing = { version = "0.1.41", features = [ "max_level_debug", "release_max_level_debug" ] } unicode-width = "0.2.0" uzers = "0.12.1" diff --git a/README.md b/README.md index 42c33b514..b4a858f00 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,6 @@ https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265 | [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol][iip] | ✅ Built-in | | [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol][iip] | ✅ Built-in | | [Rio](https://github.com/raphamorim/rio) | [Inline images protocol][iip] | ❌ Rio doesn't correctly clear images (#1786) | -| [Mintty](https://github.com/mintty/mintty) (Git Bash) | [Inline images protocol][iip] | ✅ Built-in | | [Black Box](https://gitlab.gnome.org/raggesilver/blackbox) | [Sixel graphics format][sixel] | ✅ Built-in | | [Hyper](https://github.com/vercel/hyper) | [Inline images protocol][iip] | ✅ Built-in | | X11 / Wayland | Window system protocol | ☑️ [Überzug++][ueberzug] required | diff --git a/yazi-boot/Cargo.toml b/yazi-boot/Cargo.toml index aa7555df4..8c43ea287 100644 --- a/yazi-boot/Cargo.toml +++ b/yazi-boot/Cargo.toml @@ -24,4 +24,4 @@ clap = { workspace = true } clap_complete = "4.5.38" clap_complete_fig = "4.5.2" clap_complete_nushell = "4.5.4" -vergen-gitcl = { version = "1.0.1", features = [ "build", "rustc" ] } +vergen-gitcl = { version = "1.0.2", features = [ "build", "rustc" ] } diff --git a/yazi-cli/Cargo.toml b/yazi-cli/Cargo.toml index 7c3d3322c..858081891 100644 --- a/yazi-cli/Cargo.toml +++ b/yazi-cli/Cargo.toml @@ -31,7 +31,7 @@ clap_complete = "4.5.38" clap_complete_fig = "4.5.2" clap_complete_nushell = "4.5.4" serde_json = { workspace = true } -vergen-gitcl = { version = "1.0.1", features = [ "build" ] } +vergen-gitcl = { version = "1.0.2", features = [ "build" ] } [target.'cfg(target_os = "macos")'.dependencies] crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] } diff --git a/yazi-config/src/keymap/cow.rs b/yazi-config/src/keymap/cow.rs index 62cdc706b..e9a1199f0 100644 --- a/yazi-config/src/keymap/cow.rs +++ b/yazi-config/src/keymap/cow.rs @@ -1,4 +1,4 @@ -use std::{collections::VecDeque, ops::Deref}; +use std::ops::Deref; use yazi_shared::event::CmdCow; @@ -34,10 +34,10 @@ impl Default for ChordCow { } impl ChordCow { - pub fn into_seq(self) -> VecDeque { + pub fn into_seq(self) -> Vec { match self { - Self::Owned(c) => c.run.into_iter().map(|c| c.into()).collect(), - Self::Borrowed(c) => c.run.iter().map(|c| c.into()).collect(), + Self::Owned(c) => c.run.into_iter().rev().map(|c| c.into()).collect(), + Self::Borrowed(c) => c.run.iter().rev().map(|c| c.into()).collect(), } } } diff --git a/yazi-config/src/plugin/fetcher.rs b/yazi-config/src/plugin/fetcher.rs index eb2659522..fe4a3fd59 100644 --- a/yazi-config/src/plugin/fetcher.rs +++ b/yazi-config/src/plugin/fetcher.rs @@ -28,16 +28,3 @@ impl Fetcher { || self.name.as_ref().is_some_and(|p| p.match_path(path, mime == MIME_DIR))) } } - -#[derive(Debug, Clone)] -pub struct FetcherProps { - pub id: u8, - pub name: &'static str, - pub prio: Priority, -} - -impl From<&'static Fetcher> for FetcherProps { - fn from(fetcher: &'static Fetcher) -> Self { - Self { id: fetcher.idx, name: &fetcher.run.name, prio: fetcher.prio } - } -} diff --git a/yazi-config/src/plugin/preloader.rs b/yazi-config/src/plugin/preloader.rs index 5ed17751a..4f74c9a06 100644 --- a/yazi-config/src/plugin/preloader.rs +++ b/yazi-config/src/plugin/preloader.rs @@ -26,16 +26,3 @@ impl Preloader { || self.name.as_ref().is_some_and(|p| p.match_path(path, mime == MIME_DIR)) } } - -#[derive(Debug, Clone)] -pub struct PreloaderProps { - pub id: u8, - pub name: &'static str, - pub prio: Priority, -} - -impl From<&'static Preloader> for PreloaderProps { - fn from(preloader: &'static Preloader) -> Self { - Self { id: preloader.idx, name: &preloader.run.name, prio: preloader.prio } - } -} diff --git a/yazi-core/src/manager/commands/open.rs b/yazi-core/src/manager/commands/open.rs index d318d394e..fc97aea6f 100644 --- a/yazi-core/src/manager/commands/open.rs +++ b/yazi-core/src/manager/commands/open.rs @@ -7,7 +7,7 @@ use yazi_fs::Folder; use yazi_macro::emit; use yazi_plugin::isolate; use yazi_proxy::{ManagerProxy, TasksProxy, options::OpenDoOpt}; -use yazi_shared::{MIME_DIR, event::{CmdCow, EventQuit}, fs::{File, Url}}; +use yazi_shared::{MIME_DIR, event::{Cmd, CmdCow, EventQuit}, fs::{File, Url}}; use crate::{manager::Manager, tasks::Tasks}; @@ -65,7 +65,7 @@ impl Manager { } done.extend(files.iter().map(|f| (f.url_owned(), String::new()))); - if let Err(e) = isolate::fetch("mime", files).await { + if let Err(e) = isolate::fetch(Cmd::new("mime").into(), files).await { error!("Fetch `mime` failed in opening: {e}"); } diff --git a/yazi-core/src/manager/commands/update_mimes.rs b/yazi-core/src/manager/commands/update_mimes.rs index 64fd2026c..814a99b4c 100644 --- a/yazi-core/src/manager/commands/update_mimes.rs +++ b/yazi-core/src/manager/commands/update_mimes.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{borrow::Cow, collections::HashMap}; use tracing::error; use yazi_macro::render; @@ -7,7 +7,7 @@ use yazi_shared::{event::CmdCow, fs::Url}; use crate::{manager::{LINKED, Manager}, tasks::Tasks}; pub struct Opt { - updates: HashMap, + updates: HashMap, String>, } impl TryFrom for Opt { @@ -28,7 +28,7 @@ impl Manager { let updates = opt .updates .into_iter() - .map(|(url, mime)| (Url::from(url), mime)) + .map(|(url, mime)| (Url::from(url.into_owned()), mime)) .filter(|(url, mime)| self.mimetype.get(url) != Some(mime)) .fold(HashMap::new(), |mut map, (u, m)| { for u in linked.from_file(&u) { diff --git a/yazi-core/src/manager/watcher.rs b/yazi-core/src/manager/watcher.rs index 1e6f41720..8cf38b01c 100644 --- a/yazi-core/src/manager/watcher.rs +++ b/yazi-core/src/manager/watcher.rs @@ -9,7 +9,7 @@ use tracing::error; use yazi_fs::{Files, Folder}; use yazi_plugin::isolate; use yazi_proxy::WATCHER; -use yazi_shared::{RoCell, fs::{Cha, File, FilesOp, Url, realname_unchecked}}; +use yazi_shared::{RoCell, event::Cmd, fs::{Cha, File, FilesOp, Url, realname_unchecked}}; use super::Linked; @@ -151,7 +151,7 @@ impl Watcher { } FilesOp::mutate(ops); - if let Err(e) = isolate::fetch("mime", reload).await { + if let Err(e) = isolate::fetch(Cmd::new("mime").into(), reload).await { error!("Fetch `mime` failed in watcher: {e}"); } } diff --git a/yazi-core/src/tab/commands/escape.rs b/yazi-core/src/tab/commands/escape.rs index a1ae415ef..c84672e1a 100644 --- a/yazi-core/src/tab/commands/escape.rs +++ b/yazi-core/src/tab/commands/escape.rs @@ -18,7 +18,7 @@ bitflags! { impl From for Opt { fn from(c: CmdCow) -> Self { c.args.iter().fold(Opt::empty(), |acc, (k, v)| { - match (k.as_str(), v.as_bool().unwrap_or(false)) { + match (k.as_str().unwrap_or(""), v.as_bool().unwrap_or(false)) { ("all", true) => Self::all(), ("find", true) => acc | Self::FIND, ("visual", true) => acc | Self::VISUAL, diff --git a/yazi-core/src/tasks/plugin.rs b/yazi-core/src/tasks/plugin.rs index db4dab56a..9432833f9 100644 --- a/yazi-core/src/tasks/plugin.rs +++ b/yazi-core/src/tasks/plugin.rs @@ -1,15 +1,11 @@ -use yazi_shared::event::Data; +use yazi_proxy::options::PluginOpt; use super::Tasks; impl Tasks { #[inline] - pub fn plugin_micro(&self, name: String, args: Vec) { - self.scheduler.plugin_micro(name, args); - } + pub fn plugin_micro(&self, opt: PluginOpt) { self.scheduler.plugin_micro(opt); } #[inline] - pub fn plugin_macro(&self, name: String, args: Vec) { - self.scheduler.plugin_macro(name, args); - } + pub fn plugin_macro(&self, opt: PluginOpt) { self.scheduler.plugin_macro(opt); } } diff --git a/yazi-core/src/tasks/preload.rs b/yazi-core/src/tasks/preload.rs index cc912b11d..e9dd506bc 100644 --- a/yazi-core/src/tasks/preload.rs +++ b/yazi-core/src/tasks/preload.rs @@ -13,6 +13,7 @@ impl Tasks { let mime = if f.is_dir() { MIME_DIR } else { mimetype.get(&f.url).unwrap_or_default() }; let factors = |s: &str| match s { "mime" => !mime.is_empty(), + "dummy" => f.cha.is_dummy(), _ => false, }; diff --git a/yazi-dds/Cargo.toml b/yazi-dds/Cargo.toml index e03c0914a..80475d37f 100644 --- a/yazi-dds/Cargo.toml +++ b/yazi-dds/Cargo.toml @@ -29,7 +29,7 @@ tokio-util = { workspace = true } tracing = { workspace = true } [build-dependencies] -vergen-gitcl = { version = "1.0.1", features = [ "build" ] } +vergen-gitcl = { version = "1.0.2", features = [ "build" ] } [target."cfg(unix)".dependencies] uzers = { workspace = true } diff --git a/yazi-dds/src/body/yank.rs b/yazi-dds/src/body/yank.rs index a5cb99524..64ba3df14 100644 --- a/yazi-dds/src/body/yank.rs +++ b/yazi-dds/src/body/yank.rs @@ -43,6 +43,7 @@ impl IntoLua for BodyYank<'static> { } // --- Iterator +#[derive(Clone)] pub struct BodyYankIter { pub cut: bool, pub urls: Vec, diff --git a/yazi-dds/src/sendable.rs b/yazi-dds/src/sendable.rs index 02b3f2c7f..0bbcc73cb 100644 --- a/yazi-dds/src/sendable.rs +++ b/yazi-dds/src/sendable.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{borrow::Cow, collections::HashMap}; use mlua::{ExternalError, Lua, MultiValue, Table, Value}; use yazi_shared::{OrderedFloat, event::{Data, DataKey}}; @@ -11,7 +11,7 @@ impl Sendable { Value::Nil => Data::Nil, Value::Boolean(b) => Data::Boolean(b), Value::LightUserData(_) => Err("light userdata is not supported".into_lua_err())?, - Value::Integer(n) => Data::Integer(n), + Value::Integer(i) => Data::Integer(i), Value::Number(n) => Data::Number(n), Value::String(s) => Data::String(s.to_str()?.to_owned()), Value::Table(t) => { @@ -50,24 +50,33 @@ impl Sendable { pub fn data_to_value(lua: &Lua, data: Data) -> mlua::Result { Ok(match data { - Data::Nil => Value::Nil, - Data::Boolean(v) => Value::Boolean(v), - Data::Integer(v) => Value::Integer(v), - Data::Number(v) => Value::Number(v), - Data::String(v) => Value::String(lua.create_string(v)?), - Data::List(v) => Value::Table(Self::list_to_table(lua, v)?), - Data::Dict(t) => { - let seq_len = t.keys().filter(|&k| !k.is_integer()).count(); - let table = lua.create_table_with_capacity(seq_len, t.len() - seq_len)?; - for (k, v) in t { - table.raw_set(Self::key_to_value(lua, k)?, Self::data_to_value(lua, v)?)?; + Data::List(l) => Value::Table(Self::list_to_table(lua, l)?), + Data::Dict(d) => Value::Table(Self::dict_to_table(lua, d)?), + Data::Url(u) => Value::UserData(lua.create_any_userdata(u)?), + Data::Any(a) => { + if let Ok(t) = a.downcast::() { + Value::UserData(lua.create_userdata(*t)?) + } else { + Err("unsupported userdata included".into_lua_err())? } - Value::Table(table) } - Data::Url(v) => Value::UserData(lua.create_any_userdata(v)?), - Data::Any(v) => { - if let Ok(t) = v.downcast::() { - Value::UserData(lua.create_userdata(*t)?) + data => Self::data_to_value_ref(lua, &data)?, + }) + } + + pub fn data_to_value_ref(lua: &Lua, data: &Data) -> mlua::Result { + Ok(match data { + Data::Nil => Value::Nil, + Data::Boolean(b) => Value::Boolean(*b), + Data::Integer(i) => Value::Integer(*i), + Data::Number(n) => Value::Number(*n), + Data::String(s) => Value::String(lua.create_string(s)?), + Data::List(l) => Value::Table(Self::list_to_table_ref(lua, l)?), + Data::Dict(d) => Value::Table(Self::dict_to_table_ref(lua, d)?), + Data::Url(u) => Value::UserData(lua.create_any_userdata(u.clone())?), + Data::Any(a) => { + if let Some(t) = a.downcast_ref::() { + Value::UserData(lua.create_userdata(t.clone())?) } else { Err("unsupported userdata included".into_lua_err())? } @@ -75,14 +84,40 @@ impl Sendable { }) } - pub fn list_to_table(lua: &Lua, data: Vec) -> mlua::Result { - let mut vec = Vec::with_capacity(data.len()); - for v in data.into_iter() { + pub fn list_to_table(lua: &Lua, list: Vec) -> mlua::Result
{ + let mut vec = Vec::with_capacity(list.len()); + for v in list.into_iter() { vec.push(Self::data_to_value(lua, v)?); } lua.create_sequence_from(vec) } + pub fn list_to_table_ref(lua: &Lua, list: &[Data]) -> mlua::Result
{ + let mut vec = Vec::with_capacity(list.len()); + for v in list { + vec.push(Self::data_to_value_ref(lua, v)?); + } + lua.create_sequence_from(vec) + } + + pub fn dict_to_table(lua: &Lua, dict: HashMap) -> mlua::Result
{ + let seq_len = dict.keys().filter(|&k| !k.is_integer()).count(); + let tbl = lua.create_table_with_capacity(seq_len, dict.len() - seq_len)?; + for (k, v) in dict { + tbl.raw_set(Self::key_to_value(lua, k)?, Self::data_to_value(lua, v)?)?; + } + Ok(tbl) + } + + pub fn dict_to_table_ref(lua: &Lua, dict: &HashMap) -> mlua::Result
{ + let seq_len = dict.keys().filter(|&k| !k.is_integer()).count(); + let tbl = lua.create_table_with_capacity(seq_len, dict.len() - seq_len)?; + for (k, v) in dict { + tbl.raw_set(Self::key_to_value_ref(lua, k)?, Self::data_to_value_ref(lua, v)?)?; + } + Ok(tbl) + } + pub fn list_to_values(lua: &Lua, data: Vec) -> mlua::Result { let mut vec = Vec::with_capacity(data.len()); for v in data { @@ -91,7 +126,7 @@ impl Sendable { Ok(MultiValue::from_vec(vec)) } - pub fn values_to_vec(values: MultiValue) -> mlua::Result> { + pub fn values_to_list(values: MultiValue) -> mlua::Result> { let mut vec = Vec::with_capacity(values.len()); for value in values { vec.push(Self::value_to_data(value)?); @@ -106,7 +141,7 @@ impl Sendable { Value::LightUserData(_) => Err("light userdata is not supported".into_lua_err())?, Value::Integer(v) => DataKey::Integer(v), Value::Number(v) => DataKey::Number(OrderedFloat::new(v)), - Value::String(v) => DataKey::String(v.to_str()?.to_owned()), + Value::String(v) => DataKey::String(Cow::Owned(v.to_str()?.to_owned())), Value::Table(_) => Err("table is not supported".into_lua_err())?, Value::Function(_) => Err("function is not supported".into_lua_err())?, Value::Thread(_) => Err("thread is not supported".into_lua_err())?, @@ -123,13 +158,20 @@ impl Sendable { } fn key_to_value(lua: &Lua, key: DataKey) -> mlua::Result { + Ok(match key { + DataKey::Url(u) => Value::UserData(lua.create_any_userdata(u)?), + key => Self::key_to_value_ref(lua, &key)?, + }) + } + + fn key_to_value_ref(lua: &Lua, key: &DataKey) -> mlua::Result { Ok(match key { DataKey::Nil => Value::Nil, - DataKey::Boolean(k) => Value::Boolean(k), - DataKey::Integer(k) => Value::Integer(k), - DataKey::Number(k) => Value::Number(k.get()), - DataKey::String(k) => Value::String(lua.create_string(k)?), - DataKey::Url(k) => Value::UserData(lua.create_any_userdata(k)?), + DataKey::Boolean(b) => Value::Boolean(*b), + DataKey::Integer(i) => Value::Integer(*i), + DataKey::Number(n) => Value::Number(n.get()), + DataKey::String(s) => Value::String(lua.create_string(s.as_ref())?), + DataKey::Url(u) => Value::UserData(lua.create_any_userdata(u.clone())?), }) } } diff --git a/yazi-fm/src/app/app.rs b/yazi-fm/src/app/app.rs index b91d4b669..1d08e5aa0 100644 --- a/yazi-fm/src/app/app.rs +++ b/yazi-fm/src/app/app.rs @@ -1,4 +1,4 @@ -use std::{collections::VecDeque, sync::atomic::Ordering}; +use std::sync::atomic::Ordering; use anyhow::Result; use crossterm::event::KeyEvent; @@ -71,9 +71,9 @@ impl App { } #[inline] - fn dispatch_seq(&mut self, mut cmds: VecDeque, layer: Layer) { - if let Some(cmd) = cmds.pop_front() { - Executor::new(self).execute(cmd, layer); + fn dispatch_seq(&mut self, mut cmds: Vec, layer: Layer) { + if let Some(last) = cmds.pop() { + Executor::new(self).execute(last, layer); } if !cmds.is_empty() { emit!(Seq(cmds, layer)); diff --git a/yazi-fm/src/app/commands/plugin.rs b/yazi-fm/src/app/commands/plugin.rs index 2df1ebe22..39bba4a1b 100644 --- a/yazi-fm/src/app/commands/plugin.rs +++ b/yazi-fm/src/app/commands/plugin.rs @@ -23,7 +23,7 @@ impl App { } if opt.mode == PluginMode::Async { - return self.cx.tasks.plugin_micro(opt.id.into_owned(), opt.args); + return self.cx.tasks.plugin_micro(opt); } else if opt.mode == PluginMode::Sync && hits { return self.plugin_do(opt); } @@ -47,7 +47,7 @@ impl App { }; if opt.mode.auto_then(chunk.sync_entry) != PluginMode::Sync { - return self.cx.tasks.plugin_micro(opt.id.into_owned(), opt.args); + return self.cx.tasks.plugin_micro(opt); } match LUA.named_registry_value::("rt") { @@ -66,7 +66,12 @@ impl App { if let Some(cb) = opt.cb { cb(&LUA, plugin) } else { - plugin.call_method("entry", Sendable::list_to_table(&LUA, opt.args)?) + let job = LUA.create_table_from([("args", Sendable::dict_to_table(&LUA, opt.args)?)])?; + + // TODO: remove this + yazi_plugin::isolate::install_entry_warn(&LUA, &job, opt._old_args).ok(); + + plugin.call_method("entry", job) } }); } diff --git a/yazi-fm/src/input/input.rs b/yazi-fm/src/input/input.rs index d247d97ca..eb17a3b15 100644 --- a/yazi-fm/src/input/input.rs +++ b/yazi-fm/src/input/input.rs @@ -21,7 +21,7 @@ impl<'a> Input<'a> { bail!("Highlighting is disabled"); } - let (theme, syntaxes) = futures::executor::block_on(Highlighter::init()); + let (theme, syntaxes) = Highlighter::init(); if let Some(syntax) = syntaxes.find_syntax_by_name("Bourne Again Shell (bash)") { let mut h = HighlightLines::new(syntax, theme); let regions = h.highlight_line(self.cx.input.value(), syntaxes)?; diff --git a/yazi-plugin/preset/plugins/archive.lua b/yazi-plugin/preset/plugins/archive.lua index 82437a6a2..bf4122244 100644 --- a/yazi-plugin/preset/plugins/archive.lua +++ b/yazi-plugin/preset/plugins/archive.lua @@ -1,16 +1,16 @@ local M = {} -function M:peek() - local limit = self.area.h +function M:peek(job) + local limit = job.area.h local paths, sizes = {}, {} - local files, bound, code = self.list_files({ "-p", tostring(self.file.url) }, self.skip, limit) + local files, bound, code = self.list_files({ "-p", tostring(job.file.url) }, job.skip, limit) if code ~= 0 then - return ya.preview_widgets(self, { + return ya.preview_widgets(job, { ui.Text( code == 2 and "File list in this archive is encrypted" or "Failed to start both `7z` and `7zz`. Do you have 7-zip installed?" - ):area(self.area), + ):area(job.area), }) end @@ -33,17 +33,17 @@ function M:peek() end end - if self.skip > 0 and bound < self.skip + limit then - ya.manager_emit("peek", { math.max(0, bound - limit), only_if = self.file.url, upper_bound = true }) + if job.skip > 0 and bound < job.skip + limit then + ya.manager_emit("peek", { math.max(0, bound - limit), only_if = job.file.url, upper_bound = true }) else - ya.preview_widgets(self, { - ui.Text(paths):area(self.area), - ui.Text(sizes):area(self.area):align(ui.Text.RIGHT), + ya.preview_widgets(job, { + ui.Text(paths):area(job.area), + ui.Text(sizes):area(job.area):align(ui.Text.RIGHT), }) end end -function M:seek(units) require("code").seek(self, units) end +function M:seek(job) require("code"):seek(job) end function M.spawn_7z(args) local last_err = nil diff --git a/yazi-plugin/preset/plugins/code.lua b/yazi-plugin/preset/plugins/code.lua index c4e9e61f8..6c4509d41 100644 --- a/yazi-plugin/preset/plugins/code.lua +++ b/yazi-plugin/preset/plugins/code.lua @@ -1,28 +1,28 @@ local M = {} -function M:peek() - local err, bound = ya.preview_code(self) +function M:peek(job) + local err, bound = ya.preview_code(job) if bound then - ya.manager_emit("peek", { bound, only_if = self.file.url, upper_bound = true }) + ya.manager_emit("peek", { bound, only_if = job.file.url, upper_bound = true }) elseif err and not err:find("cancelled", 1, true) then - ya.preview_widgets(self, { - ui.Text(err):area(self.area):reverse(), + ya.preview_widgets(job, { + ui.Text(err):area(job.area):reverse(), }) end end -function M:seek(units) +function M:seek(job) local h = cx.active.current.hovered - if not h or h.url ~= self.file.url then + if not h or h.url ~= job.file.url then return end - local step = math.floor(units * self.area.h / 10) - step = step == 0 and ya.clamp(-1, units, 1) or step + local step = math.floor(job.units * job.area.h / 10) + step = step == 0 and ya.clamp(-1, job.units, 1) or step ya.manager_emit("peek", { math.max(0, cx.active.preview.skip + step), - only_if = self.file.url, + only_if = job.file.url, }) end diff --git a/yazi-plugin/preset/plugins/empty.lua b/yazi-plugin/preset/plugins/empty.lua index be6716231..f2482efe2 100644 --- a/yazi-plugin/preset/plugins/empty.lua +++ b/yazi-plugin/preset/plugins/empty.lua @@ -1,35 +1,35 @@ local M = {} -function M:msg(s) ya.preview_widgets(self, { ui.Text(s):area(self.area):reverse():wrap(ui.Text.WRAP) }) end +function M:msg(job, s) ya.preview_widgets(self, { ui.Text(s):area(job.area):reverse():wrap(ui.Text.WRAP) }) end -function M:peek() - local path = tostring(self.file.url) +function M:peek(job) + local path = tostring(job.file.url) if path:sub(1, 6) ~= "/proc/" then - return self:msg("Empty file") + return self:msg(job, "Empty file") end - local limit = self.area.h + local limit = job.area.h local i, lines = 0, {} local ok, err = pcall(function() for line in io.lines(path) do i = i + 1 - if i > self.skip + limit then + if i > job.skip + limit then break - elseif i > self.skip then + elseif i > job.skip then lines[#lines + 1] = line end end end) if not ok then - self:msg(err) - elseif self.skip > 0 and i < self.skip + limit then - ya.manager_emit("peek", { math.max(0, i - limit), only_if = self.file.url, upper_bound = true }) + self:msg(job, err) + elseif job.skip > 0 and i < job.skip + limit then + ya.manager_emit("peek", { math.max(0, i - limit), only_if = job.file.url, upper_bound = true }) else - ya.preview_widgets(self, { ui.Text(lines):area(self.area) }) + ya.preview_widgets(job, { ui.Text(lines):area(job.area) }) end end -function M:seek(units) require("code").seek(self, units) end +function M:seek(job) require("code"):seek(job) end return M diff --git a/yazi-plugin/preset/plugins/extract.lua b/yazi-plugin/preset/plugins/extract.lua index 7dc78f407..779d7e839 100644 --- a/yazi-plugin/preset/plugins/extract.lua +++ b/yazi-plugin/preset/plugins/extract.lua @@ -11,17 +11,19 @@ function M:setup() end) end -function M:entry(args) - if not args[1] then +function M:entry(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 from, to, pwd = Url(args[1]), args[2] ~= "" and Url(args[2]) or nil, "" + local pwd = "" while true do if not M:try_with(from, pwd, to) then break - elseif args[3] ~= "--noisy" then - fail("'%s' is password-protected, please extract it individually and enter the password", args[1]) + elseif not job.args.noisy then + fail("'%s' is password-protected, please extract it individually and enter the password", from) end local value, event = ya.input { @@ -78,7 +80,7 @@ function M:tidy(from, to, tmp) local only = #outs == 1 if only and not outs[1].cha.is_dir and require("archive").is_tar(outs[1].url) then - self:entry { tostring(outs[1].url), tostring(to) } + self:entry { args = { tostring(outs[1].url), tostring(to) } } fs.remove("file", outs[1].url) fs.remove("dir", tmp) return diff --git a/yazi-plugin/preset/plugins/file.lua b/yazi-plugin/preset/plugins/file.lua index 2053b3bc4..2860a8320 100644 --- a/yazi-plugin/preset/plugins/file.lua +++ b/yazi-plugin/preset/plugins/file.lua @@ -1,8 +1,8 @@ local M = {} -function M:peek() +function M:peek(job) local cmd = os.getenv("YAZI_FILE_ONE") or "file" - local output, err = Command(cmd):args({ "-bL", "--", tostring(self.file.url) }):stdout(Command.PIPED):output() + local output, err = Command(cmd):args({ "-bL", "--", tostring(job.file.url) }):stdout(Command.PIPED):output() local text if output then @@ -11,7 +11,7 @@ function M:peek() text = ui.Text(string.format("Failed to start `%s`, error: %s", cmd, err)) end - ya.preview_widgets(self, { text:area(self.area):wrap(ui.Text.WRAP) }) + ya.preview_widgets(job, { text:area(job.area):wrap(ui.Text.WRAP) }) end function M:seek() end @@ -33,7 +33,7 @@ function M:spot_base(job) local url, cha = job.file.url, job.file.cha local spotter = PLUGIN.spotter(url, job.mime) local previewer = PLUGIN.previewer(url, job.mime) - local fetchers = PLUGIN.fetchers(url, job.mime) + local fetchers = PLUGIN.fetchers(job.file, job.mime) local preloaders = PLUGIN.preloaders(url, job.mime) for i, v in ipairs(fetchers) do diff --git a/yazi-plugin/preset/plugins/folder.lua b/yazi-plugin/preset/plugins/folder.lua index 0af4bf32e..bd544bd45 100644 --- a/yazi-plugin/preset/plugins/folder.lua +++ b/yazi-plugin/preset/plugins/folder.lua @@ -1,19 +1,19 @@ local M = {} -function M:peek() +function M:peek(job) local folder = cx.active.preview.folder - if not folder or folder.cwd ~= self.file.url then + if not folder or folder.cwd ~= job.file.url then return end - local bound = math.max(0, #folder.files - self.area.h) - if self.skip > bound then - return ya.manager_emit("peek", { bound, only_if = self.file.url, upper_bound = true }) + local bound = math.max(0, #folder.files - job.area.h) + if job.skip > bound then + return ya.manager_emit("peek", { bound, only_if = job.file.url, upper_bound = true }) end if #folder.files == 0 then - return ya.preview_widgets(self, { - ui.Text(folder.stage.is_loading and "Loading..." or "No items"):area(self.area):align(ui.Text.CENTER), + return ya.preview_widgets(job, { + ui.Text(folder.stage.is_loading and "Loading..." or "No items"):area(job.area):align(ui.Text.CENTER), }) end @@ -22,20 +22,20 @@ function M:peek() entities[#entities + 1] = Entity:new(f):redraw() end - ya.preview_widgets(self, { - ui.List(entities):area(self.area), - table.unpack(Marker:new(self.area, folder):redraw()), + ya.preview_widgets(job, { + ui.List(entities):area(job.area), + table.unpack(Marker:new(job.area, folder):redraw()), }) end -function M:seek(units) +function M:seek(job) local folder = cx.active.preview.folder - if folder and folder.cwd == self.file.url then - local step = math.floor(units * self.area.h / 10) - local bound = math.max(0, #folder.files - self.area.h) + if folder and folder.cwd == job.file.url then + local step = math.floor(job.units * job.area.h / 10) + local bound = math.max(0, #folder.files - job.area.h) ya.manager_emit("peek", { ya.clamp(0, cx.active.preview.skip + step, bound), - only_if = self.file.url, + only_if = job.file.url, }) end end diff --git a/yazi-plugin/preset/plugins/font.lua b/yazi-plugin/preset/plugins/font.lua index 567d72a2a..ca8257587 100644 --- a/yazi-plugin/preset/plugins/font.lua +++ b/yazi-plugin/preset/plugins/font.lua @@ -2,21 +2,21 @@ local TEXT = "ABCDEFGHIJKLM\nNOPQRSTUVWXYZ\nabcdefghijklm\nnopqrstuvwxyz\n123456 local M = {} -function M:peek() - local start, cache = os.clock(), ya.file_cache(self) - if not cache or self:preload() ~= 1 then +function M:peek(job) + local start, cache = os.clock(), ya.file_cache(job) + if not cache or self:preload(job) ~= 1 then return end ya.sleep(math.max(0, PREVIEW.image_delay / 1000 + start - os.clock())) - ya.image_show(cache, self.area) - ya.preview_widgets(self, {}) + ya.image_show(cache, job.area) + ya.preview_widgets(job, {}) end function M:seek() end -function M:preload() - local cache = ya.file_cache(self) +function M:preload(job) + local cache = ya.file_cache(job) if not cache or fs.cha(cache) then return 1 end @@ -27,7 +27,7 @@ function M:preload() "-gravity", "center", "-font", - tostring(self.file.url), + tostring(job.file.url), "-pointsize", "64", "xc:white", diff --git a/yazi-plugin/preset/plugins/image.lua b/yazi-plugin/preset/plugins/image.lua index ffe7ffee3..b76fd51a2 100644 --- a/yazi-plugin/preset/plugins/image.lua +++ b/yazi-plugin/preset/plugins/image.lua @@ -1,25 +1,25 @@ local M = {} -function M:peek() - local start, url = os.clock(), ya.file_cache(self) +function M:peek(job) + local start, url = os.clock(), ya.file_cache(job) if not url or not fs.cha(url) then - url = self.file.url + url = job.file.url end ya.sleep(math.max(0, PREVIEW.image_delay / 1000 + start - os.clock())) - ya.image_show(url, self.area) - ya.preview_widgets(self, {}) + ya.image_show(url, job.area) + ya.preview_widgets(job, {}) end function M:seek() end -function M:preload() - local cache = ya.file_cache(self) +function M:preload(job) + local cache = ya.file_cache(job) if not cache or fs.cha(cache) then return 1 end - return ya.image_precache(self.file.url, cache) and 1 or 2 + return ya.image_precache(job.file.url, cache) and 1 or 2 end function M:spot(job) diff --git a/yazi-plugin/preset/plugins/json.lua b/yazi-plugin/preset/plugins/json.lua index 85e649a15..a6c1b8553 100644 --- a/yazi-plugin/preset/plugins/json.lua +++ b/yazi-plugin/preset/plugins/json.lua @@ -1,46 +1,46 @@ local M = {} -function M:peek() +function M:peek(job) local child = Command("jq") :args({ "-C", "--tab", ".", - tostring(self.file.url), + tostring(job.file.url), }) :stdout(Command.PIPED) :stderr(Command.PIPED) :spawn() if not child then - return require("code").peek(self) + return require("code"):peek(job) end - local limit = self.area.h + local limit = job.area.h local i, lines = 0, "" repeat local next, event = child:read_line() if event == 1 then - return require("code").peek(self) + return require("code"):peek(job) elseif event ~= 0 then break end i = i + 1 - if i > self.skip then + if i > job.skip then lines = lines .. next end - until i >= self.skip + limit + until i >= job.skip + limit child:start_kill() - if self.skip > 0 and i < self.skip + limit then - ya.manager_emit("peek", { math.max(0, i - limit), only_if = self.file.url, upper_bound = true }) + if job.skip > 0 and i < job.skip + limit then + ya.manager_emit("peek", { math.max(0, i - limit), only_if = job.file.url, upper_bound = true }) else lines = lines:gsub("\t", string.rep(" ", PREVIEW.tab_size)) - ya.preview_widgets(self, { ui.Text.parse(lines):area(self.area) }) + ya.preview_widgets(job, { ui.Text.parse(lines):area(job.area) }) end end -function M:seek(units) require("code").seek(self, units) end +function M:seek(job) require("code"):seek(job) end return M diff --git a/yazi-plugin/preset/plugins/magick.lua b/yazi-plugin/preset/plugins/magick.lua index f74387dfc..ab7492027 100644 --- a/yazi-plugin/preset/plugins/magick.lua +++ b/yazi-plugin/preset/plugins/magick.lua @@ -1,20 +1,20 @@ local M = {} -function M:peek() - local start, cache = os.clock(), ya.file_cache(self) - if not cache or self:preload() ~= 1 then +function M:peek(job) + local start, cache = os.clock(), ya.file_cache(job) + if not cache or self:preload(job) ~= 1 then return end ya.sleep(math.max(0, PREVIEW.image_delay / 1000 + start - os.clock())) - ya.image_show(cache, self.area) - ya.preview_widgets(self, {}) + ya.image_show(cache, job.area) + ya.preview_widgets(job, {}) end function M:seek() end -function M:preload() - local cache = ya.file_cache(self) +function M:preload(job) + local cache = ya.file_cache(job) if not cache or fs.cha(cache) then return 1 end @@ -22,7 +22,7 @@ function M:preload() local status, err = Command("magick"):args({ "-density", "200", - tostring(self.file.url), + tostring(job.file.url), "-flatten", "-resize", string.format("%dx%d^", PREVIEW.max_width, PREVIEW.max_height), diff --git a/yazi-plugin/preset/plugins/pdf.lua b/yazi-plugin/preset/plugins/pdf.lua index b8a66a841..54138eca1 100644 --- a/yazi-plugin/preset/plugins/pdf.lua +++ b/yazi-plugin/preset/plugins/pdf.lua @@ -1,32 +1,32 @@ local M = {} -function M:peek() - local start, cache = os.clock(), ya.file_cache(self) - if not cache or self:preload() ~= 1 then +function M:peek(job) + local start, cache = os.clock(), ya.file_cache(job) + if not cache or self:preload(job) ~= 1 then return end ya.sleep(math.max(0, PREVIEW.image_delay / 1000 + start - os.clock())) - ya.image_show(cache, self.area) - ya.preview_widgets(self, {}) + ya.image_show(cache, job.area) + ya.preview_widgets(job, {}) end -function M:seek(units) +function M:seek(job) local h = cx.active.current.hovered - if h and h.url == self.file.url then - local step = ya.clamp(-1, units, 1) - ya.manager_emit("peek", { math.max(0, cx.active.preview.skip + step), only_if = self.file.url }) + if h and h.url == job.file.url then + local step = ya.clamp(-1, job.units, 1) + ya.manager_emit("peek", { math.max(0, cx.active.preview.skip + step), only_if = job.file.url }) end end -function M:preload() - local cache = ya.file_cache(self) +function M:preload(job) + local cache = ya.file_cache(job) if not cache or fs.cha(cache) then return 1 end local output = Command("pdftoppm") - :args({ "-singlefile", "-jpeg", "-jpegopt", "quality=75", "-f", tostring(self.skip + 1), tostring(self.file.url) }) + :args({ "-singlefile", "-jpeg", "-jpegopt", "quality=75", "-f", tostring(job.skip + 1), tostring(job.file.url) }) :stdout(Command.PIPED) :stderr(Command.PIPED) :output() @@ -35,8 +35,8 @@ function M:preload() return 0 elseif not output.status.success then local pages = tonumber(output.stderr:match("the last page %((%d+)%)")) or 0 - if self.skip > 0 and pages > 0 then - ya.manager_emit("peek", { math.max(0, pages - 1), only_if = self.file.url, upper_bound = true }) + if job.skip > 0 and pages > 0 then + ya.manager_emit("peek", { math.max(0, pages - 1), only_if = job.file.url, upper_bound = true }) end return 0 end diff --git a/yazi-plugin/preset/plugins/video.lua b/yazi-plugin/preset/plugins/video.lua index c93a3f87b..832e1476b 100644 --- a/yazi-plugin/preset/plugins/video.lua +++ b/yazi-plugin/preset/plugins/video.lua @@ -1,34 +1,34 @@ local M = {} -function M:peek() - local start, cache = os.clock(), ya.file_cache(self) - if not cache or self:preload() ~= 1 then +function M:peek(job) + local start, cache = os.clock(), ya.file_cache(job) + if not cache or self:preload(job) ~= 1 then return end ya.sleep(math.max(0, PREVIEW.image_delay / 1000 + start - os.clock())) - ya.image_show(cache, self.area) - ya.preview_widgets(self, {}) + ya.image_show(cache, job.area) + ya.preview_widgets(job, {}) end -function M:seek(units) +function M:seek(job) local h = cx.active.current.hovered - if h and h.url == self.file.url then + if h and h.url == job.file.url then ya.manager_emit("peek", { - math.max(0, cx.active.preview.skip + units), - only_if = self.file.url, + math.max(0, cx.active.preview.skip + job.units), + only_if = job.file.url, }) end end -function M:preload() - local percent = 5 + self.skip +function M:preload(job) + local percent = 5 + job.skip if percent > 95 then - ya.manager_emit("peek", { 90, only_if = self.file.url, upper_bound = true }) + ya.manager_emit("peek", { 90, only_if = job.file.url, upper_bound = true }) return 2 end - local cache = ya.file_cache(self) + local cache = ya.file_cache(job) if not cache then return 1 end @@ -38,7 +38,7 @@ function M:preload() return 1 end - local meta, err = self.list_meta(self.file.url, "format=duration") + local meta, err = self.list_meta(job.file.url, "format=duration") if not meta then ya.err(tostring(err)) return 0 @@ -53,7 +53,7 @@ function M:preload() "-v", "quiet", "-hwaccel", "auto", "-skip_frame", "nokey", "-ss", ss, "-an", "-sn", "-dn", - "-i", tostring(self.file.url), + "-i", tostring(job.file.url), "-vframes", 1, "-q:v", qv, "-vf", string.format("scale=%d:-2:flags=fast_bilinear", PREVIEW.max_width), diff --git a/yazi-plugin/src/config/plugin.rs b/yazi-plugin/src/config/plugin.rs index 2d0cd265d..aa9c2f918 100644 --- a/yazi-plugin/src/config/plugin.rs +++ b/yazi-plugin/src/config/plugin.rs @@ -1,18 +1,19 @@ use mlua::{Function, Lua, UserData}; use yazi_config::PLUGIN; -use crate::url::UrlRef; +use crate::{file::FileRef, url::UrlRef}; pub(super) struct Plugin; impl Plugin { pub(super) fn fetchers(lua: &Lua) -> mlua::Result { - lua.create_function(|lua, (url, mime): (UrlRef, mlua::String)| { + lua.create_function(|lua, (file, mime): (FileRef, mlua::String)| { let factors = |s: &str| match s { "mime" => !mime.as_bytes().is_empty(), + "dummy" => file.cha.is_dummy(), _ => false, }; - lua.create_sequence_from(PLUGIN.fetchers(&url, &mime.to_str()?, factors).map(Fetcher)) + lua.create_sequence_from(PLUGIN.fetchers(&file.url, &mime.to_str()?, factors).map(Fetcher)) }) } diff --git a/yazi-plugin/src/elements/paragraph.rs b/yazi-plugin/src/elements/paragraph.rs index 65490a68a..df5d5ce6c 100644 --- a/yazi-plugin/src/elements/paragraph.rs +++ b/yazi-plugin/src/elements/paragraph.rs @@ -5,10 +5,9 @@ use mlua::{FromLua, Lua, MetaMethod, MultiValue, Table, Value}; use super::Rect; use crate::RtRef; -static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); - #[inline] fn warn_deprecated(id: Option<&str>) { + static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); if !WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) { let id = match id { Some(id) => format!("`{id}.yazi` plugin"), diff --git a/yazi-plugin/src/external/highlighter.rs b/yazi-plugin/src/external/highlighter.rs index ab5b79f26..d75b22bc8 100644 --- a/yazi-plugin/src/external/highlighter.rs +++ b/yazi-plugin/src/external/highlighter.rs @@ -1,14 +1,14 @@ -use std::{borrow::Cow, io::Cursor, mem, path::{Path, PathBuf}}; +use std::{borrow::Cow, io::Cursor, mem, path::{Path, PathBuf}, sync::OnceLock}; use anyhow::{Result, anyhow}; use ratatui::{layout::Size, text::{Line, Span, Text}}; use syntect::{LoadingError, dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}}; -use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}, sync::OnceCell}; +use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}}; use yazi_config::{PREVIEW, THEME, preview::PreviewWrap}; use yazi_shared::{Ids, errors::PeekError, replace_to_printable}; static INCR: Ids = Ids::new(); -static SYNTECT: OnceCell<(Theme, SyntaxSet)> = OnceCell::const_new(); +static SYNTECT: OnceLock<(Theme, SyntaxSet)> = OnceLock::new(); pub struct Highlighter { path: PathBuf, @@ -18,21 +18,19 @@ impl Highlighter { #[inline] pub fn new(path: &Path) -> Self { Self { path: path.to_owned() } } - pub async fn init() -> (&'static Theme, &'static SyntaxSet) { + pub fn init() -> (&'static Theme, &'static SyntaxSet) { let f = || { - tokio::task::spawn_blocking(|| { - let theme = std::fs::File::open(&THEME.manager.syntect_theme) - .map_err(LoadingError::Io) - .and_then(|f| ThemeSet::load_from_reader(&mut std::io::BufReader::new(f))) - .or_else(|_| ThemeSet::load_from_reader(&mut Cursor::new(yazi_prebuild::ansi_theme()))); + let theme = std::fs::File::open(&THEME.manager.syntect_theme) + .map_err(LoadingError::Io) + .and_then(|f| ThemeSet::load_from_reader(&mut std::io::BufReader::new(f))) + .or_else(|_| ThemeSet::load_from_reader(&mut Cursor::new(yazi_prebuild::ansi_theme()))); - let syntaxes = dumps::from_uncompressed_data(yazi_prebuild::syntaxes()); + let syntaxes = dumps::from_uncompressed_data(yazi_prebuild::syntaxes()); - (theme.unwrap(), syntaxes.unwrap()) - }) + (theme.unwrap(), syntaxes.unwrap()) }; - let (theme, syntaxes) = SYNTECT.get_or_try_init(f).await.unwrap(); + let (theme, syntaxes) = SYNTECT.get_or_init(f); (theme, syntaxes) } @@ -103,10 +101,11 @@ impl Highlighter { syntax: &'static SyntaxReference, ) -> Result, PeekError> { let ticket = INCR.current(); - let (theme, syntaxes) = Self::init().await; tokio::task::spawn_blocking(move || { + let (theme, syntaxes) = Self::init(); let mut h = HighlightLines::new(syntax, theme); + for line in before { if ticket != INCR.current() { return Err("Highlighting cancelled".into()); @@ -131,7 +130,7 @@ impl Highlighter { } async fn find_syntax(path: &Path) -> Result<&'static SyntaxReference> { - let (_, syntaxes) = Self::init().await; + let (_, syntaxes) = Self::init(); let name = path.file_name().map(|n| n.to_string_lossy()).unwrap_or_default(); if let Some(s) = syntaxes.find_syntax_by_extension(&name) { return Ok(s); diff --git a/yazi-plugin/src/isolate/entry.rs b/yazi-plugin/src/isolate/entry.rs index 65f4cc7be..73422a888 100644 --- a/yazi-plugin/src/isolate/entry.rs +++ b/yazi-plugin/src/isolate/entry.rs @@ -1,25 +1,75 @@ -use mlua::{ExternalError, ExternalResult, ObjectLike, Table}; +use std::time::Duration; + +use mlua::{ExternalError, ExternalResult, Lua, MetaMethod, ObjectLike, Table, Value}; use tokio::runtime::Handle; use yazi_dds::Sendable; +use yazi_proxy::options::PluginOpt; use yazi_shared::event::Data; use super::slim_lua; -use crate::loader::LOADER; +use crate::{RtRef, loader::LOADER}; -pub async fn entry(name: String, args: Vec) -> mlua::Result<()> { - LOADER.ensure(&name).await.into_lua_err()?; +pub async fn entry(opt: PluginOpt) -> mlua::Result<()> { + LOADER.ensure(&opt.id).await.into_lua_err()?; tokio::task::spawn_blocking(move || { - let lua = slim_lua(&name)?; - let plugin: Table = if let Some(b) = LOADER.read().get(&name) { - lua.load(b.as_bytes()).set_name(name).call(())? + let lua = slim_lua(&opt.id)?; + let plugin: Table = if let Some(b) = LOADER.read().get(opt.id.as_ref()) { + lua.load(b.as_bytes()).set_name(opt.id.as_ref()).call(())? } else { return Err("unloaded plugin".into_lua_err()); }; - Handle::current() - .block_on(plugin.call_async_method("entry", Sendable::list_to_table(&lua, args))) + let job = lua.create_table_from([("args", Sendable::dict_to_table(&lua, opt.args)?)])?; + + // TODO: remove this + install_entry_warn(&lua, &job, opt._old_args).ok(); + + Handle::current().block_on(plugin.call_async_method("entry", job)) }) .await .into_lua_err()? } + +#[inline] +fn warn_deprecated(id: Option<&str>) { + static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); + if !WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) { + let id = match id { + Some(id) => format!("`{id}.yazi` plugin"), + None => "`init.lua` config".to_owned(), + }; + let s = "The first parameter of `entry()` has been replaced by the new `job` instead of the previous `args`. + +Please use `job.args` instead `args` to access the arguments in your {id}. + +See #1966 for details: https://github.com/sxyazi/yazi/pull/1966"; + yazi_proxy::AppProxy::notify(yazi_proxy::options::NotifyOpt { + title: "Deprecated API".to_owned(), + content: s.replace("{id}", &id), + level: yazi_proxy::options::NotifyLevel::Warn, + timeout: Duration::from_secs(20), + }); + } +} + +pub fn install_entry_warn(lua: &Lua, job: &Table, old_args: Vec) -> mlua::Result<()> { + let mt = lua.create_table_from([( + MetaMethod::Index.name(), + lua.create_function(|lua, (ts, key): (Table, mlua::String)| { + if key.as_bytes().first().is_some_and(|&b| b.is_ascii_digit()) { + warn_deprecated(lua.named_registry_value::("rt")?.current()); + lua + .load(mlua::chunk! { + return rawget($ts, "__unsafe_args")[tonumber($key)] + }) + .call::(()) + } else { + ts.raw_get::(key) + } + })?, + )])?; + job.set_metatable(Some(mt)); + job.raw_set("__unsafe_args", Sendable::list_to_table(lua, old_args)?)?; + Ok(()) +} diff --git a/yazi-plugin/src/isolate/fetch.rs b/yazi-plugin/src/isolate/fetch.rs index def75f1b3..54d6a5ac9 100644 --- a/yazi-plugin/src/isolate/fetch.rs +++ b/yazi-plugin/src/isolate/fetch.rs @@ -1,21 +1,22 @@ use mlua::{ExternalError, ExternalResult, IntoLua, ObjectLike, Table}; use tokio::runtime::Handle; use yazi_config::LAYOUT; +use yazi_dds::Sendable; +use yazi_shared::event::CmdCow; use super::slim_lua; use crate::{bindings::Cast, elements::Rect, file::File, loader::LOADER}; -pub async fn fetch(name: &str, files: Vec) -> mlua::Result { +pub async fn fetch(cmd: CmdCow, files: Vec) -> mlua::Result { if files.is_empty() { return Ok(1); } - LOADER.ensure(name).await.into_lua_err()?; + LOADER.ensure(&cmd.name).await.into_lua_err()?; - let name = name.to_owned(); tokio::task::spawn_blocking(move || { - let lua = slim_lua(&name)?; - let plugin: Table = if let Some(b) = LOADER.read().get(&name) { - lua.load(b.as_bytes()).set_name(name).call(())? + let lua = slim_lua(&cmd.name)?; + let plugin: Table = if let Some(b) = LOADER.read().get(&cmd.name) { + lua.load(b.as_bytes()).set_name(&cmd.name).call(())? } else { return Err("unloaded plugin".into_lua_err()); }; @@ -31,6 +32,7 @@ pub async fn fetch(name: &str, files: Vec) -> mlua::Resul "fetch", lua.create_table_from([ ("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?), + ("args", Sendable::dict_to_table_ref(&lua, &cmd.args)?.into_lua(&lua)?), ("files", files.into_lua(&lua)?), ])?, )) diff --git a/yazi-plugin/src/isolate/peek.rs b/yazi-plugin/src/isolate/peek.rs index e6728884d..ce57a9086 100644 --- a/yazi-plugin/src/isolate/peek.rs +++ b/yazi-plugin/src/isolate/peek.rs @@ -1,18 +1,19 @@ -use std::borrow::Cow; +use std::{borrow::Cow, time::Duration}; -use mlua::{ExternalError, ExternalResult, HookTriggers, ObjectLike, Table, VmState}; +use mlua::{ExternalError, ExternalResult, HookTriggers, IntoLua, Lua, MetaMethod, MultiValue, ObjectLike, Table, VmState}; use tokio::{runtime::Handle, select}; use tokio_util::sync::CancellationToken; use tracing::error; use yazi_config::LAYOUT; +use yazi_dds::Sendable; use yazi_proxy::{AppProxy, options::{PluginCallback, PluginOpt}}; use yazi_shared::event::Cmd; use super::slim_lua; -use crate::{LUA, bindings::{Cast, Window}, elements::Rect, file::File, loader::LOADER}; +use crate::{RtRef, bindings::Cast, elements::Rect, file::File, loader::LOADER}; pub fn peek( - cmd: &Cmd, + cmd: &'static Cmd, file: yazi_shared::fs::File, mime: Cow<'static, str>, skip: usize, @@ -20,12 +21,11 @@ pub fn peek( let ct = CancellationToken::new(); let (ct1, ct2) = (ct.clone(), ct.clone()); - let name = cmd.name.to_owned(); tokio::task::spawn_blocking(move || { let future = async { - LOADER.ensure(&name).await.into_lua_err()?; + LOADER.ensure(&cmd.name).await.into_lua_err()?; - let lua = slim_lua(&name)?; + let lua = slim_lua(&cmd.name)?; lua.set_hook( HookTriggers::new().on_calls().on_returns().every_nth_instruction(2000), move |_, dbg| { @@ -37,20 +37,24 @@ pub fn peek( }, ); - let plugin: Table = if let Some(b) = LOADER.read().get(&name) { - lua.load(b.as_bytes()).set_name(name).call(())? + let plugin: Table = if let Some(b) = LOADER.read().get(&cmd.name) { + lua.load(b.as_bytes()).set_name(&cmd.name).call(())? } else { return Err("unloaded plugin".into_lua_err()); }; - plugin.raw_set("file", File::cast(&lua, file)?)?; - plugin.raw_set("mime", mime)?; - plugin.raw_set("skip", skip)?; - plugin.raw_set("area", Rect::from(LAYOUT.get().preview))?; - // TODO: remove this as it's not safe in async context, - // there may be race conditions between the `window` and `area` - plugin.raw_set("window", Window::get())?; - - if ct2.is_cancelled() { Ok(()) } else { plugin.call_async_method("peek", ()).await } + + let job = lua.create_table_from([ + ("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?), + ("args", Sendable::dict_to_table_ref(&lua, &cmd.args)?.into_lua(&lua)?), + ("file", File::cast(&lua, file)?.into_lua(&lua)?), + ("mime", mime.into_lua(&lua)?), + ("skip", skip.into_lua(&lua)?), + ])?; + + // TODO: remove this + install_warn_mt(&lua, &plugin, job.clone()).ok(); + + if ct2.is_cancelled() { Ok(()) } else { plugin.call_async_method("peek", job).await } }; let result = Handle::current().block_on(async { @@ -70,17 +74,73 @@ pub fn peek( ct } -pub fn peek_sync(cmd: &Cmd, file: yazi_shared::fs::File, mime: Cow<'static, str>, skip: usize) { - let cb: PluginCallback = Box::new(move |_, plugin| { - plugin.raw_set("file", File::cast(&LUA, file)?)?; - plugin.raw_set("mime", mime)?; - plugin.raw_set("skip", skip)?; - plugin.raw_set("area", Rect::from(LAYOUT.get().preview))?; - // TODO: remove this as it's not safe in async context, - // there may be race conditions between the `window` and `area` - plugin.raw_set("window", Window::get())?; - plugin.call_method("peek", ()) +pub fn peek_sync( + cmd: &'static Cmd, + file: yazi_shared::fs::File, + mime: Cow<'static, str>, + skip: usize, +) { + let cb: PluginCallback = Box::new(move |lua, plugin| { + let job = lua.create_table_from([ + ("area", Rect::from(LAYOUT.get().preview).into_lua(lua)?), + ("args", Sendable::dict_to_table_ref(lua, &cmd.args)?.into_lua(lua)?), + ("file", File::cast(lua, file)?.into_lua(lua)?), + ("mime", mime.into_lua(lua)?), + ("skip", skip.into_lua(lua)?), + ])?; + + // TODO: remove this + install_warn_mt(lua, &plugin, job.clone()).ok(); + + plugin.call_method("peek", job) }); AppProxy::plugin(PluginOpt::new_callback(&cmd.name, cb)); } + +pub(super) fn install_warn_mt(lua: &Lua, plugin: &Table, job: Table) -> mlua::Result<()> { + let mt = lua.create_table_from([( + MetaMethod::Index.name(), + lua.create_function(|lua, (ts, key): (Table, mlua::String)| { + let b = key.as_bytes() == b"file" + || key.as_bytes() == b"mime" + || key.as_bytes() == b"skip" + || key.as_bytes() == b"area"; + if b { + warn_deprecated(lua.named_registry_value::("rt")?.current()); + } + lua + .load(mlua::chunk! { + if $b then + return rawget($ts, "__unsafe_job")[$key] + else + return rawget($ts, $key) + end + }) + .call::(()) + })?, + )])?; + plugin.set_metatable(Some(mt)); + plugin.raw_set("__unsafe_job", job.clone())?; + Ok(()) +} + +#[inline] +fn warn_deprecated(id: Option<&str>) { + static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); + if !WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) { + let id = match id { + Some(id) => format!("`{id}.yazi` plugin"), + None => "`init.lua` config".to_owned(), + }; + let s = "The file list for peek() and preload() method has been moved from `self` to the first parameter `job` of the method to avoid conflicts with the plugin's own attributes. + +Please use the new `job` parameter in your {id} instead of `self`. See #1966 for details: https://github.com/sxyazi/yazi/pull/1966"; + yazi_proxy::AppProxy::notify(yazi_proxy::options::NotifyOpt { + title: "Deprecated API".to_owned(), + content: s.replace("{id}", &id), + level: yazi_proxy::options::NotifyLevel::Warn, + timeout: Duration::from_secs(20), + }); + } +} diff --git a/yazi-plugin/src/isolate/preload.rs b/yazi-plugin/src/isolate/preload.rs index b8397a783..012acf6b1 100644 --- a/yazi-plugin/src/isolate/preload.rs +++ b/yazi-plugin/src/isolate/preload.rs @@ -1,27 +1,34 @@ -use mlua::{ExternalError, ExternalResult, ObjectLike, Table}; +use mlua::{ExternalError, ExternalResult, IntoLua, ObjectLike, Table}; use tokio::runtime::Handle; use yazi_config::LAYOUT; +use yazi_dds::Sendable; +use yazi_shared::event::Cmd; use super::slim_lua; use crate::{bindings::Cast, elements::Rect, file::File, loader::LOADER}; -pub async fn preload(name: &str, file: yazi_shared::fs::File) -> mlua::Result { - LOADER.ensure(name).await.into_lua_err()?; +pub async fn preload(cmd: &'static Cmd, file: yazi_shared::fs::File) -> mlua::Result { + LOADER.ensure(&cmd.name).await.into_lua_err()?; - let name = name.to_owned(); tokio::task::spawn_blocking(move || { - let lua = slim_lua(&name)?; - let plugin: Table = if let Some(b) = LOADER.read().get(&name) { - lua.load(b.as_bytes()).set_name(name).call(())? + let lua = slim_lua(&cmd.name)?; + let plugin: Table = if let Some(b) = LOADER.read().get(&cmd.name) { + lua.load(b.as_bytes()).set_name(&cmd.name).call(())? } else { return Err("unloaded plugin".into_lua_err()); }; - plugin.raw_set("skip", 0)?; - plugin.raw_set("area", Rect::from(LAYOUT.get().preview))?; - plugin.raw_set("file", File::cast(&lua, file)?)?; + let job = lua.create_table_from([ + ("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?), + ("args", Sendable::dict_to_table_ref(&lua, &cmd.args)?.into_lua(&lua)?), + ("file", File::cast(&lua, file)?.into_lua(&lua)?), + ("skip", 0.into_lua(&lua)?), + ])?; - Handle::current().block_on(plugin.call_async_method("preload", ())) + // TODO: remove this + super::install_warn_mt(&lua, &plugin, job.clone()).ok(); + + Handle::current().block_on(plugin.call_async_method("preload", job)) }) .await .into_lua_err()? diff --git a/yazi-plugin/src/isolate/seek.rs b/yazi-plugin/src/isolate/seek.rs index 490696c2b..a85305a36 100644 --- a/yazi-plugin/src/isolate/seek.rs +++ b/yazi-plugin/src/isolate/seek.rs @@ -1,15 +1,19 @@ -use mlua::ObjectLike; +use mlua::{IntoLua, ObjectLike}; use yazi_config::LAYOUT; use yazi_proxy::{AppProxy, options::{PluginCallback, PluginOpt}}; use yazi_shared::event::Cmd; -use crate::{LUA, bindings::Cast, elements::Rect, file::File}; +use crate::{bindings::Cast, elements::Rect, file::File}; pub fn seek_sync(cmd: &Cmd, file: yazi_shared::fs::File, units: i16) { - let cb: PluginCallback = Box::new(move |_, plugin| { - plugin.raw_set("file", File::cast(&LUA, file)?)?; - plugin.raw_set("area", Rect::from(LAYOUT.get().preview))?; - plugin.call_method("seek", units) + let cb: PluginCallback = Box::new(move |lua, plugin| { + let job = lua.create_table_from([ + ("file", File::cast(lua, file)?.into_lua(lua)?), + ("area", Rect::from(LAYOUT.get().preview).into_lua(lua)?), + ("units", units.into_lua(lua)?), + ])?; + + plugin.call_method("seek", job) }); AppProxy::plugin(PluginOpt::new_callback(&cmd.name, cb)); diff --git a/yazi-plugin/src/isolate/spot.rs b/yazi-plugin/src/isolate/spot.rs index 0137c7d15..f1b0f01b0 100644 --- a/yazi-plugin/src/isolate/spot.rs +++ b/yazi-plugin/src/isolate/spot.rs @@ -4,13 +4,14 @@ use mlua::{ExternalError, ExternalResult, HookTriggers, IntoLua, ObjectLike, Tab use tokio::{runtime::Handle, select}; use tokio_util::sync::CancellationToken; use tracing::error; +use yazi_dds::Sendable; use yazi_shared::event::Cmd; use super::slim_lua; use crate::{bindings::Cast, file::File, loader::LOADER}; pub fn spot( - cmd: &Cmd, + cmd: &'static Cmd, file: yazi_shared::fs::File, mime: Cow<'static, str>, skip: usize, @@ -18,12 +19,11 @@ pub fn spot( let ct = CancellationToken::new(); let (ct1, ct2) = (ct.clone(), ct.clone()); - let name = cmd.name.to_owned(); tokio::task::spawn_blocking(move || { let future = async { - LOADER.ensure(&name).await.into_lua_err()?; + LOADER.ensure(&cmd.name).await.into_lua_err()?; - let lua = slim_lua(&name)?; + let lua = slim_lua(&cmd.name)?; lua.set_hook( HookTriggers::new().on_calls().on_returns().every_nth_instruction(2000), move |_, dbg| { @@ -35,18 +35,19 @@ pub fn spot( }, ); - let plugin: Table = if let Some(b) = LOADER.read().get(&name) { - lua.load(b.as_bytes()).set_name(name).call(())? + let plugin: Table = if let Some(b) = LOADER.read().get(&cmd.name) { + lua.load(b.as_bytes()).set_name(&cmd.name).call(())? } else { return Err("unloaded plugin".into_lua_err()); }; - let args = lua.create_table_from([ + let job = lua.create_table_from([ + ("args", Sendable::dict_to_table_ref(&lua, &cmd.args)?.into_lua(&lua)?), ("file", File::cast(&lua, file)?.into_lua(&lua)?), ("mime", mime.into_lua(&lua)?), ("skip", skip.into_lua(&lua)?), ])?; - if ct2.is_cancelled() { Ok(()) } else { plugin.call_async_method("spot", args).await } + if ct2.is_cancelled() { Ok(()) } else { plugin.call_async_method("spot", job).await } }; let result = Handle::current().block_on(async { diff --git a/yazi-plugin/src/utils/call.rs b/yazi-plugin/src/utils/call.rs index 06e43d1e9..91a98211a 100644 --- a/yazi-plugin/src/utils/call.rs +++ b/yazi-plugin/src/utils/call.rs @@ -1,11 +1,11 @@ -use std::collections::HashMap; +use std::{borrow::Cow, collections::HashMap}; use mlua::{ExternalError, Function, Lua, ObjectLike, Table, Value}; use tracing::error; use yazi_config::LAYOUT; use yazi_dds::Sendable; use yazi_macro::{emit, render}; -use yazi_shared::{Layer, event::{Cmd, Data}}; +use yazi_shared::{Layer, event::{Cmd, Data, DataKey}}; use super::Utils; @@ -61,16 +61,19 @@ impl Utils { }) } - fn parse_args(t: Table) -> mlua::Result> { + fn parse_args(t: Table) -> mlua::Result> { let mut args = HashMap::with_capacity(t.raw_len()); for pair in t.pairs::() { let (k, v) = pair?; match k { Value::Integer(i) if i > 0 => { - args.insert((i - 1).to_string(), Sendable::value_to_data(v)?); + args.insert(DataKey::Integer(i), Sendable::value_to_data(v)?); } Value::String(s) => { - args.insert(s.to_str()?.replace('_', "-"), Sendable::value_to_data(v)?); + args.insert( + DataKey::String(Cow::Owned(s.to_str()?.replace('_', "-"))), + Sendable::value_to_data(v)?, + ); } _ => return Err("invalid key in cmd".into_lua_err()), } diff --git a/yazi-plugin/src/utils/preview.rs b/yazi-plugin/src/utils/preview.rs index e62a46e1f..382ade427 100644 --- a/yazi-plugin/src/utils/preview.rs +++ b/yazi-plugin/src/utils/preview.rs @@ -20,14 +20,15 @@ impl TryFrom
for PreviewLock { type Error = mlua::Error; fn try_from(t: Table) -> Result { - let file: FileRef = t.raw_get("file")?; + // TODO: use `raw_get` instead of `get` + let file: FileRef = t.get("file")?; Ok(Self { url: file.url_owned(), cha: file.cha, - mime: t.raw_get("mime")?, + mime: t.get("mime")?, - skip: t.raw_get("skip")?, - area: t.raw_get("area")?, + skip: t.get("skip")?, + area: t.get("area")?, data: Default::default(), }) } @@ -36,7 +37,7 @@ impl TryFrom
for PreviewLock { impl Utils { pub(super) fn preview_code(lua: &Lua) -> mlua::Result { lua.create_async_function(|lua, t: Table| async move { - let area: Area = t.raw_get("area")?; + let area: Area = t.get("area")?; // TODO: use `raw_get` instead of `get` let mut lock = PreviewLock::try_from(t)?; let inner = match Highlighter::new(&lock.url).highlight(lock.skip, area.size()).await { diff --git a/yazi-plugin/src/utils/sync.rs b/yazi-plugin/src/utils/sync.rs index c177b5f32..58b3bb851 100644 --- a/yazi-plugin/src/utils/sync.rs +++ b/yazi-plugin/src/utils/sync.rs @@ -40,7 +40,7 @@ impl Utils { } async fn retrieve(id: &str, calls: usize, args: MultiValue) -> mlua::Result> { - let args = Sendable::values_to_vec(args)?; + let args = Sendable::values_to_list(args)?; let (tx, rx) = oneshot::channel::>(); let callback: PluginCallback = { @@ -55,7 +55,7 @@ impl Utils { .chain(args.into_iter().map(|d| Sendable::data_to_value(lua, d))) .collect::>()?; - let values = Sendable::values_to_vec(block.call(MultiValue::from_vec(args))?)?; + let values = Sendable::values_to_list(block.call(MultiValue::from_vec(args))?)?; tx.send(values).map_err(|_| "send failed".into_lua_err()) }) }; diff --git a/yazi-proxy/src/options/plugin.rs b/yazi-proxy/src/options/plugin.rs index 02b737688..3ac624583 100644 --- a/yazi-proxy/src/options/plugin.rs +++ b/yazi-proxy/src/options/plugin.rs @@ -1,17 +1,20 @@ -use std::borrow::Cow; +use std::{borrow::Cow, collections::HashMap, fmt::Debug}; use anyhow::bail; use mlua::{Lua, Table}; -use yazi_shared::event::{CmdCow, Data}; +use yazi_shared::event::{Cmd, CmdCow, Data, DataKey}; pub type PluginCallback = Box mlua::Result<()> + Send + Sync>; #[derive(Default)] pub struct PluginOpt { pub id: Cow<'static, str>, + pub args: HashMap, pub mode: PluginMode, - pub args: Vec, pub cb: Option, + + // TODO: remove this + pub _old_args: Vec, } impl TryFrom for PluginOpt { @@ -26,10 +29,13 @@ impl TryFrom for PluginOpt { bail!("plugin id cannot be empty"); }; - let args = if let Some(s) = c.str("args") { - shell_words::split(s)?.into_iter().map(Data::String).collect() + let (args, _old_args) = if let Some(s) = c.str("args") { + ( + Cmd::parse_args(shell_words::split(s)?.into_iter())?, + shell_words::split(s)?.into_iter().map(Data::String).collect(), + ) } else { - vec![] + (Default::default(), Default::default()) }; let mut mode = c.str("mode").map(Into::into).unwrap_or_default(); @@ -46,7 +52,18 @@ Please add `--- @sync entry` metadata at the head of your `{id}` plugin instead. }); } - Ok(Self { id, mode, args, cb: c.take_any("callback") }) + Ok(Self { id, args, mode, cb: c.take_any("callback"), _old_args }) + } +} + +impl Debug for PluginOpt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PluginOpt") + .field("id", &self.id) + .field("args", &self.args) + .field("mode", &self.mode) + .field("cb", &self.cb.is_some()) + .finish() } } diff --git a/yazi-scheduler/src/plugin/op.rs b/yazi-scheduler/src/plugin/op.rs index 183fa658e..86b513815 100644 --- a/yazi-scheduler/src/plugin/op.rs +++ b/yazi-scheduler/src/plugin/op.rs @@ -1,4 +1,4 @@ -use yazi_shared::event::Data; +use yazi_proxy::options::PluginOpt; #[derive(Debug)] pub enum PluginOp { @@ -15,8 +15,6 @@ impl PluginOp { #[derive(Debug)] pub struct PluginOpEntry { - pub id: usize, - // TODO: remove these fields and use `CmdCow` instead - pub name: String, - pub args: Vec, + pub id: usize, + pub opt: PluginOpt, } diff --git a/yazi-scheduler/src/plugin/plugin.rs b/yazi-scheduler/src/plugin/plugin.rs index dd37705ec..0af379cf3 100644 --- a/yazi-scheduler/src/plugin/plugin.rs +++ b/yazi-scheduler/src/plugin/plugin.rs @@ -21,7 +21,7 @@ impl Plugin { pub async fn work(&self, op: PluginOp) -> Result<()> { match op { PluginOp::Entry(task) => { - isolate::entry(task.name, task.args).await?; + isolate::entry(task.opt).await?; } } Ok(()) @@ -30,7 +30,7 @@ impl Plugin { pub async fn micro(&self, task: PluginOpEntry) -> Result<()> { self.prog.send(TaskProg::New(task.id, 0))?; - if let Err(e) = isolate::entry(task.name, task.args).await { + if let Err(e) = isolate::entry(task.opt).await { self.fail(task.id, format!("Micro plugin failed:\n{e}"))?; return Ok(()); } diff --git a/yazi-scheduler/src/prework/op.rs b/yazi-scheduler/src/prework/op.rs index eb95508f7..3532dd6a2 100644 --- a/yazi-scheduler/src/prework/op.rs +++ b/yazi-scheduler/src/prework/op.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use yazi_config::plugin::{FetcherProps, PreloaderProps}; +use yazi_config::plugin::{Fetcher, Preloader}; use yazi_shared::{Throttle, fs::Url}; #[derive(Debug)] @@ -20,17 +20,17 @@ impl PreworkOp { } } -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct PreworkOpFetch { pub id: usize, - pub plugin: FetcherProps, + pub plugin: &'static Fetcher, pub targets: Vec, } #[derive(Clone, Debug)] pub struct PreworkOpLoad { pub id: usize, - pub plugin: PreloaderProps, + pub plugin: &'static Preloader, pub target: yazi_shared::fs::File, } diff --git a/yazi-scheduler/src/prework/prework.rs b/yazi-scheduler/src/prework/prework.rs index 5cb749240..5bb75c2ae 100644 --- a/yazi-scheduler/src/prework/prework.rs +++ b/yazi-scheduler/src/prework/prework.rs @@ -6,7 +6,7 @@ use tokio::sync::mpsc; use tracing::error; use yazi_config::Priority; use yazi_plugin::isolate; -use yazi_shared::fs::{FilesOp, Url, calculate_size}; +use yazi_shared::{event::CmdCow, fs::{FilesOp, Url, calculate_size}}; use super::{PreworkOp, PreworkOpFetch, PreworkOpLoad, PreworkOpSize}; use crate::{HIGH, NORMAL, TaskOp, TaskProg}; @@ -31,13 +31,13 @@ impl Prework { match op { PreworkOp::Fetch(task) => { let urls: Vec<_> = task.targets.iter().map(|f| f.url_owned()).collect(); - let result = isolate::fetch(task.plugin.name, task.targets).await; + let result = isolate::fetch(CmdCow::from(&task.plugin.run), task.targets).await; if let Err(e) = result { self.fail( task.id, format!( "Failed to run fetcher `{}` with:\n{}\n\nError message:\n{e}", - task.plugin.name, + task.plugin.run.name, urls.iter().map(ToString::to_string).collect::>().join("\n") ), )?; @@ -48,36 +48,36 @@ impl Prework { if code & 1 == 0 { error!( "Returned {code} when running fetcher `{}` with:\n{}", - task.plugin.name, + task.plugin.run.name, urls.iter().map(ToString::to_string).collect::>().join("\n") ); } if code & 2 != 0 { let mut loaded = self.loaded.lock(); for url in urls { - loaded.get_mut(&url).map(|x| *x &= !(1 << task.plugin.id)); + loaded.get_mut(&url).map(|x| *x &= !(1 << task.plugin.idx)); } } self.prog.send(TaskProg::Adv(task.id, 1, 0))?; } PreworkOp::Load(task) => { let url = task.target.url_owned(); - let result = isolate::preload(task.plugin.name, task.target).await; + let result = isolate::preload(&task.plugin.run, task.target).await; if let Err(e) = result { self.fail( task.id, - format!("Failed to run preloader `{}` with `{url}`:\n{e}", task.plugin.name), + format!("Failed to run preloader `{}` with `{url}`:\n{e}", task.plugin.run.name), )?; return Err(e.into()); }; let code = result.unwrap(); if code & 1 == 0 { - error!("Returned {code} when running preloader `{}` with `{url}`", task.plugin.name); + error!("Returned {code} when running preloader `{}` with `{url}`", task.plugin.run.name); } if code & 2 != 0 { let mut loaded = self.loaded.lock(); - loaded.get_mut(&url).map(|x| *x &= !(1 << task.plugin.id)); + loaded.get_mut(&url).map(|x| *x &= !(1 << task.plugin.idx)); } self.prog.send(TaskProg::Adv(task.id, 1, 0))?; } diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index e2c6dd3bc..6299c6034 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -6,8 +6,8 @@ use parking_lot::Mutex; use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::JoinHandle}; use yazi_config::{TASKS, open::Opener, plugin::{Fetcher, Preloader}}; use yazi_dds::Pump; -use yazi_proxy::ManagerProxy; -use yazi_shared::{Throttle, event::Data, fs::{Url, must_be_dir, remove_dir_clean, unique_name}}; +use yazi_proxy::{ManagerProxy, options::PluginOpt}; +use yazi_shared::{Throttle, fs::{Url, must_be_dir, remove_dir_clean, unique_name}}; use super::{Ongoing, TaskProg, TaskStage}; use crate::{HIGH, LOW, NORMAL, TaskKind, TaskOp, file::{File, FileOpDelete, FileOpHardlink, FileOpLink, FileOpPaste, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, prework::{Prework, PreworkOpFetch, PreworkOpLoad, PreworkOpSize}, process::{Process, ProcessOpBg, ProcessOpBlock, ProcessOpOrphan}}; @@ -206,21 +206,17 @@ impl Scheduler { }) } - pub fn plugin_micro(&self, name: String, args: Vec) { - let id = self.ongoing.lock().add(TaskKind::User, format!("Run micro plugin `{name}`")); + pub fn plugin_micro(&self, opt: PluginOpt) { + let id = self.ongoing.lock().add(TaskKind::User, format!("Run micro plugin `{}`", opt.id)); let plugin = self.plugin.clone(); - self.send_micro( - id, - NORMAL, - async move { plugin.micro(PluginOpEntry { id, name, args }).await }, - ); + self.send_micro(id, NORMAL, async move { plugin.micro(PluginOpEntry { id, opt }).await }); } - pub fn plugin_macro(&self, name: String, args: Vec) { - let id = self.ongoing.lock().add(TaskKind::User, format!("Run macro plugin `{name}`")); + pub fn plugin_macro(&self, opt: PluginOpt) { + let id = self.ongoing.lock().add(TaskKind::User, format!("Run macro plugin `{}`", opt.id)); - self.plugin.macro_(PluginOpEntry { id, name, args }).ok(); + self.plugin.macro_(PluginOpEntry { id, opt }).ok(); } pub fn fetch_paged(&self, fetcher: &'static Fetcher, targets: Vec) { @@ -229,10 +225,9 @@ impl Scheduler { format!("Run fetcher `{}` with {} target(s)", fetcher.run.name, targets.len()), ); - let plugin = fetcher.into(); let prework = self.prework.clone(); self.send_micro(id, NORMAL, async move { - prework.fetch(PreworkOpFetch { id, plugin, targets }).await + prework.fetch(PreworkOpFetch { id, plugin: fetcher, targets }).await }); } @@ -240,14 +235,11 @@ impl Scheduler { let id = self.ongoing.lock().add(TaskKind::Preload, format!("Run preloader `{}`", preloader.run.name)); - let plugin = preloader.into(); let target = target.clone(); let prework = self.prework.clone(); - self.send_micro( - id, - NORMAL, - async move { prework.load(PreworkOpLoad { id, plugin, target }).await }, - ); + self.send_micro(id, NORMAL, async move { + prework.load(PreworkOpLoad { id, plugin: preloader, target }).await + }); } pub fn prework_size(&self, targets: Vec<&Url>) { diff --git a/yazi-shared/src/event/cmd.rs b/yazi-shared/src/event/cmd.rs index 6f5cfcd01..d07ce2e6a 100644 --- a/yazi-shared/src/event/cmd.rs +++ b/yazi-shared/src/event/cmd.rs @@ -1,15 +1,15 @@ -use std::{any::Any, collections::HashMap, fmt::{self, Display}, mem, str::FromStr}; +use std::{any::Any, borrow::Cow, collections::HashMap, fmt::{self, Display}, mem, str::FromStr}; -use anyhow::bail; +use anyhow::{Result, bail}; use serde::{Deserialize, de}; -use super::Data; +use super::{Data, DataKey}; use crate::fs::Url; #[derive(Debug, Default)] pub struct Cmd { pub name: String, - pub args: HashMap, + pub args: HashMap, } impl Cmd { @@ -23,68 +23,74 @@ impl Cmd { args: args .iter() .enumerate() - .map(|(i, s)| (i.to_string(), Data::String(s.to_string()))) + .map(|(i, s)| (DataKey::Integer((i + 1) as i64), Data::String(s.to_string()))) .collect(), } } // --- With #[inline] - pub fn with(mut self, name: impl ToString, value: impl ToString) -> Self { - self.args.insert(name.to_string(), Data::String(value.to_string())); + pub fn with(mut self, name: impl Into, value: impl ToString) -> Self { + self.args.insert(name.into(), Data::String(value.to_string())); self } #[inline] - pub fn with_opt(mut self, name: impl ToString, value: Option) -> Self { + pub fn with_opt(mut self, name: impl Into, value: Option) -> Self { if let Some(v) = value { - self.args.insert(name.to_string(), Data::String(v.to_string())); + self.args.insert(name.into(), Data::String(v.to_string())); } self } #[inline] - pub fn with_bool(mut self, name: impl ToString, state: bool) -> Self { - self.args.insert(name.to_string(), Data::Boolean(state)); + pub fn with_bool(mut self, name: impl Into, state: bool) -> Self { + self.args.insert(name.into(), Data::Boolean(state)); self } #[inline] - pub fn with_any(mut self, name: impl ToString, data: impl Any + Send + Sync) -> Self { - self.args.insert(name.to_string(), Data::Any(Box::new(data))); + pub fn with_any(mut self, name: impl Into, data: impl Any + Send + Sync) -> Self { + self.args.insert(name.into(), Data::Any(Box::new(data))); self } // --- Get #[inline] - pub fn get(&self, name: &str) -> Option<&Data> { self.args.get(name) } + pub fn get(&self, name: impl Into) -> Option<&Data> { self.args.get(&name.into()) } #[inline] - pub fn str(&self, name: &str) -> Option<&str> { self.get(name).and_then(Data::as_str) } + pub fn str(&self, name: impl Into) -> Option<&str> { + self.get(name).and_then(Data::as_str) + } #[inline] - pub fn bool(&self, name: &str) -> bool { self.maybe_bool(name).unwrap_or(false) } + pub fn bool(&self, name: impl Into) -> bool { self.maybe_bool(name).unwrap_or(false) } #[inline] - pub fn maybe_bool(&self, name: &str) -> Option { self.get(name).and_then(Data::as_bool) } + pub fn maybe_bool(&self, name: impl Into) -> Option { + self.get(name).and_then(Data::as_bool) + } #[inline] - pub fn first(&self) -> Option<&Data> { self.get("0") } + pub fn first(&self) -> Option<&Data> { self.get(1) } #[inline] - pub fn first_str(&self) -> Option<&str> { self.str("0") } + pub fn first_str(&self) -> Option<&str> { self.str(1) } // --- Take #[inline] - pub fn take(&mut self, name: &str) -> Option { self.args.remove(name) } + pub fn take(&mut self, name: impl Into) -> Option { + self.args.remove(&name.into()) + } #[inline] - pub fn take_str(&mut self, name: &str) -> Option { + pub fn take_str(&mut self, name: impl Into) -> Option { if let Some(Data::String(s)) = self.take(name) { Some(s) } else { None } } #[inline] - pub fn take_first(&mut self) -> Option { self.take("0") } + pub fn take_first(&mut self) -> Option { self.take(1) } #[inline] pub fn take_first_str(&mut self) -> Option { @@ -95,8 +101,35 @@ impl Cmd { pub fn take_first_url(&mut self) -> Option { self.take_first()?.into_url() } #[inline] - pub fn take_any(&mut self, name: &str) -> Option { - self.args.remove(name).and_then(|d| d.into_any()) + pub fn take_any(&mut self, name: impl Into) -> Option { + self.args.remove(&name.into()).and_then(|d| d.into_any()) + } + + // Parse + pub fn parse_args(words: impl Iterator) -> Result> { + let mut i = 0i64; + words + .into_iter() + .map(|word| { + let Some(arg) = word.strip_prefix("--") else { + i += 1; + return Ok((DataKey::Integer(i), Data::String(word))); + }; + + let mut parts = arg.splitn(2, '='); + let Some(key) = parts.next().map(|s| s.to_owned()) else { + bail!("invalid argument: {arg}"); + }; + + let val = if let Some(val) = parts.next() { + Data::String(val.to_owned()) + } else { + Data::Boolean(true) + }; + + Ok((DataKey::String(Cow::Owned(key)), val)) + }) + .collect() } } @@ -104,14 +137,20 @@ impl Display for Cmd { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.name)?; for (k, v) in &self.args { - if k.as_bytes().first().is_some_and(|b| b.is_ascii_digit()) { - if let Some(s) = v.as_str() { - write!(f, " {s}")?; + match k { + DataKey::Integer(_) => { + if let Some(s) = v.as_str() { + write!(f, " {s}")?; + } + } + DataKey::String(k) => { + if v.as_bool().is_some_and(|b| b) { + write!(f, " --{k}")?; + } else if let Some(s) = v.as_str() { + write!(f, " --{k}={s}")?; + } } - } else if v.as_bool().is_some_and(|b| b) { - write!(f, " --{k}")?; - } else if let Some(s) = v.as_str() { - write!(f, " --{k}={s}")?; + _ => {} } } Ok(()) @@ -128,27 +167,7 @@ impl FromStr for Cmd { bail!("command name cannot be empty"); } - let mut cmd = Cmd { name: mem::take(&mut args[0]), ..Default::default() }; - let mut i = 0usize; - for arg in args.into_iter().skip(1) { - let Some(arg) = arg.strip_prefix("--") else { - cmd.args.insert(i.to_string(), Data::String(arg)); - i += 1; - continue; - }; - - let mut parts = arg.splitn(2, '='); - let Some(key) = parts.next().map(|s| s.to_owned()) else { - bail!("invalid argument: {arg}"); - }; - - if let Some(val) = parts.next() { - cmd.args.insert(key, Data::String(val.to_owned())); - } else { - cmd.args.insert(key, Data::Boolean(true)); - } - } - Ok(cmd) + Ok(Cmd { name: mem::take(&mut args[0]), args: Cmd::parse_args(args.into_iter().skip(1))? }) } } diff --git a/yazi-shared/src/event/cow.rs b/yazi-shared/src/event/cow.rs index ac5dd97aa..916eaf406 100644 --- a/yazi-shared/src/event/cow.rs +++ b/yazi-shared/src/event/cow.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, ops::Deref}; -use super::{Cmd, Data}; +use super::{Cmd, Data, DataKey}; use crate::fs::Url; #[derive(Debug)] @@ -30,7 +30,7 @@ impl Deref for CmdCow { impl CmdCow { #[inline] - pub fn try_take(&mut self, name: &str) -> Option { + pub fn try_take(&mut self, name: impl Into) -> Option { match self { Self::Owned(c) => c.take(name), Self::Borrowed(_) => None, @@ -38,7 +38,7 @@ impl CmdCow { } #[inline] - pub fn take_str(&mut self, name: &str) -> Option> { + pub fn take_str(&mut self, name: impl Into) -> Option> { match self { Self::Owned(c) => c.take_str(name).map(Cow::Owned), Self::Borrowed(c) => c.str(name).map(Cow::Borrowed), @@ -46,7 +46,7 @@ impl CmdCow { } #[inline] - pub fn take_url(&mut self, name: &str) -> Option { + pub fn take_url(&mut self, name: impl Into) -> Option { match self { Self::Owned(c) => c.take(name).and_then(Data::into_url), Self::Borrowed(c) => c.get(name).and_then(Data::to_url), @@ -69,7 +69,7 @@ impl CmdCow { } #[inline] - pub fn take_any(&mut self, name: &str) -> Option { + pub fn take_any(&mut self, name: impl Into) -> Option { match self { Self::Owned(c) => c.take_any(name), Self::Borrowed(_) => None, diff --git a/yazi-shared/src/event/data.rs b/yazi-shared/src/event/data.rs index 40f7bebb3..cdbbc3f15 100644 --- a/yazi-shared/src/event/data.rs +++ b/yazi-shared/src/event/data.rs @@ -1,4 +1,4 @@ -use std::{any::Any, collections::HashMap}; +use std::{any::Any, borrow::Cow, collections::HashMap}; use serde::{Deserialize, Serialize}; @@ -57,7 +57,7 @@ impl Data { } } - pub fn into_dict_string(self) -> HashMap { + pub fn into_dict_string(self) -> HashMap, String> { let Self::Dict(dict) = self else { return Default::default(); }; @@ -89,7 +89,7 @@ pub enum DataKey { Boolean(bool), Integer(i64), Number(OrderedFloat), - String(String), + String(Cow<'static, str>), #[serde(skip_deserializing)] Url(Url), } @@ -97,6 +97,26 @@ pub enum DataKey { impl DataKey { #[inline] pub fn is_integer(&self) -> bool { matches!(self, Self::Integer(_)) } + + #[inline] + pub fn as_str(&self) -> Option<&str> { + match self { + Self::String(s) => Some(s), + _ => None, + } + } +} + +impl From for DataKey { + fn from(value: usize) -> Self { Self::Integer(value as i64) } +} + +impl From<&'static str> for DataKey { + fn from(value: &'static str) -> Self { Self::String(Cow::Borrowed(value)) } +} + +impl From for DataKey { + fn from(value: String) -> Self { Self::String(Cow::Owned(value)) } } // --- Macros diff --git a/yazi-shared/src/event/event.rs b/yazi-shared/src/event/event.rs index cd8d03b9b..a18b39d17 100644 --- a/yazi-shared/src/event/event.rs +++ b/yazi-shared/src/event/event.rs @@ -1,4 +1,4 @@ -use std::{collections::VecDeque, ffi::OsString}; +use std::ffi::OsString; use crossterm::event::{KeyEvent, MouseEvent}; use tokio::sync::mpsc; @@ -12,7 +12,7 @@ static RX: RoCell> = RoCell::new(); #[derive(Debug)] pub enum Event { Call(CmdCow, Layer), - Seq(VecDeque, Layer), + Seq(Vec, Layer), Render, Key(KeyEvent), Mouse(MouseEvent),