From b910b5f0ab51d9fa0b9ef458f8832fcebf3395bd Mon Sep 17 00:00:00 2001 From: sxyazi Date: Thu, 21 Dec 2023 00:40:32 +0800 Subject: [PATCH] .. --- Cargo.lock | 2 + yazi-adaptor/Cargo.toml | 1 + yazi-adaptor/src/adaptor.rs | 45 ++++++---- yazi-adaptor/src/iterm2.rs | 6 +- yazi-adaptor/src/kitty.rs | 6 +- yazi-adaptor/src/kitty_old.rs | 6 +- yazi-adaptor/src/lib.rs | 5 ++ yazi-adaptor/src/sixel.rs | 6 +- yazi-adaptor/src/ueberzug.rs | 2 +- yazi-core/src/clipboard.rs | 11 ++- yazi-core/src/tab/preview.rs | 7 +- yazi-fm/src/app/app.rs | 14 ++- yazi-fm/src/app/commands/plugin.rs | 4 + yazi-fm/src/components/preview.rs | 6 +- yazi-fm/src/lives/active.rs | 4 +- yazi-fm/src/lives/folder.rs | 2 +- yazi-fm/src/root.rs | 2 +- yazi-fm/src/widgets/clear.rs | 2 +- yazi-plugin/Cargo.toml | 1 + yazi-plugin/preset/plugins/archive.lua | 11 ++- yazi-plugin/preset/plugins/code.lua | 11 ++- yazi-plugin/preset/plugins/folder.lua | 19 +++- yazi-plugin/preset/plugins/image.lua | 2 +- yazi-plugin/preset/plugins/json.lua | 9 +- yazi-plugin/preset/plugins/noop.lua | 9 ++ yazi-plugin/preset/plugins/pdf.lua | 6 +- yazi-plugin/preset/plugins/video.lua | 9 +- yazi-plugin/src/bindings/mod.rs | 2 + yazi-plugin/src/bindings/window.rs | 26 ++++++ yazi-plugin/src/isolate/peek.rs | 4 +- yazi-plugin/src/isolate/preload.rs | 7 +- yazi-plugin/src/loader.rs | 2 +- yazi-plugin/src/utils/call.rs | 2 +- yazi-plugin/src/utils/preview.rs | 118 +++++++++++++------------ yazi-scheduler/src/scheduler.rs | 4 +- yazi-scheduler/src/workers/preload.rs | 11 +-- 36 files changed, 239 insertions(+), 145 deletions(-) create mode 100644 yazi-plugin/preset/plugins/noop.lua create mode 100644 yazi-plugin/src/bindings/window.rs diff --git a/Cargo.lock b/Cargo.lock index 434126b9..2a5739cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2614,6 +2614,7 @@ name = "yazi-adaptor" version = "0.1.5" dependencies = [ "anyhow", + "arc-swap", "base64", "color_quant", "image", @@ -2713,6 +2714,7 @@ version = "0.1.5" dependencies = [ "ansi-to-tui", "anyhow", + "crossterm", "futures", "libc", "md-5", diff --git a/yazi-adaptor/Cargo.toml b/yazi-adaptor/Cargo.toml index 9ff5bc47..687d7b3a 100644 --- a/yazi-adaptor/Cargo.toml +++ b/yazi-adaptor/Cargo.toml @@ -14,6 +14,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.1.5" } # External dependencies anyhow = "^1" +arc-swap = "^1" base64 = "^0" color_quant = "^1" image = "^0" diff --git a/yazi-adaptor/src/adaptor.rs b/yazi-adaptor/src/adaptor.rs index 2b9f0a66..a521ff0a 100644 --- a/yazi-adaptor/src/adaptor.rs +++ b/yazi-adaptor/src/adaptor.rs @@ -1,14 +1,12 @@ -use std::{env, path::Path, sync::atomic::{AtomicBool, Ordering}}; +use std::{env, path::Path, sync::Arc}; use anyhow::{anyhow, Result}; use ratatui::prelude::Rect; use tracing::warn; -use yazi_shared::env_exists; +use yazi_shared::{env_exists, term::Term}; use super::{Iterm2, Kitty, KittyOld}; -use crate::{ueberzug::Ueberzug, Sixel, TMUX}; - -static IMAGE_SHOWN: AtomicBool = AtomicBool::new(false); +use crate::{ueberzug::Ueberzug, Sixel, SHOWN, TMUX}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Adaptor { @@ -162,29 +160,40 @@ impl Adaptor { pub(super) fn start(self) { Ueberzug::start(self); } pub async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> { - self.image_hide(rect).ok(); - IMAGE_SHOWN.store(true, Ordering::Relaxed); + self.image_hide().ok(); - match self { + let size = match self { Self::Kitty => Kitty::image_show(path, rect).await, Self::KittyOld => KittyOld::image_show(path, rect).await, Self::Iterm2 => Iterm2::image_show(path, rect).await, Self::Sixel => Sixel::image_show(path, rect).await, _ => Ueberzug::image_show(path, rect).await, - } + }?; + + let shown = Term::ratio() + .map(|(r1, r2)| Rect { + x: rect.x, + y: rect.y, + width: (size.0 as f64 / r1).ceil() as u16, + height: (size.1 as f64 / r2).ceil() as u16, + }) + .unwrap_or(rect); + + SHOWN.store(Some(Arc::new(shown))); + Ok(size) } - pub fn image_hide(self, rect: Rect) -> Result<()> { - if !IMAGE_SHOWN.swap(false, Ordering::Relaxed) { - return Ok(()); - } + pub fn image_hide(self) -> Result<()> { + if let Some(rect) = SHOWN.swap(None) { self.image_erase(*rect) } else { Ok(()) } + } + pub fn image_erase(self, rect: Rect) -> Result<()> { match self { - Self::Kitty => Kitty::image_hide(rect), - Self::Iterm2 => Iterm2::image_hide(rect), - Self::KittyOld => KittyOld::image_hide(), - Self::Sixel => Sixel::image_hide(rect), - _ => Ueberzug::image_hide(rect), + Self::Kitty => Kitty::image_erase(rect), + Self::Iterm2 => Iterm2::image_erase(rect), + Self::KittyOld => KittyOld::image_erase(), + Self::Sixel => Sixel::image_erase(rect), + _ => Ueberzug::image_erase(rect), } } diff --git a/yazi-adaptor/src/iterm2.rs b/yazi-adaptor/src/iterm2.rs index bedfda49..4f38e54e 100644 --- a/yazi-adaptor/src/iterm2.rs +++ b/yazi-adaptor/src/iterm2.rs @@ -7,7 +7,7 @@ use ratatui::prelude::Rect; use yazi_shared::term::Term; use super::image::Image; -use crate::{CLOSE, START}; +use crate::{adaptor::Adaptor, CLOSE, START}; pub(super) struct Iterm2; @@ -17,14 +17,14 @@ impl Iterm2 { let size = (img.width(), img.height()); let b = Self::encode(img).await?; - Self::image_hide(rect)?; + Adaptor::Iterm2.image_hide()?; Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| { stdout.write_all(&b)?; Ok(size) }) } - pub(super) fn image_hide(rect: Rect) -> Result<()> { + pub(super) fn image_erase(rect: Rect) -> Result<()> { let stdout = BufWriter::new(stdout().lock()); let s = " ".repeat(rect.width as usize); Term::move_lock(stdout, (0, 0), |stdout| { diff --git a/yazi-adaptor/src/kitty.rs b/yazi-adaptor/src/kitty.rs index eb1b1103..66cb4e4b 100644 --- a/yazi-adaptor/src/kitty.rs +++ b/yazi-adaptor/src/kitty.rs @@ -7,7 +7,7 @@ use ratatui::prelude::Rect; use yazi_shared::term::Term; use super::image::Image; -use crate::{CLOSE, ESCAPE, START}; +use crate::{adaptor::Adaptor, CLOSE, ESCAPE, START}; static DIACRITICS: [char; 297] = [ '\u{0305}', @@ -317,7 +317,7 @@ impl Kitty { let size = (img.width(), img.height()); let b = Self::encode(img).await?; - Self::image_hide(rect)?; + Adaptor::Kitty.image_hide()?; Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| { stdout.write_all(&b)?; @@ -340,7 +340,7 @@ impl Kitty { }) } - pub(super) fn image_hide(rect: Rect) -> Result<()> { + pub(super) fn image_erase(rect: Rect) -> Result<()> { let stdout = BufWriter::new(stdout().lock()); let s = " ".repeat(rect.width as usize); Term::move_lock(stdout, (0, 0), |stdout| { diff --git a/yazi-adaptor/src/kitty_old.rs b/yazi-adaptor/src/kitty_old.rs index 358c477c..e2861c3b 100644 --- a/yazi-adaptor/src/kitty_old.rs +++ b/yazi-adaptor/src/kitty_old.rs @@ -7,7 +7,7 @@ use ratatui::prelude::Rect; use yazi_shared::term::Term; use super::image::Image; -use crate::{CLOSE, ESCAPE, START}; +use crate::{adaptor::Adaptor, CLOSE, ESCAPE, START}; pub(super) struct KittyOld; @@ -17,7 +17,7 @@ impl KittyOld { let size = (img.width(), img.height()); let b = Self::encode(img).await?; - Self::image_hide()?; + Adaptor::KittyOld.image_hide()?; Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| { stdout.write_all(&b)?; Ok(size) @@ -25,7 +25,7 @@ impl KittyOld { } #[inline] - pub(super) fn image_hide() -> Result<()> { + pub(super) fn image_erase() -> Result<()> { let mut stdout = stdout().lock(); stdout.write_all(format!("{}_Gq=1,a=d,d=A{}\\{}", START, ESCAPE, CLOSE).as_bytes())?; stdout.flush()?; diff --git a/yazi-adaptor/src/lib.rs b/yazi-adaptor/src/lib.rs index de261e47..b4d52f4a 100644 --- a/yazi-adaptor/src/lib.rs +++ b/yazi-adaptor/src/lib.rs @@ -25,12 +25,17 @@ static ESCAPE: RoCell<&'static str> = RoCell::new(); static START: RoCell<&'static str> = RoCell::new(); static CLOSE: RoCell<&'static str> = RoCell::new(); +// Image state +static SHOWN: RoCell> = RoCell::new(); + pub fn init() { TMUX.init(env_exists("TMUX")); START.init(if *TMUX { "\x1bPtmux;\x1b\x1b" } else { "\x1b" }); CLOSE.init(if *TMUX { "\x1b\\" } else { "" }); ESCAPE.init(if *TMUX { "\x1b\x1b" } else { "\x1b" }); + SHOWN.with(Default::default); + ADAPTOR.init(Adaptor::detect()); ADAPTOR.start(); diff --git a/yazi-adaptor/src/sixel.rs b/yazi-adaptor/src/sixel.rs index 736eca9c..c14591f1 100644 --- a/yazi-adaptor/src/sixel.rs +++ b/yazi-adaptor/src/sixel.rs @@ -6,7 +6,7 @@ use image::DynamicImage; use ratatui::prelude::Rect; use yazi_shared::term::Term; -use crate::{Image, CLOSE, ESCAPE, START}; +use crate::{adaptor::Adaptor, Image, CLOSE, ESCAPE, START}; pub(super) struct Sixel; @@ -16,14 +16,14 @@ impl Sixel { let size = (img.width(), img.height()); let b = Self::encode(img).await?; - Self::image_hide(rect)?; + Adaptor::Sixel.image_hide()?; Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| { stdout.write_all(&b)?; Ok(size) }) } - pub(super) fn image_hide(rect: Rect) -> Result<()> { + pub(super) fn image_erase(rect: Rect) -> Result<()> { let stdout = BufWriter::new(stdout().lock()); let s = " ".repeat(rect.width as usize); Term::move_lock(stdout, (0, 0), |stdout| { diff --git a/yazi-adaptor/src/ueberzug.rs b/yazi-adaptor/src/ueberzug.rs index 4ddbf472..669b0294 100644 --- a/yazi-adaptor/src/ueberzug.rs +++ b/yazi-adaptor/src/ueberzug.rs @@ -61,7 +61,7 @@ impl Ueberzug { Ok(((w as f64 * ratio).round() as u32, (h as f64 * ratio).round() as u32)) } - pub(super) fn image_hide(_: Rect) -> Result<()> { + pub(super) fn image_erase(_: Rect) -> Result<()> { if let Some(tx) = &*DEMON { Ok(tx.send(None)?) } else { diff --git a/yazi-core/src/clipboard.rs b/yazi-core/src/clipboard.rs index 418d85a3..311a98f0 100644 --- a/yazi-core/src/clipboard.rs +++ b/yazi-core/src/clipboard.rs @@ -1,13 +1,12 @@ -use std::ffi::OsString; +use std::{cell::RefCell, ffi::OsString}; -use parking_lot::Mutex; use yazi_shared::RoCell; pub static CLIPBOARD: RoCell = RoCell::new(); #[derive(Default)] pub struct Clipboard { - content: Mutex, + content: RefCell, } impl Clipboard { @@ -19,7 +18,7 @@ impl Clipboard { use yazi_shared::in_ssh_connection; if in_ssh_connection() { - return self.content.lock().clone(); + return self.content.borrow().clone(); } let all = [ @@ -37,7 +36,7 @@ impl Clipboard { return OsString::from_vec(output.stdout); } } - self.content.lock().clone() + self.content.borrow().clone() } #[cfg(windows)] @@ -60,7 +59,7 @@ impl Clipboard { use tokio::{io::AsyncWriteExt, process::Command}; use yazi_shared::in_ssh_connection; - *self.content.lock() = s.as_ref().to_owned(); + *self.content.borrow_mut() = s.as_ref().to_owned(); if in_ssh_connection() { execute!(stdout(), osc52::SetClipboard::new(s.as_ref())).ok(); } diff --git a/yazi-core/src/tab/preview.rs b/yazi-core/src/tab/preview.rs index 3658be90..534d5ac2 100644 --- a/yazi-core/src/tab/preview.rs +++ b/yazi-core/src/tab/preview.rs @@ -1,6 +1,6 @@ use tokio_util::sync::CancellationToken; use yazi_adaptor::ADAPTOR; -use yazi_config::{LAYOUT, PLUGIN}; +use yazi_config::PLUGIN; use yazi_plugin::{external::Highlighter, utils::PreviewLock}; use yazi_shared::fs::{Cha, File, Url}; @@ -18,11 +18,12 @@ impl Preview { return; } - self.abort(); let Some(previewer) = PLUGIN.previewer(&file.url, &mime) else { + self.reset(); return; }; + self.abort(); if previewer.sync { yazi_plugin::isolate::peek_sync(&previewer.exec, file, self.skip); } else { @@ -39,7 +40,7 @@ impl Preview { #[inline] pub fn reset(&mut self) -> bool { self.abort(); - ADAPTOR.image_hide(LAYOUT.load().preview).ok(); + ADAPTOR.image_hide().ok(); self.lock.take().is_some() } diff --git a/yazi-fm/src/app/app.rs b/yazi-fm/src/app/app.rs index 763e31a2..6e2d7e01 100644 --- a/yazi-fm/src/app/app.rs +++ b/yazi-fm/src/app/app.rs @@ -2,7 +2,7 @@ use std::sync::atomic::Ordering; use anyhow::{Ok, Result}; use crossterm::event::KeyEvent; -use ratatui::{backend::Backend, prelude::Rect}; +use ratatui::backend::Backend; use yazi_config::{keymap::Key, ARGS}; use yazi_core::input::InputMode; use yazi_shared::{emit, event::{Event, Exec}, fs::FilesOp, term::Term, Layer, COLLISION}; @@ -36,7 +36,7 @@ impl App { Event::Key(key) => app.dispatch_key(key), Event::Paste(str) => app.dispatch_paste(str), Event::Render(_) => app.dispatch_render()?, - Event::Resize(cols, rows) => app.dispatch_resize(cols, rows), + Event::Resize(cols, rows) => app.dispatch_resize(cols, rows)?, Event::Call(exec, layer) => app.dispatch_call(exec, layer), event => app.dispatch_module(event), } @@ -112,15 +112,13 @@ impl App { Ok(()) } - fn dispatch_resize(&mut self, cols: u16, rows: u16) { - if let Some(term) = &mut self.term { - term.resize(Rect::new(0, 0, cols, rows)).ok(); - } + fn dispatch_resize(&mut self, _: u16, _: u16) -> Result<()> { + self.cx.manager.active_mut().preview.reset(); + self.dispatch_render()?; self.cx.manager.current_mut().set_page(true); - self.cx.manager.active_mut().preview.reset(); self.cx.manager.peek(()); - emit!(Render); + Ok(()) } #[inline] diff --git a/yazi-fm/src/app/commands/plugin.rs b/yazi-fm/src/app/commands/plugin.rs index 23350a6b..53a171f2 100644 --- a/yazi-fm/src/app/commands/plugin.rs +++ b/yazi-fm/src/app/commands/plugin.rs @@ -15,6 +15,10 @@ impl App { return self.cx.tasks.plugin_micro(&opt.name); } + if LOADED.read().contains_key(&opt.name) { + return self.plugin_do(opt); + } + tokio::spawn(async move { if LOADED.ensure(&opt.name).await.is_ok() { emit!(Call(Exec::call("plugin_do", vec![opt.name]).with_data(opt.data).vec(), Layer::App)); diff --git a/yazi-fm/src/components/preview.rs b/yazi-fm/src/components/preview.rs index 735c6582..5a0067ff 100644 --- a/yazi-fm/src/components/preview.rs +++ b/yazi-fm/src/components/preview.rs @@ -12,12 +12,16 @@ impl<'a> Preview<'a> { } impl Widget for Preview<'_> { - fn render(self, _: ratatui::layout::Rect, buf: &mut Buffer) { + fn render(self, area: ratatui::layout::Rect, buf: &mut Buffer) { let preview = &self.cx.manager.active().preview; let Some(lock) = &preview.lock else { return; }; + if (lock.window.rows, lock.window.cols) != (area.height, area.width) { + return; + } + for w in &lock.data { w.clone_render(buf); } diff --git a/yazi-fm/src/lives/active.rs b/yazi-fm/src/lives/active.rs index c36414b5..0ba37501 100644 --- a/yazi-fm/src/lives/active.rs +++ b/yazi-fm/src/lives/active.rs @@ -60,7 +60,7 @@ impl<'a, 'b> Active<'a, 'b> { fn preview(&self, tab: &'a yazi_core::tab::Tab) -> mlua::Result> { let inner = &tab.preview; - let window = || inner.lock.as_ref().map(|l| (l.skip, LAYOUT.load().preview.height as usize)); + let window = Some((inner.skip, LAYOUT.load().preview.height as usize)); let ud = self.scope.create_any_userdata_ref(inner)?; ud.set_named_user_value( @@ -70,7 +70,7 @@ impl<'a, 'b> Active<'a, 'b> { .hovered() .filter(|&f| f.is_dir()) .and_then(|f| tab.history(&f.url)) - .and_then(|f| Folder::new(self.scope, f).make(window()).ok()), + .and_then(|f| Folder::new(self.scope, f).make(window).ok()), )?; Ok(ud) diff --git a/yazi-fm/src/lives/folder.rs b/yazi-fm/src/lives/folder.rs index bbf7a364..c90d5800 100644 --- a/yazi-fm/src/lives/folder.rs +++ b/yazi-fm/src/lives/folder.rs @@ -166,7 +166,7 @@ impl<'a, 'b> Folder<'a, 'b> { pub(crate) fn make(&self, window: Option<(usize, usize)>) -> mlua::Result> { let window = - window.unwrap_or_else(|| (self.inner.offset, LAYOUT.load().current.height as usize)); + window.unwrap_or_else(|| (self.inner.offset, LAYOUT.load().preview.height as usize)); let ud = self.scope.create_any_userdata_ref(self.inner)?; ud.set_named_user_value( diff --git a/yazi-fm/src/root.rs b/yazi-fm/src/root.rs index e1c34a93..d77ef3ae 100644 --- a/yazi-fm/src/root.rs +++ b/yazi-fm/src/root.rs @@ -21,7 +21,7 @@ impl<'a> Widget for Root<'a> { components::Header.render(chunks[0], buf); components::Manager.render(chunks[1], buf); components::Status.render(chunks[2], buf); - components::Preview::new(self.cx).render(chunks[2], buf); + components::Preview::new(self.cx).render(area, buf); if self.cx.tasks.visible { tasks::Layout::new(self.cx).render(area, buf); diff --git a/yazi-fm/src/widgets/clear.rs b/yazi-fm/src/widgets/clear.rs index aa939e7a..ef68110f 100644 --- a/yazi-fm/src/widgets/clear.rs +++ b/yazi-fm/src/widgets/clear.rs @@ -32,7 +32,7 @@ impl Widget for Clear { return; }; - ADAPTOR.image_hide(r).ok(); + ADAPTOR.image_erase(r).ok(); COLLISION.store(true, Ordering::Relaxed); for x in r.left()..r.right() { for y in r.top()..r.bottom() { diff --git a/yazi-plugin/Cargo.toml b/yazi-plugin/Cargo.toml index e00de264..38517200 100644 --- a/yazi-plugin/Cargo.toml +++ b/yazi-plugin/Cargo.toml @@ -16,6 +16,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.1.5" } # External dependencies ansi-to-tui = "^3" anyhow = "^1" +crossterm = "^0" futures = "^0" libc = "^0" md-5 = "^0" diff --git a/yazi-plugin/preset/plugins/archive.lua b/yazi-plugin/preset/plugins/archive.lua index 8811fec1..1b93ab19 100644 --- a/yazi-plugin/preset/plugins/archive.lua +++ b/yazi-plugin/preset/plugins/archive.lua @@ -1,9 +1,9 @@ local Archive = {} function Archive:peek() - local _, max = ya.preview_archive(self.area, self.file, self.skip) - if max then - ya.manager_emit("peek", { tostring(max) }) + local _, bound = ya.preview_archive(self) + if bound then + ya.manager_emit("peek", { tostring(bound), only_if = tostring(self.file.url), upper_bound = "" }) end end @@ -11,7 +11,10 @@ function Archive:seek(units) local h = cx.active.current.hovered if h and h.url == self.file.url then local step = math.floor(units * self.area.h / 10) - ya.manager_emit("peek", { tostring(math.max(0, cx.active.preview.skip + step)) }) + ya.manager_emit("peek", { + tostring(math.max(0, cx.active.preview.skip + step)), + only_if = tostring(self.file.url), + }) end end diff --git a/yazi-plugin/preset/plugins/code.lua b/yazi-plugin/preset/plugins/code.lua index 7b8ea169..a649a60d 100644 --- a/yazi-plugin/preset/plugins/code.lua +++ b/yazi-plugin/preset/plugins/code.lua @@ -1,9 +1,9 @@ local Code = {} function Code:peek() - local _, max = ya.preview_code(self.area, self.file, self.skip) - if max then - ya.manager_emit("peek", { tostring(max) }) + local _, bound = ya.preview_code(self) + if bound then + ya.manager_emit("peek", { tostring(bound), only_if = tostring(self.file.url), upper_bound = "" }) end end @@ -11,7 +11,10 @@ function Code:seek(units) local h = cx.active.current.hovered if h and h.url == self.file.url then local step = math.floor(units * self.area.h / 10) - ya.manager_emit("peek", { tostring(math.max(0, cx.active.preview.skip + step)) }) + ya.manager_emit("peek", { + tostring(math.max(0, cx.active.preview.skip + step)), + only_if = tostring(self.file.url), + }) end end diff --git a/yazi-plugin/preset/plugins/folder.lua b/yazi-plugin/preset/plugins/folder.lua index f6769ddd..40762a9a 100644 --- a/yazi-plugin/preset/plugins/folder.lua +++ b/yazi-plugin/preset/plugins/folder.lua @@ -2,10 +2,15 @@ local Folder_ = {} function Folder_:peek() local folder = Folder:by_kind(Folder.PREVIEW) - if folder == nil then + if folder == nil or folder.cwd ~= self.file.url then return {} end + local bound = math.max(0, #folder.files - self.area.h) + if self.skip > bound then + ya.manager_emit("peek", { tostring(bound), only_if = tostring(self.file.url), upper_bound = "" }) + end + local items = {} for _, f in ipairs(folder.window) do local item = ui.ListItem(ui.Line { Folder:icon(f), ui.Span(f.name) }) @@ -16,11 +21,19 @@ function Folder_:peek() end items[#items + 1] = item end - ya.preview_widgets(self.file, self.skip, { ui.List(self.area, items) }) + ya.preview_widgets(self, { ui.List(self.area, items) }) end function Folder_:seek(units) - -- TODO + local folder = Folder:by_kind(Folder.PREVIEW) + if folder and folder.cwd == self.file.url then + local step = math.floor(units * self.area.h / 10) + local bound = math.max(0, #folder.files - self.area.h) + ya.manager_emit("peek", { + tostring(ya.clamp(0, cx.active.preview.skip + step, bound)), + only_if = tostring(self.file.url), + }) + end end return Folder_ diff --git a/yazi-plugin/preset/plugins/image.lua b/yazi-plugin/preset/plugins/image.lua index 60a9b1ce..eeef648f 100644 --- a/yazi-plugin/preset/plugins/image.lua +++ b/yazi-plugin/preset/plugins/image.lua @@ -5,7 +5,7 @@ function Image:cache() return ya.cache_file(self.file.url .. tostring(self.file. function Image:peek() local cache = self:cache() ya.image_show(fs.symlink_metadata(cache) and cache or self.file.url, self.area) - ya.preview_widgets(self.file, self.skip, {}) + ya.preview_widgets(self, {}) end function Image:seek() end diff --git a/yazi-plugin/preset/plugins/json.lua b/yazi-plugin/preset/plugins/json.lua index b30e7484..2ddfdb28 100644 --- a/yazi-plugin/preset/plugins/json.lua +++ b/yazi-plugin/preset/plugins/json.lua @@ -28,10 +28,10 @@ function Json:peek() child:start_kill() if self.skip > 0 and i < self.skip + limit then - ya.manager_emit("peek", { tostring(math.max(0, i - limit)) }) + ya.manager_emit("peek", { tostring(math.max(0, i - limit)), only_if = tostring(self.file.url), upper_bound = "" }) else lines = lines:gsub("\t", string.rep(" ", PREVIEW.tab_size)) - ya.preview_widgets(self.file, self.skip, { ui.Paragraph.parse(self.area, lines) }) + ya.preview_widgets(self, { ui.Paragraph.parse(self.area, lines) }) end end @@ -39,7 +39,10 @@ function Json:seek(units) local h = cx.active.current.hovered if h and h.url == self.file.url then local step = math.floor(units * self.area.h / 10) - ya.manager_emit("peek", { tostring(math.max(0, cx.active.preview.skip + step)) }) + ya.manager_emit("peek", { + tostring(math.max(0, cx.active.preview.skip + step)), + only_if = tostring(self.file.url), + }) end end diff --git a/yazi-plugin/preset/plugins/noop.lua b/yazi-plugin/preset/plugins/noop.lua new file mode 100644 index 00000000..7c114a96 --- /dev/null +++ b/yazi-plugin/preset/plugins/noop.lua @@ -0,0 +1,9 @@ +local Noop = {} + +function Noop:peek() end + +function Noop:seek() end + +function Noop:preload() return 1 end + +return Noop diff --git a/yazi-plugin/preset/plugins/pdf.lua b/yazi-plugin/preset/plugins/pdf.lua index b9a21cea..2c2bacf6 100644 --- a/yazi-plugin/preset/plugins/pdf.lua +++ b/yazi-plugin/preset/plugins/pdf.lua @@ -5,7 +5,7 @@ function Pdf:cache() return ya.cache_file(self.file.url .. self.skip .. tostring function Pdf:peek() if self:preload() == 1 then ya.image_show(self:cache(), self.area) - ya.preview_widgets(self.file, self.skip, {}) + ya.preview_widgets(self, {}) end end @@ -13,7 +13,7 @@ function Pdf:seek(units) local h = cx.active.current.hovered if h and h.url == self.file.url then local step = ya.clamp(-1, units, 1) - ya.manager_emit("peek", { tostring(math.max(0, cx.active.preview.skip + step)) }) + ya.manager_emit("peek", { tostring(math.max(0, cx.active.preview.skip + step)), only_if = tostring(self.file.url) }) end end @@ -32,7 +32,7 @@ function Pdf:preload() if not output.status:success() then local pages = tonumber(output.stderr:match("the last page %((%d+)%)")) or 0 if self.skip > 0 and pages > 0 then - ya.manager_emit("peek", { tostring(math.max(0, pages - 1)) }) + ya.manager_emit("peek", { tostring(math.max(0, pages - 1)), only_if = tostring(self.file.url), upper_bound = "" }) end return 0 end diff --git a/yazi-plugin/preset/plugins/video.lua b/yazi-plugin/preset/plugins/video.lua index e9f6641e..2fe4523d 100644 --- a/yazi-plugin/preset/plugins/video.lua +++ b/yazi-plugin/preset/plugins/video.lua @@ -5,21 +5,24 @@ function Video:cache() return ya.cache_file(self.file.url .. self.skip .. tostri function Video:peek() if self:preload() == 1 then ya.image_show(self:cache(), self.area) - ya.preview_widgets(self.file, self.skip, {}) + ya.preview_widgets(self, {}) end end function Video:seek(units) local h = cx.active.current.hovered if h and h.url == self.file.url then - ya.manager_emit("peek", { tostring(math.max(0, cx.active.preview.skip + units)) }) + ya.manager_emit("peek", { + tostring(math.max(0, cx.active.preview.skip + units)), + only_if = tostring(self.file.url), + }) end end function Video:preload() local percentage = 5 + self.skip if percentage > 95 then - ya.manager_emit("peek", { "90" }) + ya.manager_emit("peek", { "90", only_if = tostring(self.file.url), upper_bound = "" }) return 2 end diff --git a/yazi-plugin/src/bindings/mod.rs b/yazi-plugin/src/bindings/mod.rs index 8ce302bd..87b62e37 100644 --- a/yazi-plugin/src/bindings/mod.rs +++ b/yazi-plugin/src/bindings/mod.rs @@ -5,12 +5,14 @@ mod cha; mod file; mod range; mod url; +mod window; pub use bindings::*; pub use cha::*; pub use file::*; pub use range::*; pub use url::*; +pub use window::*; pub trait Cast { fn cast(lua: &mlua::Lua, data: T) -> mlua::Result; diff --git a/yazi-plugin/src/bindings/window.rs b/yazi-plugin/src/bindings/window.rs new file mode 100644 index 00000000..3fcb2cf5 --- /dev/null +++ b/yazi-plugin/src/bindings/window.rs @@ -0,0 +1,26 @@ +use mlua::{prelude::LuaUserDataFields, FromLua, UserData}; +use yazi_shared::term::Term; + +#[derive(Debug, Clone, Copy, FromLua)] +pub struct Window { + pub rows: u16, + pub cols: u16, + pub width: u16, + pub height: u16, +} + +impl Default for Window { + fn default() -> Self { + let ws = Term::size(); + Self { rows: ws.rows, cols: ws.columns, width: ws.width, height: ws.height } + } +} + +impl UserData for Window { + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("rows", |_, me| Ok(me.rows)); + fields.add_field_method_get("cols", |_, me| Ok(me.cols)); + fields.add_field_method_get("width", |_, me| Ok(me.width)); + fields.add_field_method_get("height", |_, me| Ok(me.height)); + } +} diff --git a/yazi-plugin/src/isolate/peek.rs b/yazi-plugin/src/isolate/peek.rs index 30897dae..c87bf4be 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::Exec, Layer}; use super::slim_lua; -use crate::{bindings::{Cast, File}, elements::Rect, OptData, LOADED, LUA}; +use crate::{bindings::{Cast, File, Window}, elements::Rect, OptData, LOADED, LUA}; pub fn peek(exec: &Exec, file: yazi_shared::fs::File, skip: usize) -> CancellationToken { let ct = CancellationToken::new(); @@ -33,6 +33,7 @@ pub fn peek(exec: &Exec, file: yazi_shared::fs::File, skip: usize) -> Cancellati plugin.set("file", File::cast(&lua, file)?)?; plugin.set("skip", skip)?; plugin.set("area", Rect::cast(&lua, LAYOUT.load().preview)?)?; + plugin.set("window", Window::default())?; if ct2.is_cancelled() { Ok(()) } else { plugin.call_async_method("peek", ()).await } }; @@ -59,6 +60,7 @@ pub fn peek_sync(exec: &Exec, file: yazi_shared::fs::File, skip: usize) { plugin.set("file", File::cast(&LUA, file)?)?; plugin.set("skip", skip)?; plugin.set("area", Rect::cast(&LUA, LAYOUT.load().preview)?)?; + plugin.set("window", Window::default())?; plugin.call_method("peek", ()) })), tx: None, diff --git a/yazi-plugin/src/isolate/preload.rs b/yazi-plugin/src/isolate/preload.rs index cf7272c5..92b532ea 100644 --- a/yazi-plugin/src/isolate/preload.rs +++ b/yazi-plugin/src/isolate/preload.rs @@ -5,10 +5,13 @@ use yazi_config::LAYOUT; use super::slim_lua; use crate::{bindings::{Cast, File}, elements::Rect, LOADED}; -pub async fn preload(name: String, files: Vec) -> mlua::Result { +pub async fn preload( + name: String, + files: Vec, + multi: bool, +) -> mlua::Result { LOADED.ensure(&name).await.into_lua_err()?; - let multi = files.len() > 1; tokio::task::spawn_blocking(move || { let lua = slim_lua()?; let plugin: Table = if let Some(b) = LOADED.read().get(&name) { diff --git a/yazi-plugin/src/loader.rs b/yazi-plugin/src/loader.rs index b3404799..fe4b7aa2 100644 --- a/yazi-plugin/src/loader.rs +++ b/yazi-plugin/src/loader.rs @@ -24,7 +24,7 @@ impl Loader { let b = fs::read(BOOT.plugin_dir.join(name)).await.map(|v| v.into()).unwrap_or(Cow::from( match name { - "noop.lua" => &[] as &[u8], + "noop.lua" => include_bytes!("../preset/plugins/noop.lua") as &[u8], "archive.lua" => include_bytes!("../preset/plugins/archive.lua"), "code.lua" => include_bytes!("../preset/plugins/code.lua"), "folder.lua" => include_bytes!("../preset/plugins/folder.lua"), diff --git a/yazi-plugin/src/utils/call.rs b/yazi-plugin/src/utils/call.rs index 521b3b64..78081802 100644 --- a/yazi-plugin/src/utils/call.rs +++ b/yazi-plugin/src/utils/call.rs @@ -20,7 +20,7 @@ impl Utils { args.push(v.to_str()?.to_owned()); } Value::String(s) => { - named.insert(s.to_str()?.to_owned(), v.to_str()?.to_owned()); + named.insert(s.to_str()?.replace('_', "-"), v.to_str()?.to_owned()); } _ => return Err("invalid key in exec".into_lua_err()), } diff --git a/yazi-plugin/src/utils/preview.rs b/yazi-plugin/src/utils/preview.rs index 4fc4da70..b3f42f69 100644 --- a/yazi-plugin/src/utils/preview.rs +++ b/yazi-plugin/src/utils/preview.rs @@ -4,87 +4,89 @@ use yazi_config::PREVIEW; use yazi_shared::{emit, event::Exec, Layer, PeekError}; use super::Utils; -use crate::{bindings::FileRef, cast_to_renderable, elements::{Paragraph, RectRef, Renderable}, external::{self, Highlighter}}; +use crate::{bindings::{FileRef, Window}, cast_to_renderable, elements::{Paragraph, RectRef, Renderable}, external::{self, Highlighter}}; pub struct PreviewLock { pub url: yazi_shared::fs::Url, pub cha: yazi_shared::fs::Cha, - pub skip: usize, - pub data: Vec>, + pub skip: usize, + pub window: Window, + pub data: Vec>, +} + +impl<'a> TryFrom> for PreviewLock { + type Error = mlua::Error; + + fn try_from(t: Table) -> Result { + let file: FileRef = t.get("file")?; + Ok(Self { + url: file.url(), + cha: file.cha, + skip: t.get("skip")?, + window: t.get("window")?, + data: Default::default(), + }) + } } impl Utils { pub(super) fn preview(lua: &Lua, ya: &Table) -> mlua::Result<()> { ya.set( "preview_code", - lua.create_async_function( - |lua, (area, file, skip): (RectRef, FileRef, usize)| async move { - let s = match Highlighter::new(&file.url).highlight(skip, area.height as usize).await { - Ok(s) => s.replace('\t', &" ".repeat(PREVIEW.tab_size as usize)), - Err(PeekError::Exceed(max)) => return (false, max).into_lua_multi(lua), - Err(_) => return (false, Value::Nil).into_lua_multi(lua), - }; + lua.create_async_function(|lua, t: Table| async move { + let area: RectRef = t.get("area")?; + let mut lock = PreviewLock::try_from(t)?; - let lock = PreviewLock { - url: file.url(), - cha: file.cha, - skip, - data: vec![Box::new(Paragraph { - area: *area, - text: s.into_text().into_lua_err()?, - ..Default::default() - })], - }; + let s = match Highlighter::new(&lock.url).highlight(lock.skip, area.height as usize).await { + Ok(s) => s.replace('\t', &" ".repeat(PREVIEW.tab_size as usize)), + Err(PeekError::Exceed(max)) => return (false, max).into_lua_multi(lua), + Err(_) => return (false, Value::Nil).into_lua_multi(lua), + }; - emit!(Call(Exec::call("preview", vec![]).with_data(lock).vec(), Layer::Manager)); - (true, Value::Nil).into_lua_multi(lua) - }, - )?, + lock.data = vec![Box::new(Paragraph { + area: *area, + text: s.into_text().into_lua_err()?, + ..Default::default() + })]; + + emit!(Call(Exec::call("preview", vec![]).with_data(lock).vec(), Layer::Manager)); + (true, Value::Nil).into_lua_multi(lua) + })?, )?; - // TODO: remove this once "archives as directories" feature is implemented ya.set( "preview_archive", - lua.create_async_function( - |lua, (area, file, skip): (RectRef, FileRef, usize)| async move { - let lines: Vec<_> = match external::lsar(&file.url, 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), - }; + lua.create_async_function(|lua, t: Table| async move { + let area: RectRef = t.get("area")?; + let mut lock = PreviewLock::try_from(t)?; - let lock = PreviewLock { - url: file.url(), - cha: file.cha, - skip, - data: vec![Box::new(Paragraph { - area: *area, - text: ratatui::text::Text::from(lines), - ..Default::default() - })], - }; + 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), + }; - emit!(Call(Exec::call("preview", vec![]).with_data(lock).vec(), Layer::Manager)); - (true, Value::Nil).into_lua_multi(lua) - }, - )?, + lock.data = vec![Box::new(Paragraph { + area: *area, + text: ratatui::text::Text::from(lines), + ..Default::default() + })]; + + emit!(Call(Exec::call("preview", vec![]).with_data(lock).vec(), Layer::Manager)); + (true, Value::Nil).into_lua_multi(lua) + })?, )?; ya.set( "preview_widgets", - lua.create_async_function( - |_, (file, skip, widgets): (FileRef, usize, Vec)| async move { - let lock = PreviewLock { - url: file.url(), - cha: file.cha, - skip, - data: widgets.into_iter().filter_map(cast_to_renderable).collect(), - }; - emit!(Call(Exec::call("preview", vec![]).with_data(lock).vec(), Layer::Manager)); - Ok(()) - }, - )?, + lua.create_async_function(|_, (t, widgets): (Table, Vec)| async move { + let mut lock = PreviewLock::try_from(t)?; + lock.data = widgets.into_iter().filter_map(cast_to_renderable).collect(); + + emit!(Call(Exec::call("preview", vec![]).with_data(lock).vec(), Layer::Manager)); + Ok(()) + })?, )?; Ok(()) diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index b9cf594d..dffda18a 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -308,11 +308,11 @@ impl Scheduler { _ = self.todo.send_blocking({ let preload = self.preload.clone(); - let rule_id = rule.id; + let (rule_id, rule_multi) = (rule.id, rule.multi); let cmd = rule.exec.cmd.clone(); let targets = targets.into_iter().cloned().collect(); async move { - preload.rule(PreloadOpRule { id, rule_id, plugin: cmd, targets }).await.ok(); + preload.rule(PreloadOpRule { id, rule_id, rule_multi, plugin: cmd, targets }).await.ok(); } .boxed() }); diff --git a/yazi-scheduler/src/workers/preload.rs b/yazi-scheduler/src/workers/preload.rs index 3c9bc1a6..7c6c6575 100644 --- a/yazi-scheduler/src/workers/preload.rs +++ b/yazi-scheduler/src/workers/preload.rs @@ -24,10 +24,11 @@ pub struct PreloadOpSize { #[derive(Clone, Debug)] pub struct PreloadOpRule { - pub id: usize, - pub rule_id: u8, - pub plugin: String, - pub targets: Vec, + pub id: usize, + pub rule_id: u8, + pub rule_multi: bool, + pub plugin: String, + pub targets: Vec, } impl Preload { @@ -39,7 +40,7 @@ impl Preload { self.sch.send(TaskOp::New(task.id, 0))?; let urls: Vec<_> = task.targets.iter().map(|f| f.url()).collect(); - let result = yazi_plugin::isolate::preload(task.plugin, task.targets).await; + let result = yazi_plugin::isolate::preload(task.plugin, task.targets, task.rule_multi).await; if let Err(e) = result { self.fail(task.id, format!("Preload task failed:\n{e}"))?; return Err(e.into());