From 507a2e390cf9876ea079091acd9fe23ad4894743 Mon Sep 17 00:00:00 2001 From: sxyazi Date: Thu, 27 Jun 2024 00:10:48 +0800 Subject: [PATCH] perf!: reimplement and significantly speed up archive previewing --- Cargo.lock | 22 ++++--- yazi-adapter/Cargo.toml | 2 +- yazi-config/Cargo.toml | 2 +- yazi-config/preset/theme.toml | 20 +++--- yazi-config/preset/yazi.toml | 2 +- yazi-config/src/plugin/fetcher.rs | 3 +- yazi-config/src/plugin/plugin.rs | 2 +- yazi-config/src/theme/icons.rs | 16 +++-- yazi-core/Cargo.toml | 2 +- yazi-core/src/manager/commands/open.rs | 2 +- yazi-core/src/manager/watcher.rs | 2 +- yazi-fm/Cargo.toml | 2 +- yazi-fm/src/lives/file.rs | 30 +++------ yazi-plugin/Cargo.toml | 4 +- yazi-plugin/preset/plugins/archive.lua | 69 ++++++++++++++++++++- yazi-plugin/src/bindings/file.rs | 29 --------- yazi-plugin/src/bindings/mod.rs | 4 -- yazi-plugin/src/{bindings => cha}/cha.rs | 42 ++++++++++++- yazi-plugin/src/cha/mod.rs | 12 ++++ yazi-plugin/src/external/lsar.rs | 79 ------------------------ yazi-plugin/src/external/mod.rs | 2 - yazi-plugin/src/file/file.rs | 63 +++++++++++++++++++ yazi-plugin/src/file/mod.rs | 12 ++++ yazi-plugin/src/fs/fs.rs | 2 +- yazi-plugin/src/isolate/fetch.rs | 2 +- yazi-plugin/src/isolate/isolate.rs | 7 ++- yazi-plugin/src/isolate/peek.rs | 2 +- yazi-plugin/src/isolate/preload.rs | 2 +- yazi-plugin/src/isolate/seek.rs | 2 +- yazi-plugin/src/lib.rs | 2 + yazi-plugin/src/lua.rs | 4 +- yazi-plugin/src/utils/cache.rs | 2 +- yazi-plugin/src/utils/preview.rs | 25 +------- yazi-plugin/src/utils/target.rs | 19 +----- yazi-scheduler/src/preload/prework.rs | 22 +++++-- yazi-shared/Cargo.toml | 2 +- yazi-shared/src/fs/cha.rs | 24 +++---- yazi-shared/src/fs/file.rs | 5 ++ 38 files changed, 299 insertions(+), 246 deletions(-) delete mode 100644 yazi-plugin/src/bindings/file.rs rename yazi-plugin/src/{bindings => cha}/cha.rs (58%) create mode 100644 yazi-plugin/src/cha/mod.rs delete mode 100644 yazi-plugin/src/external/lsar.rs create mode 100644 yazi-plugin/src/file/file.rs create mode 100644 yazi-plugin/src/file/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 351ca5a31..e12338114 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1024,6 +1024,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1242,7 +1251,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09697a6cec88e7f58a02c7ab5c18c611c6907c8654613df9cc0192658a4fb859" dependencies = [ - "itertools", + "itertools 0.12.1", "once_cell", "proc-macro-error", "proc-macro2", @@ -1575,19 +1584,20 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.26.3" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" dependencies = [ "bitflags 2.5.0", "cassowary", "compact_str", "crossterm", - "itertools", + "itertools 0.13.0", "lru", "paste", "stability", "strum", + "strum_macros", "unicode-segmentation", "unicode-truncate", "unicode-width", @@ -2291,7 +2301,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" dependencies = [ - "itertools", + "itertools 0.12.1", "unicode-width", ] @@ -2892,8 +2902,6 @@ dependencies = [ "mlua", "parking_lot", "ratatui", - "serde", - "serde_json", "shell-escape", "shell-words", "syntect", diff --git a/yazi-adapter/Cargo.toml b/yazi-adapter/Cargo.toml index 822661c37..7b6b21bfb 100644 --- a/yazi-adapter/Cargo.toml +++ b/yazi-adapter/Cargo.toml @@ -23,7 +23,7 @@ futures = "0.3.30" image = "=0.24.9" imagesize = "0.13.0" kamadak-exif = "0.5.5" -ratatui = "0.26.3" +ratatui = "0.27.0" scopeguard = "1.2.0" tokio = { version = "1.38.0", features = [ "full" ] } diff --git a/yazi-config/Cargo.toml b/yazi-config/Cargo.toml index e0e932950..8df9f74db 100644 --- a/yazi-config/Cargo.toml +++ b/yazi-config/Cargo.toml @@ -18,7 +18,7 @@ bitflags = "2.5.0" crossterm = "0.27.0" globset = "0.4.14" indexmap = "2.2.6" -ratatui = "0.26.3" +ratatui = "0.27.0" serde = { version = "1.0.203", features = [ "derive" ] } toml = { version = "0.8.14", features = [ "preserve_order" ] } validator = { version = "0.18.1", features = [ "derive" ] } diff --git a/yazi-config/preset/theme.toml b/yazi-config/preset/theme.toml index 0eb9cf660..5a0b86246 100644 --- a/yazi-config/preset/theme.toml +++ b/yazi-config/preset/theme.toml @@ -764,18 +764,18 @@ exts = [ ] conds = [ # Special files - { cond = "orphan", text = "" }, - { cond = "link" , text = "" }, - { cond = "block" , text = "" }, - { cond = "char" , text = "" }, - { cond = "fifo" , text = "" }, - { cond = "sock" , text = "" }, - { cond = "sticky", text = "" }, + { if = "orphan", text = "" }, + { if = "link" , text = "" }, + { if = "block" , text = "" }, + { if = "char" , text = "" }, + { if = "fifo" , text = "" }, + { if = "sock" , text = "" }, + { if = "sticky", text = "" }, # Fallback - { cond = "dir", text = "" }, - { cond = "exec", text = "" }, - { cond = "!dir", text = "" }, + { if = "dir", text = "" }, + { if = "exec", text = "" }, + { if = "!dir", text = "" }, ] # : }}} diff --git a/yazi-config/preset/yazi.toml b/yazi-config/preset/yazi.toml index c466b1031..d844c0f57 100644 --- a/yazi-config/preset/yazi.toml +++ b/yazi-config/preset/yazi.toml @@ -83,7 +83,7 @@ suppress_preload = false fetchers = [ # Mimetype - { id = "mime", name = "*", cond = "!mime", run = "mime", prio = "high" }, + { id = "mime", name = "*", run = "mime", if = "!mime", prio = "high" }, ] preloaders = [ # Image diff --git a/yazi-config/src/plugin/fetcher.rs b/yazi-config/src/plugin/fetcher.rs index eece4136e..c08a710be 100644 --- a/yazi-config/src/plugin/fetcher.rs +++ b/yazi-config/src/plugin/fetcher.rs @@ -9,7 +9,8 @@ pub struct Fetcher { pub idx: u8, pub id: String, - pub cond: Option, + #[serde(rename = "if")] + pub if_: Option, pub name: Option, pub mime: Option, pub run: Cmd, diff --git a/yazi-config/src/plugin/plugin.rs b/yazi-config/src/plugin/plugin.rs index 902244b44..9e51fa0ec 100644 --- a/yazi-config/src/plugin/plugin.rs +++ b/yazi-config/src/plugin/plugin.rs @@ -25,7 +25,7 @@ impl Plugin { .fetchers .iter() .filter(|&p| { - p.cond.as_ref().and_then(|c| c.eval(f)) != Some(false) + p.if_.as_ref().and_then(|c| c.eval(f)) != Some(false) && (p.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m)) || p.name.as_ref().is_some_and(|p| p.match_path(path, is_dir))) }) diff --git a/yazi-config/src/theme/icons.rs b/yazi-config/src/theme/icons.rs index 4c2a63fe1..04c1e81b6 100644 --- a/yazi-config/src/theme/icons.rs +++ b/yazi-config/src/theme/icons.rs @@ -16,11 +16,11 @@ pub struct Icons { impl Icons { pub fn matches(&self, file: &File) -> Option<&Icon> { - if let Some((_, i)) = self.globs.iter().find(|(p, _)| p.match_path(&file.url, file.is_dir())) { + if let Some(i) = self.match_by_glob(file) { return Some(i); } - if let Some(i) = self.match_name(file) { + if let Some(i) = self.match_by_name(file) { return Some(i); } @@ -41,7 +41,12 @@ impl Icons { } #[inline] - fn match_name(&self, file: &File) -> Option<&Icon> { + fn match_by_glob(&self, file: &File) -> Option<&Icon> { + self.globs.iter().find(|(p, _)| p.match_path(&file.url, file.is_dir())).map(|(_, i)| i) + } + + #[inline] + fn match_by_name(&self, file: &File) -> Option<&Icon> { let name = file.name()?.to_str()?; if file.is_dir() { self.dirs.get(name).or_else(|| self.dirs.get(&name.to_ascii_lowercase())) @@ -110,7 +115,8 @@ impl<'de> Deserialize<'de> for Icons { } #[derive(Deserialize)] pub struct ShadowCond { - cond: Condition, + #[serde(rename = "if")] + if_: Condition, text: String, fg_dark: Option, #[allow(dead_code)] @@ -136,7 +142,7 @@ impl<'de> Deserialize<'de> for Icons { .conds .into_iter() .map(|v| { - (v.cond, Icon { text: v.text, style: Style { fg: v.fg_dark, ..Default::default() } }) + (v.if_, Icon { text: v.text, style: Style { fg: v.fg_dark, ..Default::default() } }) }) .collect(); diff --git a/yazi-core/Cargo.toml b/yazi-core/Cargo.toml index 50442c8b3..f35bdc6ae 100644 --- a/yazi-core/Cargo.toml +++ b/yazi-core/Cargo.toml @@ -26,7 +26,7 @@ dirs = "5.0.1" futures = "0.3.30" notify = { version = "6.1.1", default-features = false, features = [ "macos_fsevent" ] } parking_lot = "0.12.3" -ratatui = "0.26.3" +ratatui = "0.27.0" regex = "1.10.5" scopeguard = "1.2.0" serde = "1.0.203" diff --git a/yazi-core/src/manager/commands/open.rs b/yazi-core/src/manager/commands/open.rs index c1a80d593..1b36edc1a 100644 --- a/yazi-core/src/manager/commands/open.rs +++ b/yazi-core/src/manager/commands/open.rs @@ -64,7 +64,7 @@ impl Manager { done.extend(files.iter().map(|f| (f.url(), String::new()))); if let Err(e) = isolate::fetch("mime", files).await { - error!("fetch `mime` failed in opening: {e}"); + error!("Fetch `mime` failed in opening: {e}"); } ManagerProxy::open_do(OpenDoOpt { hovered, targets: done, interactive: opt.interactive }); diff --git a/yazi-core/src/manager/watcher.rs b/yazi-core/src/manager/watcher.rs index 1ac30c321..53449f3a7 100644 --- a/yazi-core/src/manager/watcher.rs +++ b/yazi-core/src/manager/watcher.rs @@ -130,7 +130,7 @@ impl Watcher { continue; } if let Err(e) = isolate::fetch("mime", reload).await { - error!("fetch `mime` failed in watcher: {e}"); + error!("Fetch `mime` failed in watcher: {e}"); } } } diff --git a/yazi-fm/Cargo.toml b/yazi-fm/Cargo.toml index c06cb4ba7..f243a0ba4 100644 --- a/yazi-fm/Cargo.toml +++ b/yazi-fm/Cargo.toml @@ -29,7 +29,7 @@ crossterm = { version = "0.27.0", features = [ "event-stream" ] } fdlimit = "0.3.0" futures = "0.3.30" mlua = { version = "0.9.9", features = [ "lua54" ] } -ratatui = "0.26.3" +ratatui = "0.27.0" scopeguard = "1.2.0" syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] } tokio = { version = "1.38.0", features = [ "full" ] } diff --git a/yazi-fm/src/lives/file.rs b/yazi-fm/src/lives/file.rs index 6fdfdfce8..bb8e28c54 100644 --- a/yazi-fm/src/lives/file.rs +++ b/yazi-fm/src/lives/file.rs @@ -2,7 +2,7 @@ use std::ops::Deref; use mlua::{AnyUserData, IntoLua, Lua, UserDataFields, UserDataMethods}; use yazi_config::THEME; -use yazi_plugin::{bindings::{Cast, Cha, Icon, Range}, elements::Style, url::Url}; +use yazi_plugin::{bindings::Range, elements::Style}; use yazi_shared::MIME_DIR; use super::{CtxRef, SCOPE}; @@ -19,6 +19,10 @@ impl Deref for File { fn deref(&self) -> &Self::Target { &self.folder().files[self.idx] } } +impl AsRef for File { + fn as_ref(&self) -> &yazi_shared::fs::File { self } +} + impl File { #[inline] pub(super) fn make( @@ -31,16 +35,9 @@ impl File { pub(super) fn register(lua: &Lua) -> mlua::Result<()> { lua.register_userdata_type::(|reg| { - reg.add_field_method_get("idx", |_, me| Ok(me.idx + 1)); - reg.add_field_method_get("url", |lua, me| Url::cast(lua, me.url.clone())); - reg.add_field_method_get("cha", |lua, me| Cha::cast(lua, me.cha)); - reg.add_field_method_get("link_to", |lua, me| { - me.link_to.as_ref().cloned().map(|u| Url::cast(lua, u)).transpose() - }); + yazi_plugin::file::File::register_with(reg); - reg.add_field_method_get("name", |lua, me| { - me.url.file_name().map(|n| lua.create_string(n.as_encoded_bytes())).transpose() - }); + reg.add_field_method_get("idx", |_, me| Ok(me.idx + 1)); reg.add_method("size", |_, me, ()| { Ok(if me.is_dir() { me.folder().files.sizes.get(&me.url).copied() } else { Some(me.len) }) }); @@ -57,19 +54,6 @@ impl File { p.next_back(); Some(lua.create_string(p.as_path().as_os_str().as_encoded_bytes())).transpose() }); - reg.add_method("icon", |lua, me, ()| { - use yazi_shared::theme::IconCache; - - match me.icon.get() { - IconCache::Missing => { - let matched = THEME.icons.matches(me); - me.icon.set(matched.map_or(IconCache::Undefined, IconCache::Icon)); - matched.map(|i| Icon::cast(lua, i)).transpose() - } - IconCache::Undefined => Ok(None), - IconCache::Icon(cached) => Some(Icon::cast(lua, cached)).transpose(), - } - }); reg.add_method("style", |lua, me, ()| { let cx = lua.named_registry_value::("cx")?; let mime = diff --git a/yazi-plugin/Cargo.toml b/yazi-plugin/Cargo.toml index 6190239aa..f8d966934 100644 --- a/yazi-plugin/Cargo.toml +++ b/yazi-plugin/Cargo.toml @@ -29,9 +29,7 @@ futures = "0.3.30" md-5 = "0.10.6" mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] } parking_lot = "0.12.3" -ratatui = "0.26.3" -serde = "1.0.203" -serde_json = "1.0.117" +ratatui = "0.27.0" shell-escape = "0.1.5" shell-words = "1.1.0" syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] } diff --git a/yazi-plugin/preset/plugins/archive.lua b/yazi-plugin/preset/plugins/archive.lua index e53a7925e..b0d09dceb 100644 --- a/yazi-plugin/preset/plugins/archive.lua +++ b/yazi-plugin/preset/plugins/archive.lua @@ -1,9 +1,64 @@ local M = {} function M:peek() - local _, bound = ya.preview_archive(self) - if bound then - ya.manager_emit("peek", { bound, only_if = self.file.url, upper_bound = true }) + local child + if ya.target_os() == "macos" then + child = self:try_spawn("7zz") or self:try_spawn("7z") + else + child = self:try_spawn("7z") or self:try_spawn("7zz") + end + + if not child then + return ya.err("spawn `7z` and `7zz` both commands failed, error code: " .. tostring(self.last_error)) + end + + local limit = self.area.h + local i, icon, names, sizes = 0, nil, {}, {} + repeat + local next, event = child:read_line() + if event ~= 0 then + break + end + + local attr, size, name = next:match("^[-%d]+%s+[:%d]+%s+([.%a]+)%s+(%d+)%s+%d+%s+(.+)[\r\n]+") + if not name then + goto continue + end + + i = i + 1 + if i <= self.skip then + goto continue + end + + icon = File({ + url = Url(name), + cha = Cha { kind = attr:sub(1, 1) == "D" and 1 or 0 }, + }):icon() + + if icon then + names[#names + 1] = ui.Line { ui.Span(" " .. icon.text .. " "):style(icon.style), ui.Span(name) } + else + names[#names + 1] = ui.Line(name) + end + + size = tonumber(size) + if size > 0 then + sizes[#sizes + 1] = ui.Line(string.format(" %s ", ya.readable_size(size))) + else + sizes[#sizes + 1] = ui.Line(" - ") + end + + ::continue:: + until i >= self.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 }) + else + ya.preview_widgets(self, { + ui.Paragraph(self.area, names), + ui.Paragraph(self.area, sizes):align(ui.Paragraph.RIGHT), + }) end end @@ -18,4 +73,12 @@ function M:seek(units) end end +function M:try_spawn(name) + local child, code = Command(name):args({ "l", "-ba", tostring(self.file.url) }):stdout(Command.PIPED):spawn() + if not child then + self.last_error = code + end + return child +end + return M diff --git a/yazi-plugin/src/bindings/file.rs b/yazi-plugin/src/bindings/file.rs deleted file mode 100644 index 9f714658f..000000000 --- a/yazi-plugin/src/bindings/file.rs +++ /dev/null @@ -1,29 +0,0 @@ -use mlua::{AnyUserData, Lua, UserDataFields, UserDataRef}; - -use super::{Cast, Cha}; -use crate::url::Url; - -pub type FileRef<'lua> = UserDataRef<'lua, yazi_shared::fs::File>; - -pub struct File; - -impl File { - pub fn register(lua: &Lua) -> mlua::Result<()> { - lua.register_userdata_type::(|reg| { - reg.add_field_method_get("url", |lua, me| Url::cast(lua, me.url.clone())); - reg.add_field_method_get("cha", |lua, me| Cha::cast(lua, me.cha)); - reg.add_field_method_get("link_to", |lua, me| { - me.link_to.as_ref().cloned().map(|u| Url::cast(lua, u)).transpose() - }); - - // Extension - reg.add_field_method_get("name", |lua, me| { - me.url.file_name().map(|n| lua.create_string(n.as_encoded_bytes())).transpose() - }); - }) - } -} - -impl> Cast for File { - fn cast(lua: &Lua, data: T) -> mlua::Result { lua.create_any_userdata(data.into()) } -} diff --git a/yazi-plugin/src/bindings/mod.rs b/yazi-plugin/src/bindings/mod.rs index 6a7f716e2..a0b7870e2 100644 --- a/yazi-plugin/src/bindings/mod.rs +++ b/yazi-plugin/src/bindings/mod.rs @@ -1,8 +1,6 @@ #![allow(clippy::module_inception)] mod bindings; -mod cha; -mod file; mod icon; mod input; mod mouse; @@ -12,8 +10,6 @@ mod range; mod window; pub use bindings::*; -pub use cha::*; -pub use file::*; pub use icon::*; pub use input::*; pub use mouse::*; diff --git a/yazi-plugin/src/bindings/cha.rs b/yazi-plugin/src/cha/cha.rs similarity index 58% rename from yazi-plugin/src/bindings/cha.rs rename to yazi-plugin/src/cha/cha.rs index d2228fef2..af3e48130 100644 --- a/yazi-plugin/src/bindings/cha.rs +++ b/yazi-plugin/src/cha/cha.rs @@ -1,8 +1,11 @@ -use std::time::UNIX_EPOCH; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use mlua::{AnyUserData, Lua, UserDataFields, UserDataMethods}; +use mlua::{AnyUserData, ExternalError, Lua, Table, UserDataFields, UserDataMethods, UserDataRef}; +use yazi_shared::fs::ChaKind; -use super::Cast; +use crate::bindings::Cast; + +pub type UrlRef<'lua> = UserDataRef<'lua, yazi_shared::fs::Cha>; pub struct Cha; @@ -48,6 +51,39 @@ impl Cha { Ok(()) } + + pub fn install(lua: &Lua) -> mlua::Result<()> { + #[inline] + fn parse_time(f: Option) -> mlua::Result> { + Ok(match f { + Some(n) if n >= 0.0 => Some(SystemTime::UNIX_EPOCH + Duration::from_secs_f64(n)), + Some(n) => Err(format!("Invalid timestamp: {n}").into_lua_err())?, + None => None, + }) + } + + lua.globals().raw_set( + "Cha", + lua.create_function(|lua, t: Table| { + let kind = + ChaKind::from_bits(t.raw_get("kind")?).ok_or_else(|| "Invalid kind".into_lua_err())?; + + Self::cast(lua, yazi_shared::fs::Cha { + kind, + len: t.raw_get("len").unwrap_or_default(), + accessed: parse_time(t.raw_get("atime").ok())?, + created: parse_time(t.raw_get("ctime").ok())?, + modified: parse_time(t.raw_get("mtime").ok())?, + #[cfg(unix)] + permissions: t.raw_get("permissions").unwrap_or_default(), + #[cfg(unix)] + uid: t.raw_get("uid").unwrap_or_default(), + #[cfg(unix)] + gid: t.raw_get("gid").unwrap_or_default(), + }) + })?, + ) + } } impl> Cast for Cha { diff --git a/yazi-plugin/src/cha/mod.rs b/yazi-plugin/src/cha/mod.rs new file mode 100644 index 000000000..6c9c1cf77 --- /dev/null +++ b/yazi-plugin/src/cha/mod.rs @@ -0,0 +1,12 @@ +#![allow(clippy::module_inception)] + +mod cha; + +pub use cha::*; + +pub fn pour(lua: &mlua::Lua) -> mlua::Result<()> { + cha::Cha::register(lua)?; + cha::Cha::install(lua)?; + + Ok(()) +} diff --git a/yazi-plugin/src/external/lsar.rs b/yazi-plugin/src/external/lsar.rs deleted file mode 100644 index d48a0f88b..000000000 --- a/yazi-plugin/src/external/lsar.rs +++ /dev/null @@ -1,79 +0,0 @@ -use std::path::Path; - -use anyhow::anyhow; -use serde::Deserialize; -use serde_json::Value; -use tokio::process::Command; -use yazi_shared::PeekError; - -#[derive(Debug)] -pub enum LsarAttr { - Posix(u16), - Windows(u16), - Dos(u8), -} - -#[derive(Debug, Deserialize)] -pub struct LsarFile { - #[serde(rename = "XADFileName")] - pub name: String, - #[serde(rename = "XADLastModificationDate")] - pub last_modified: String, - #[serde(rename = "XADFileSize")] - pub size: Option, - #[serde(rename = "XADCompressedSize")] - pub compressed_size: Option, - #[serde(rename = "XADCompressionName")] - pub compression_name: Option, - - #[serde(skip)] - pub attributes: Option, -} - -#[allow(clippy::manual_map)] -pub async fn lsar(path: &Path, skip: usize, limit: usize) -> Result, PeekError> { - let output = Command::new("lsar").arg("-j").arg(path).kill_on_drop(true).output().await?; - if !output.status.success() { - return Err(String::from_utf8_lossy(&output.stderr).to_string().into()); - } - - #[derive(Deserialize)] - struct Outer { - #[serde(rename = "lsarContents")] - contents: Vec, - } - - let output = String::from_utf8_lossy(&output.stdout); - let contents = serde_json::from_str::(output.trim()).map_err(|e| anyhow!(e))?.contents; - - let mut i = 0; - let mut files = Vec::with_capacity(limit); - for content in contents { - i += 1; - if i > skip + limit { - break; - } else if i <= skip { - continue; - } - - let attributes = if let Some(p) = content.get("XADPosixPermissions").and_then(|p| p.as_u64()) { - Some(LsarAttr::Posix(p as u16)) - } else if let Some(a) = content.get("XADWindowsFileAttributes").and_then(|a| a.as_u64()) { - Some(LsarAttr::Windows(a as u16)) - } else if let Some(a) = content.get("XADDOSFileAttributes").and_then(|a| a.as_u64()) { - Some(LsarAttr::Dos(a as u8)) - } else { - None - }; - - let mut file = serde_json::from_value::(content).map_err(|e| anyhow!(e))?; - file.attributes = attributes; - files.push(file); - } - - if skip > 0 && files.len() < limit { - Err(PeekError::Exceed(i.saturating_sub(limit))) - } else { - Ok(files) - } -} diff --git a/yazi-plugin/src/external/mod.rs b/yazi-plugin/src/external/mod.rs index 04bb43d88..5a7b270ac 100644 --- a/yazi-plugin/src/external/mod.rs +++ b/yazi-plugin/src/external/mod.rs @@ -1,9 +1,7 @@ mod fd; mod highlighter; -mod lsar; mod rg; pub use fd::*; pub use highlighter::*; -pub use lsar::*; pub use rg::*; diff --git a/yazi-plugin/src/file/file.rs b/yazi-plugin/src/file/file.rs new file mode 100644 index 000000000..5b7266ae5 --- /dev/null +++ b/yazi-plugin/src/file/file.rs @@ -0,0 +1,63 @@ +use mlua::{AnyUserData, Lua, Table, UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry}; +use yazi_config::THEME; + +use crate::{bindings::{Cast, Icon}, cha::Cha, url::Url}; + +pub type FileRef<'lua> = UserDataRef<'lua, yazi_shared::fs::File>; + +pub struct File; + +impl File { + #[inline] + pub fn register(lua: &Lua) -> mlua::Result<()> { + lua.register_userdata_type::(Self::register_with) + } + + pub fn register_with(reg: &mut UserDataRegistry) + where + T: AsRef, + { + reg.add_field_method_get("url", |lua, me| Url::cast(lua, me.as_ref().url.clone())); + reg.add_field_method_get("cha", |lua, me| Cha::cast(lua, me.as_ref().cha)); + reg.add_field_method_get("link_to", |lua, me| { + me.as_ref().link_to.clone().map(|u| Url::cast(lua, u)).transpose() + }); + + // Extension + reg.add_field_method_get("name", |lua, me| { + me.as_ref().url.file_name().map(|n| lua.create_string(n.as_encoded_bytes())).transpose() + }); + + reg.add_method("icon", |lua, me, ()| { + use yazi_shared::theme::IconCache; + + let me = me.as_ref(); + match me.icon.get() { + IconCache::Missing => { + let matched = THEME.icons.matches(me); + me.icon.set(matched.map_or(IconCache::Undefined, IconCache::Icon)); + matched.map(|i| Icon::cast(lua, i)).transpose() + } + IconCache::Undefined => Ok(None), + IconCache::Icon(cached) => Some(Icon::cast(lua, cached)).transpose(), + } + }); + } + + pub fn install(lua: &Lua) -> mlua::Result<()> { + lua.globals().raw_set( + "File", + lua.create_function(|lua, t: Table| { + Self::cast(lua, yazi_shared::fs::File { + url: t.raw_get::<_, AnyUserData>("url")?.take()?, + cha: t.raw_get::<_, AnyUserData>("cha")?.take()?, + ..Default::default() + }) + })?, + ) + } +} + +impl> Cast for File { + fn cast(lua: &Lua, data: T) -> mlua::Result { lua.create_any_userdata(data.into()) } +} diff --git a/yazi-plugin/src/file/mod.rs b/yazi-plugin/src/file/mod.rs new file mode 100644 index 000000000..ac2d9fb60 --- /dev/null +++ b/yazi-plugin/src/file/mod.rs @@ -0,0 +1,12 @@ +#![allow(clippy::module_inception)] + +mod file; + +pub use file::*; + +pub fn pour(lua: &mlua::Lua) -> mlua::Result<()> { + file::File::register(lua)?; + file::File::install(lua)?; + + Ok(()) +} diff --git a/yazi-plugin/src/fs/fs.rs b/yazi-plugin/src/fs/fs.rs index aa3ecf9e1..158b931c7 100644 --- a/yazi-plugin/src/fs/fs.rs +++ b/yazi-plugin/src/fs/fs.rs @@ -1,7 +1,7 @@ use mlua::{IntoLuaMulti, Lua, Value}; use tokio::fs; -use crate::{bindings::{Cast, Cha}, url::UrlRef}; +use crate::{bindings::Cast, cha::Cha, url::UrlRef}; pub fn install(lua: &Lua) -> mlua::Result<()> { lua.globals().raw_set( diff --git a/yazi-plugin/src/isolate/fetch.rs b/yazi-plugin/src/isolate/fetch.rs index 1e23ab961..bee5c63cd 100644 --- a/yazi-plugin/src/isolate/fetch.rs +++ b/yazi-plugin/src/isolate/fetch.rs @@ -3,7 +3,7 @@ use tokio::runtime::Handle; use yazi_config::LAYOUT; use super::slim_lua; -use crate::{bindings::{Cast, File}, elements::Rect, loader::LOADER}; +use crate::{bindings::Cast, elements::Rect, file::File, loader::LOADER}; pub async fn fetch(name: &str, files: Vec) -> mlua::Result { LOADER.ensure(name).await.into_lua_err()?; diff --git a/yazi-plugin/src/isolate/isolate.rs b/yazi-plugin/src/isolate/isolate.rs index 933cff574..d82719ffe 100644 --- a/yazi-plugin/src/isolate/isolate.rs +++ b/yazi-plugin/src/isolate/isolate.rs @@ -1,14 +1,15 @@ use mlua::Lua; -use crate::{bindings, elements, runtime::Runtime}; +use crate::{elements, runtime::Runtime}; pub fn slim_lua(name: &str) -> mlua::Result { let lua = Lua::new(); lua.set_named_registry_value("rt", Runtime::new(name))?; // Base - bindings::Cha::register(&lua)?; - bindings::File::register(&lua)?; + crate::bindings::Icon::register(&lua)?; + crate::cha::pour(&lua)?; + crate::file::pour(&lua)?; crate::url::pour(&lua)?; crate::fs::install(&lua)?; diff --git a/yazi-plugin/src/isolate/peek.rs b/yazi-plugin/src/isolate/peek.rs index 886caf941..019b7cfad 100644 --- a/yazi-plugin/src/isolate/peek.rs +++ b/yazi-plugin/src/isolate/peek.rs @@ -6,7 +6,7 @@ use yazi_config::LAYOUT; use yazi_shared::{emit, event::Cmd, Layer}; use super::slim_lua; -use crate::{bindings::{Cast, File, Window}, elements::Rect, loader::LOADER, Opt, OptCallback, LUA}; +use crate::{bindings::{Cast, Window}, elements::Rect, file::File, loader::LOADER, Opt, OptCallback, LUA}; pub fn peek(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) -> CancellationToken { let ct = CancellationToken::new(); diff --git a/yazi-plugin/src/isolate/preload.rs b/yazi-plugin/src/isolate/preload.rs index 55cbcd22c..928d55d72 100644 --- a/yazi-plugin/src/isolate/preload.rs +++ b/yazi-plugin/src/isolate/preload.rs @@ -3,7 +3,7 @@ use tokio::runtime::Handle; use yazi_config::LAYOUT; use super::slim_lua; -use crate::{bindings::{Cast, File}, elements::Rect, loader::LOADER}; +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()?; diff --git a/yazi-plugin/src/isolate/seek.rs b/yazi-plugin/src/isolate/seek.rs index 74af4f86b..07e86926c 100644 --- a/yazi-plugin/src/isolate/seek.rs +++ b/yazi-plugin/src/isolate/seek.rs @@ -2,7 +2,7 @@ use mlua::TableExt; use yazi_config::LAYOUT; use yazi_shared::{emit, event::Cmd, Layer}; -use crate::{bindings::{Cast, File}, elements::Rect, Opt, OptCallback, LUA}; +use crate::{bindings::Cast, elements::Rect, file::File, Opt, OptCallback, LUA}; pub fn seek_sync(cmd: &Cmd, file: yazi_shared::fs::File, units: i16) { let cb: OptCallback = Box::new(move |_, plugin| { diff --git a/yazi-plugin/src/lib.rs b/yazi-plugin/src/lib.rs index cc74d28c6..e4fbd3ef6 100644 --- a/yazi-plugin/src/lib.rs +++ b/yazi-plugin/src/lib.rs @@ -2,10 +2,12 @@ pub mod bindings; mod cast; +pub mod cha; mod clipboard; mod config; pub mod elements; pub mod external; +pub mod file; pub mod fs; pub mod isolate; pub mod loader; diff --git a/yazi-plugin/src/lua.rs b/yazi-plugin/src/lua.rs index 1b3c5a159..fd7f10def 100644 --- a/yazi-plugin/src/lua.rs +++ b/yazi-plugin/src/lua.rs @@ -21,13 +21,13 @@ fn stage_1(lua: &'static Lua) -> Result<()> { // Base lua.set_named_registry_value("rt", Runtime::default())?; lua.load(include_str!("../preset/ya.lua")).exec()?; - crate::bindings::Cha::register(lua)?; - crate::bindings::File::register(lua)?; crate::bindings::Icon::register(lua)?; crate::bindings::MouseEvent::register(lua)?; crate::elements::pour(lua)?; crate::loader::install(lua)?; crate::pubsub::install(lua)?; + crate::cha::pour(lua)?; + crate::file::pour(lua)?; crate::url::pour(lua)?; // Components diff --git a/yazi-plugin/src/utils/cache.rs b/yazi-plugin/src/utils/cache.rs index b0f9a31aa..a090436b9 100644 --- a/yazi-plugin/src/utils/cache.rs +++ b/yazi-plugin/src/utils/cache.rs @@ -3,7 +3,7 @@ use mlua::{Lua, Table}; use yazi_config::PREVIEW; use super::Utils; -use crate::{bindings::{Cast, FileRef}, url::Url}; +use crate::{bindings::Cast, file::FileRef, url::Url}; impl Utils { pub(super) fn cache(lua: &Lua, ya: &Table) -> mlua::Result<()> { diff --git a/yazi-plugin/src/utils/preview.rs b/yazi-plugin/src/utils/preview.rs index 73a6aed99..231e90197 100644 --- a/yazi-plugin/src/utils/preview.rs +++ b/yazi-plugin/src/utils/preview.rs @@ -2,7 +2,7 @@ use mlua::{AnyUserData, IntoLuaMulti, Lua, Table, Value}; use yazi_shared::{emit, event::Cmd, Layer, PeekError}; use super::Utils; -use crate::{bindings::{FileRef, Window}, cast_to_renderable, elements::{Paragraph, RectRef, Renderable}, external::{self, Highlighter}}; +use crate::{bindings::Window, cast_to_renderable, elements::{Paragraph, RectRef, Renderable}, external::Highlighter, file::FileRef}; pub struct PreviewLock { pub url: yazi_shared::fs::Url, @@ -49,29 +49,6 @@ impl Utils { })?, )?; - ya.raw_set( - "preview_archive", - lua.create_async_function(|lua, t: Table| async move { - let area: RectRef = t.raw_get("area")?; - let mut lock = PreviewLock::try_from(t)?; - - let lines: Vec<_> = match external::lsar(&lock.url, lock.skip, area.height as usize).await { - Ok(items) => items.into_iter().map(|f| ratatui::text::Line::from(f.name)).collect(), - Err(PeekError::Exceed(max)) => return (false, max).into_lua_multi(lua), - Err(_) => return (false, Value::Nil).into_lua_multi(lua), - }; - - lock.data = vec![Box::new(Paragraph { - area: *area, - text: ratatui::text::Text::from(lines), - ..Default::default() - })]; - - emit!(Call(Cmd::new("preview").with_any("lock", lock), Layer::Manager)); - (true, Value::Nil).into_lua_multi(lua) - })?, - )?; - ya.raw_set( "preview_widgets", lua.create_async_function(|_, (t, widgets): (Table, Vec)| async move { diff --git a/yazi-plugin/src/utils/target.rs b/yazi-plugin/src/utils/target.rs index 36f5d0297..2663c806b 100644 --- a/yazi-plugin/src/utils/target.rs +++ b/yazi-plugin/src/utils/target.rs @@ -4,23 +4,8 @@ use super::Utils; impl Utils { pub(super) fn target(lua: &Lua, ya: &Table) -> mlua::Result<()> { - ya.raw_set( - "target_family", - lua.create_function(|_, ()| { - #[cfg(unix)] - { - Ok("unix") - } - #[cfg(windows)] - { - Ok("windows") - } - #[cfg(target_family = "wasm")] - { - Ok("wasm") - } - })?, - )?; + ya.raw_set("target_os", lua.create_function(|_, ()| Ok(std::env::consts::OS))?)?; + ya.raw_set("target_family", lua.create_function(|_, ()| Ok(std::env::consts::FAMILY))?)?; Ok(()) } diff --git a/yazi-scheduler/src/preload/prework.rs b/yazi-scheduler/src/preload/prework.rs index 91387f09c..ae0dc3c6d 100644 --- a/yazi-scheduler/src/preload/prework.rs +++ b/yazi-scheduler/src/preload/prework.rs @@ -33,13 +33,24 @@ impl Prework { let urls: Vec<_> = task.targets.iter().map(|f| f.url()).collect(); let result = isolate::fetch(&task.plugin.name, task.targets).await; if let Err(e) = result { - self.fail(task.id, format!("Fetch task failed:\n{e}"))?; + self.fail( + task.id, + format!( + "Failed to run fetcher `{}` with:\n{}\n\nError message:\n{e}", + task.plugin.name, + urls.iter().map(ToString::to_string).collect::>().join("\n") + ), + )?; return Err(e.into()); }; let code = result.unwrap(); if code & 1 == 0 { - error!("Fetch task `{}` returned {code}", task.plugin.name); + error!( + "Returned {code} when running fetcher `{}` with:\n{}", + task.plugin.name, + urls.iter().map(ToString::to_string).collect::>().join("\n") + ); } if code >> 1 & 1 != 0 { let mut loaded = self.loaded.lock(); @@ -53,13 +64,16 @@ impl Prework { let url = task.target.url(); let result = isolate::preload(&task.plugin.name, task.target).await; if let Err(e) = result { - self.fail(task.id, format!("Preload task failed:\n{e}"))?; + self.fail( + task.id, + format!("Failed to run preloader `{}` with `{url}`:\n{e}", task.plugin.name), + )?; return Err(e.into()); }; let code = result.unwrap(); if code & 1 == 0 { - error!("Preload task `{}` returned {code}", task.plugin.name); + error!("Returned {code} when running preloader `{}` with `{url}`", task.plugin.name); } if code >> 1 & 1 != 0 { let mut loaded = self.loaded.lock(); diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index bb9afa549..f43ea71e6 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -17,7 +17,7 @@ dirs = "5.0.1" futures = "0.3.30" parking_lot = "0.12.3" percent-encoding = "2.3.1" -ratatui = "0.26.3" +ratatui = "0.27.0" regex = "1.10.5" serde = { version = "1.0.203", features = [ "derive" ] } shell-words = "1.1.0" diff --git a/yazi-shared/src/fs/cha.rs b/yazi-shared/src/fs/cha.rs index 55225e304..4e364afb5 100644 --- a/yazi-shared/src/fs/cha.rs +++ b/yazi-shared/src/fs/cha.rs @@ -5,16 +5,16 @@ use bitflags::bitflags; bitflags! { #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct ChaKind: u8 { - const DIR = 0b00000001; + const DIR = 0b00000001; - const HIDDEN = 0b00000010; - const LINK = 0b00000100; - const ORPHAN = 0b00001000; + const HIDDEN = 0b00000010; + const LINK = 0b00000100; + const ORPHAN = 0b00001000; - const BLOCK_DEVICE = 0b00010000; - const CHAR_DEVICE = 0b00100000; - const FIFO = 0b01000000; - const SOCKET = 0b10000000; + const BLOCK = 0b00010000; + const CHAR = 0b00100000; + const FIFO = 0b01000000; + const SOCKET = 0b10000000; } } @@ -44,10 +44,10 @@ impl From for Cha { { use std::os::unix::prelude::FileTypeExt; if m.file_type().is_block_device() { - ck |= ChaKind::BLOCK_DEVICE; + ck |= ChaKind::BLOCK; } if m.file_type().is_char_device() { - ck |= ChaKind::CHAR_DEVICE; + ck |= ChaKind::CHAR; } if m.file_type().is_fifo() { ck |= ChaKind::FIFO; @@ -105,10 +105,10 @@ impl Cha { pub fn is_orphan(&self) -> bool { self.kind.contains(ChaKind::ORPHAN) } #[inline] - pub fn is_block(&self) -> bool { self.kind.contains(ChaKind::BLOCK_DEVICE) } + pub fn is_block(&self) -> bool { self.kind.contains(ChaKind::BLOCK) } #[inline] - pub fn is_char(&self) -> bool { self.kind.contains(ChaKind::CHAR_DEVICE) } + pub fn is_char(&self) -> bool { self.kind.contains(ChaKind::CHAR) } #[inline] pub fn is_fifo(&self) -> bool { self.kind.contains(ChaKind::FIFO) } diff --git a/yazi-shared/src/fs/file.rs b/yazi-shared/src/fs/file.rs index 90841bf5b..eb3eebf57 100644 --- a/yazi-shared/src/fs/file.rs +++ b/yazi-shared/src/fs/file.rs @@ -20,6 +20,11 @@ impl Deref for File { fn deref(&self) -> &Self::Target { &self.cha } } +impl AsRef for File { + #[inline] + fn as_ref(&self) -> &File { self } +} + impl File { #[inline] pub async fn from(url: Url) -> Result {