perf!: reimplement and significantly speed up archive previewing (#1220)

This commit is contained in:
三咲雅 · Misaki Masa 2024-06-27 00:47:56 +08:00 committed by GitHub
parent c64530b35b
commit 626053da25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 299 additions and 246 deletions

22
Cargo.lock generated
View file

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

View file

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

View file

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

View file

@ -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 = "" },
]
# : }}}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

@ -1,9 +1,7 @@
mod fd;
mod highlighter;
mod lsar;
mod rg;
pub use fd::*;
pub use highlighter::*;
pub use lsar::*;
pub use rg::*;

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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