mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
perf!: reimplement and significantly speed up archive previewing (#1220)
This commit is contained in:
parent
c64530b35b
commit
626053da25
38 changed files with 299 additions and 246 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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" ] }
|
||||
|
||||
|
|
|
|||
|
|
@ -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" ] }
|
||||
|
|
|
|||
|
|
@ -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 = "" },
|
||||
]
|
||||
|
||||
# : }}}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ pub struct Fetcher {
|
|||
pub idx: u8,
|
||||
|
||||
pub id: String,
|
||||
pub cond: Option<Condition>,
|
||||
#[serde(rename = "if")]
|
||||
pub if_: Option<Condition>,
|
||||
pub name: Option<Pattern>,
|
||||
pub mime: Option<Pattern>,
|
||||
pub run: Cmd,
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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<Color>,
|
||||
#[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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" ] }
|
||||
|
|
|
|||
|
|
@ -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<yazi_shared::fs::File> 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::<Self>(|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::<CtxRef>("cx")?;
|
||||
let mime =
|
||||
|
|
|
|||
|
|
@ -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" ] }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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::<yazi_shared::fs::File>(|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<T: Into<yazi_shared::fs::File>> Cast<T> for File {
|
||||
fn cast(lua: &Lua, data: T) -> mlua::Result<AnyUserData> { lua.create_any_userdata(data.into()) }
|
||||
}
|
||||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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<f64>) -> mlua::Result<Option<SystemTime>> {
|
||||
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<T: Into<yazi_shared::fs::Cha>> Cast<T> for Cha {
|
||||
12
yazi-plugin/src/cha/mod.rs
Normal file
12
yazi-plugin/src/cha/mod.rs
Normal file
|
|
@ -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(())
|
||||
}
|
||||
79
yazi-plugin/src/external/lsar.rs
vendored
79
yazi-plugin/src/external/lsar.rs
vendored
|
|
@ -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<usize>,
|
||||
#[serde(rename = "XADCompressedSize")]
|
||||
pub compressed_size: Option<usize>,
|
||||
#[serde(rename = "XADCompressionName")]
|
||||
pub compression_name: Option<String>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub attributes: Option<LsarAttr>,
|
||||
}
|
||||
|
||||
#[allow(clippy::manual_map)]
|
||||
pub async fn lsar(path: &Path, skip: usize, limit: usize) -> Result<Vec<LsarFile>, 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<Value>,
|
||||
}
|
||||
|
||||
let output = String::from_utf8_lossy(&output.stdout);
|
||||
let contents = serde_json::from_str::<Outer>(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::<LsarFile>(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)
|
||||
}
|
||||
}
|
||||
2
yazi-plugin/src/external/mod.rs
vendored
2
yazi-plugin/src/external/mod.rs
vendored
|
|
@ -1,9 +1,7 @@
|
|||
mod fd;
|
||||
mod highlighter;
|
||||
mod lsar;
|
||||
mod rg;
|
||||
|
||||
pub use fd::*;
|
||||
pub use highlighter::*;
|
||||
pub use lsar::*;
|
||||
pub use rg::*;
|
||||
|
|
|
|||
63
yazi-plugin/src/file/file.rs
Normal file
63
yazi-plugin/src/file/file.rs
Normal file
|
|
@ -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::<yazi_shared::fs::File>(Self::register_with)
|
||||
}
|
||||
|
||||
pub fn register_with<T>(reg: &mut UserDataRegistry<T>)
|
||||
where
|
||||
T: AsRef<yazi_shared::fs::File>,
|
||||
{
|
||||
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<T: Into<yazi_shared::fs::File>> Cast<T> for File {
|
||||
fn cast(lua: &Lua, data: T) -> mlua::Result<AnyUserData> { lua.create_any_userdata(data.into()) }
|
||||
}
|
||||
12
yazi-plugin/src/file/mod.rs
Normal file
12
yazi-plugin/src/file/mod.rs
Normal file
|
|
@ -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(())
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<yazi_shared::fs::File>) -> mlua::Result<u8> {
|
||||
LOADER.ensure(name).await.into_lua_err()?;
|
||||
|
|
|
|||
|
|
@ -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<Lua> {
|
||||
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)?;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<u8> {
|
||||
LOADER.ensure(name).await.into_lua_err()?;
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<()> {
|
||||
|
|
|
|||
|
|
@ -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<AnyUserData>)| async move {
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::<Vec<_>>().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::<Vec<_>>().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();
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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<Metadata> 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) }
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ impl Deref for File {
|
|||
fn deref(&self) -> &Self::Target { &self.cha }
|
||||
}
|
||||
|
||||
impl AsRef<File> for File {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &File { self }
|
||||
}
|
||||
|
||||
impl File {
|
||||
#[inline]
|
||||
pub async fn from(url: Url) -> Result<Self> {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue