This commit is contained in:
sxyazi 2023-12-21 00:40:32 +08:00
parent f997bfdea0
commit b910b5f0ab
No known key found for this signature in database
36 changed files with 239 additions and 145 deletions

2
Cargo.lock generated
View file

@ -2614,6 +2614,7 @@ name = "yazi-adaptor"
version = "0.1.5" version = "0.1.5"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arc-swap",
"base64", "base64",
"color_quant", "color_quant",
"image", "image",
@ -2713,6 +2714,7 @@ version = "0.1.5"
dependencies = [ dependencies = [
"ansi-to-tui", "ansi-to-tui",
"anyhow", "anyhow",
"crossterm",
"futures", "futures",
"libc", "libc",
"md-5", "md-5",

View file

@ -14,6 +14,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.1.5" }
# External dependencies # External dependencies
anyhow = "^1" anyhow = "^1"
arc-swap = "^1"
base64 = "^0" base64 = "^0"
color_quant = "^1" color_quant = "^1"
image = "^0" image = "^0"

View file

@ -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 anyhow::{anyhow, Result};
use ratatui::prelude::Rect; use ratatui::prelude::Rect;
use tracing::warn; use tracing::warn;
use yazi_shared::env_exists; use yazi_shared::{env_exists, term::Term};
use super::{Iterm2, Kitty, KittyOld}; use super::{Iterm2, Kitty, KittyOld};
use crate::{ueberzug::Ueberzug, Sixel, TMUX}; use crate::{ueberzug::Ueberzug, Sixel, SHOWN, TMUX};
static IMAGE_SHOWN: AtomicBool = AtomicBool::new(false);
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Adaptor { pub enum Adaptor {
@ -162,29 +160,40 @@ impl Adaptor {
pub(super) fn start(self) { Ueberzug::start(self); } pub(super) fn start(self) { Ueberzug::start(self); }
pub async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> { pub async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> {
self.image_hide(rect).ok(); self.image_hide().ok();
IMAGE_SHOWN.store(true, Ordering::Relaxed);
match self { let size = match self {
Self::Kitty => Kitty::image_show(path, rect).await, Self::Kitty => Kitty::image_show(path, rect).await,
Self::KittyOld => KittyOld::image_show(path, rect).await, Self::KittyOld => KittyOld::image_show(path, rect).await,
Self::Iterm2 => Iterm2::image_show(path, rect).await, Self::Iterm2 => Iterm2::image_show(path, rect).await,
Self::Sixel => Sixel::image_show(path, rect).await, Self::Sixel => Sixel::image_show(path, rect).await,
_ => Ueberzug::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<()> { pub fn image_hide(self) -> Result<()> {
if !IMAGE_SHOWN.swap(false, Ordering::Relaxed) { if let Some(rect) = SHOWN.swap(None) { self.image_erase(*rect) } else { Ok(()) }
return Ok(()); }
}
pub fn image_erase(self, rect: Rect) -> Result<()> {
match self { match self {
Self::Kitty => Kitty::image_hide(rect), Self::Kitty => Kitty::image_erase(rect),
Self::Iterm2 => Iterm2::image_hide(rect), Self::Iterm2 => Iterm2::image_erase(rect),
Self::KittyOld => KittyOld::image_hide(), Self::KittyOld => KittyOld::image_erase(),
Self::Sixel => Sixel::image_hide(rect), Self::Sixel => Sixel::image_erase(rect),
_ => Ueberzug::image_hide(rect), _ => Ueberzug::image_erase(rect),
} }
} }

View file

@ -7,7 +7,7 @@ use ratatui::prelude::Rect;
use yazi_shared::term::Term; use yazi_shared::term::Term;
use super::image::Image; use super::image::Image;
use crate::{CLOSE, START}; use crate::{adaptor::Adaptor, CLOSE, START};
pub(super) struct Iterm2; pub(super) struct Iterm2;
@ -17,14 +17,14 @@ impl Iterm2 {
let size = (img.width(), img.height()); let size = (img.width(), img.height());
let b = Self::encode(img).await?; let b = Self::encode(img).await?;
Self::image_hide(rect)?; Adaptor::Iterm2.image_hide()?;
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| { Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| {
stdout.write_all(&b)?; stdout.write_all(&b)?;
Ok(size) 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 stdout = BufWriter::new(stdout().lock());
let s = " ".repeat(rect.width as usize); let s = " ".repeat(rect.width as usize);
Term::move_lock(stdout, (0, 0), |stdout| { Term::move_lock(stdout, (0, 0), |stdout| {

View file

@ -7,7 +7,7 @@ use ratatui::prelude::Rect;
use yazi_shared::term::Term; use yazi_shared::term::Term;
use super::image::Image; use super::image::Image;
use crate::{CLOSE, ESCAPE, START}; use crate::{adaptor::Adaptor, CLOSE, ESCAPE, START};
static DIACRITICS: [char; 297] = [ static DIACRITICS: [char; 297] = [
'\u{0305}', '\u{0305}',
@ -317,7 +317,7 @@ impl Kitty {
let size = (img.width(), img.height()); let size = (img.width(), img.height());
let b = Self::encode(img).await?; let b = Self::encode(img).await?;
Self::image_hide(rect)?; Adaptor::Kitty.image_hide()?;
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| { Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| {
stdout.write_all(&b)?; 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 stdout = BufWriter::new(stdout().lock());
let s = " ".repeat(rect.width as usize); let s = " ".repeat(rect.width as usize);
Term::move_lock(stdout, (0, 0), |stdout| { Term::move_lock(stdout, (0, 0), |stdout| {

View file

@ -7,7 +7,7 @@ use ratatui::prelude::Rect;
use yazi_shared::term::Term; use yazi_shared::term::Term;
use super::image::Image; use super::image::Image;
use crate::{CLOSE, ESCAPE, START}; use crate::{adaptor::Adaptor, CLOSE, ESCAPE, START};
pub(super) struct KittyOld; pub(super) struct KittyOld;
@ -17,7 +17,7 @@ impl KittyOld {
let size = (img.width(), img.height()); let size = (img.width(), img.height());
let b = Self::encode(img).await?; let b = Self::encode(img).await?;
Self::image_hide()?; Adaptor::KittyOld.image_hide()?;
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| { Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| {
stdout.write_all(&b)?; stdout.write_all(&b)?;
Ok(size) Ok(size)
@ -25,7 +25,7 @@ impl KittyOld {
} }
#[inline] #[inline]
pub(super) fn image_hide() -> Result<()> { pub(super) fn image_erase() -> Result<()> {
let mut stdout = stdout().lock(); let mut stdout = stdout().lock();
stdout.write_all(format!("{}_Gq=1,a=d,d=A{}\\{}", START, ESCAPE, CLOSE).as_bytes())?; stdout.write_all(format!("{}_Gq=1,a=d,d=A{}\\{}", START, ESCAPE, CLOSE).as_bytes())?;
stdout.flush()?; stdout.flush()?;

View file

@ -25,12 +25,17 @@ static ESCAPE: RoCell<&'static str> = RoCell::new();
static START: RoCell<&'static str> = RoCell::new(); static START: RoCell<&'static str> = RoCell::new();
static CLOSE: RoCell<&'static str> = RoCell::new(); static CLOSE: RoCell<&'static str> = RoCell::new();
// Image state
static SHOWN: RoCell<arc_swap::ArcSwapOption<ratatui::layout::Rect>> = RoCell::new();
pub fn init() { pub fn init() {
TMUX.init(env_exists("TMUX")); TMUX.init(env_exists("TMUX"));
START.init(if *TMUX { "\x1bPtmux;\x1b\x1b" } else { "\x1b" }); START.init(if *TMUX { "\x1bPtmux;\x1b\x1b" } else { "\x1b" });
CLOSE.init(if *TMUX { "\x1b\\" } else { "" }); CLOSE.init(if *TMUX { "\x1b\\" } else { "" });
ESCAPE.init(if *TMUX { "\x1b\x1b" } else { "\x1b" }); ESCAPE.init(if *TMUX { "\x1b\x1b" } else { "\x1b" });
SHOWN.with(Default::default);
ADAPTOR.init(Adaptor::detect()); ADAPTOR.init(Adaptor::detect());
ADAPTOR.start(); ADAPTOR.start();

View file

@ -6,7 +6,7 @@ use image::DynamicImage;
use ratatui::prelude::Rect; use ratatui::prelude::Rect;
use yazi_shared::term::Term; use yazi_shared::term::Term;
use crate::{Image, CLOSE, ESCAPE, START}; use crate::{adaptor::Adaptor, Image, CLOSE, ESCAPE, START};
pub(super) struct Sixel; pub(super) struct Sixel;
@ -16,14 +16,14 @@ impl Sixel {
let size = (img.width(), img.height()); let size = (img.width(), img.height());
let b = Self::encode(img).await?; let b = Self::encode(img).await?;
Self::image_hide(rect)?; Adaptor::Sixel.image_hide()?;
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| { Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| {
stdout.write_all(&b)?; stdout.write_all(&b)?;
Ok(size) 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 stdout = BufWriter::new(stdout().lock());
let s = " ".repeat(rect.width as usize); let s = " ".repeat(rect.width as usize);
Term::move_lock(stdout, (0, 0), |stdout| { Term::move_lock(stdout, (0, 0), |stdout| {

View file

@ -61,7 +61,7 @@ impl Ueberzug {
Ok(((w as f64 * ratio).round() as u32, (h as f64 * ratio).round() as u32)) 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 { if let Some(tx) = &*DEMON {
Ok(tx.send(None)?) Ok(tx.send(None)?)
} else { } else {

View file

@ -1,13 +1,12 @@
use std::ffi::OsString; use std::{cell::RefCell, ffi::OsString};
use parking_lot::Mutex;
use yazi_shared::RoCell; use yazi_shared::RoCell;
pub static CLIPBOARD: RoCell<Clipboard> = RoCell::new(); pub static CLIPBOARD: RoCell<Clipboard> = RoCell::new();
#[derive(Default)] #[derive(Default)]
pub struct Clipboard { pub struct Clipboard {
content: Mutex<OsString>, content: RefCell<OsString>,
} }
impl Clipboard { impl Clipboard {
@ -19,7 +18,7 @@ impl Clipboard {
use yazi_shared::in_ssh_connection; use yazi_shared::in_ssh_connection;
if in_ssh_connection() { if in_ssh_connection() {
return self.content.lock().clone(); return self.content.borrow().clone();
} }
let all = [ let all = [
@ -37,7 +36,7 @@ impl Clipboard {
return OsString::from_vec(output.stdout); return OsString::from_vec(output.stdout);
} }
} }
self.content.lock().clone() self.content.borrow().clone()
} }
#[cfg(windows)] #[cfg(windows)]
@ -60,7 +59,7 @@ impl Clipboard {
use tokio::{io::AsyncWriteExt, process::Command}; use tokio::{io::AsyncWriteExt, process::Command};
use yazi_shared::in_ssh_connection; 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() { if in_ssh_connection() {
execute!(stdout(), osc52::SetClipboard::new(s.as_ref())).ok(); execute!(stdout(), osc52::SetClipboard::new(s.as_ref())).ok();
} }

View file

@ -1,6 +1,6 @@
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use yazi_adaptor::ADAPTOR; use yazi_adaptor::ADAPTOR;
use yazi_config::{LAYOUT, PLUGIN}; use yazi_config::PLUGIN;
use yazi_plugin::{external::Highlighter, utils::PreviewLock}; use yazi_plugin::{external::Highlighter, utils::PreviewLock};
use yazi_shared::fs::{Cha, File, Url}; use yazi_shared::fs::{Cha, File, Url};
@ -18,11 +18,12 @@ impl Preview {
return; return;
} }
self.abort();
let Some(previewer) = PLUGIN.previewer(&file.url, &mime) else { let Some(previewer) = PLUGIN.previewer(&file.url, &mime) else {
self.reset();
return; return;
}; };
self.abort();
if previewer.sync { if previewer.sync {
yazi_plugin::isolate::peek_sync(&previewer.exec, file, self.skip); yazi_plugin::isolate::peek_sync(&previewer.exec, file, self.skip);
} else { } else {
@ -39,7 +40,7 @@ impl Preview {
#[inline] #[inline]
pub fn reset(&mut self) -> bool { pub fn reset(&mut self) -> bool {
self.abort(); self.abort();
ADAPTOR.image_hide(LAYOUT.load().preview).ok(); ADAPTOR.image_hide().ok();
self.lock.take().is_some() self.lock.take().is_some()
} }

View file

@ -2,7 +2,7 @@ use std::sync::atomic::Ordering;
use anyhow::{Ok, Result}; use anyhow::{Ok, Result};
use crossterm::event::KeyEvent; use crossterm::event::KeyEvent;
use ratatui::{backend::Backend, prelude::Rect}; use ratatui::backend::Backend;
use yazi_config::{keymap::Key, ARGS}; use yazi_config::{keymap::Key, ARGS};
use yazi_core::input::InputMode; use yazi_core::input::InputMode;
use yazi_shared::{emit, event::{Event, Exec}, fs::FilesOp, term::Term, Layer, COLLISION}; 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::Key(key) => app.dispatch_key(key),
Event::Paste(str) => app.dispatch_paste(str), Event::Paste(str) => app.dispatch_paste(str),
Event::Render(_) => app.dispatch_render()?, 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::Call(exec, layer) => app.dispatch_call(exec, layer),
event => app.dispatch_module(event), event => app.dispatch_module(event),
} }
@ -112,15 +112,13 @@ impl App {
Ok(()) Ok(())
} }
fn dispatch_resize(&mut self, cols: u16, rows: u16) { fn dispatch_resize(&mut self, _: u16, _: u16) -> Result<()> {
if let Some(term) = &mut self.term { self.cx.manager.active_mut().preview.reset();
term.resize(Rect::new(0, 0, cols, rows)).ok(); self.dispatch_render()?;
}
self.cx.manager.current_mut().set_page(true); self.cx.manager.current_mut().set_page(true);
self.cx.manager.active_mut().preview.reset();
self.cx.manager.peek(()); self.cx.manager.peek(());
emit!(Render); Ok(())
} }
#[inline] #[inline]

View file

@ -15,6 +15,10 @@ impl App {
return self.cx.tasks.plugin_micro(&opt.name); return self.cx.tasks.plugin_micro(&opt.name);
} }
if LOADED.read().contains_key(&opt.name) {
return self.plugin_do(opt);
}
tokio::spawn(async move { tokio::spawn(async move {
if LOADED.ensure(&opt.name).await.is_ok() { if LOADED.ensure(&opt.name).await.is_ok() {
emit!(Call(Exec::call("plugin_do", vec![opt.name]).with_data(opt.data).vec(), Layer::App)); emit!(Call(Exec::call("plugin_do", vec![opt.name]).with_data(opt.data).vec(), Layer::App));

View file

@ -12,12 +12,16 @@ impl<'a> Preview<'a> {
} }
impl Widget for Preview<'_> { 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 preview = &self.cx.manager.active().preview;
let Some(lock) = &preview.lock else { let Some(lock) = &preview.lock else {
return; return;
}; };
if (lock.window.rows, lock.window.cols) != (area.height, area.width) {
return;
}
for w in &lock.data { for w in &lock.data {
w.clone_render(buf); w.clone_render(buf);
} }

View file

@ -60,7 +60,7 @@ impl<'a, 'b> Active<'a, 'b> {
fn preview(&self, tab: &'a yazi_core::tab::Tab) -> mlua::Result<AnyUserData<'a>> { fn preview(&self, tab: &'a yazi_core::tab::Tab) -> mlua::Result<AnyUserData<'a>> {
let inner = &tab.preview; 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)?; let ud = self.scope.create_any_userdata_ref(inner)?;
ud.set_named_user_value( ud.set_named_user_value(
@ -70,7 +70,7 @@ impl<'a, 'b> Active<'a, 'b> {
.hovered() .hovered()
.filter(|&f| f.is_dir()) .filter(|&f| f.is_dir())
.and_then(|f| tab.history(&f.url)) .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) Ok(ud)

View file

@ -166,7 +166,7 @@ impl<'a, 'b> Folder<'a, 'b> {
pub(crate) fn make(&self, window: Option<(usize, usize)>) -> mlua::Result<AnyUserData<'a>> { pub(crate) fn make(&self, window: Option<(usize, usize)>) -> mlua::Result<AnyUserData<'a>> {
let window = 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)?; let ud = self.scope.create_any_userdata_ref(self.inner)?;
ud.set_named_user_value( ud.set_named_user_value(

View file

@ -21,7 +21,7 @@ impl<'a> Widget for Root<'a> {
components::Header.render(chunks[0], buf); components::Header.render(chunks[0], buf);
components::Manager.render(chunks[1], buf); components::Manager.render(chunks[1], buf);
components::Status.render(chunks[2], 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 { if self.cx.tasks.visible {
tasks::Layout::new(self.cx).render(area, buf); tasks::Layout::new(self.cx).render(area, buf);

View file

@ -32,7 +32,7 @@ impl Widget for Clear {
return; return;
}; };
ADAPTOR.image_hide(r).ok(); ADAPTOR.image_erase(r).ok();
COLLISION.store(true, Ordering::Relaxed); COLLISION.store(true, Ordering::Relaxed);
for x in r.left()..r.right() { for x in r.left()..r.right() {
for y in r.top()..r.bottom() { for y in r.top()..r.bottom() {

View file

@ -16,6 +16,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.1.5" }
# External dependencies # External dependencies
ansi-to-tui = "^3" ansi-to-tui = "^3"
anyhow = "^1" anyhow = "^1"
crossterm = "^0"
futures = "^0" futures = "^0"
libc = "^0" libc = "^0"
md-5 = "^0" md-5 = "^0"

View file

@ -1,9 +1,9 @@
local Archive = {} local Archive = {}
function Archive:peek() function Archive:peek()
local _, max = ya.preview_archive(self.area, self.file, self.skip) local _, bound = ya.preview_archive(self)
if max then if bound then
ya.manager_emit("peek", { tostring(max) }) ya.manager_emit("peek", { tostring(bound), only_if = tostring(self.file.url), upper_bound = "" })
end end
end end
@ -11,7 +11,10 @@ function Archive:seek(units)
local h = cx.active.current.hovered local h = cx.active.current.hovered
if h and h.url == self.file.url then if h and h.url == self.file.url then
local step = math.floor(units * self.area.h / 10) 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
end end

View file

@ -1,9 +1,9 @@
local Code = {} local Code = {}
function Code:peek() function Code:peek()
local _, max = ya.preview_code(self.area, self.file, self.skip) local _, bound = ya.preview_code(self)
if max then if bound then
ya.manager_emit("peek", { tostring(max) }) ya.manager_emit("peek", { tostring(bound), only_if = tostring(self.file.url), upper_bound = "" })
end end
end end
@ -11,7 +11,10 @@ function Code:seek(units)
local h = cx.active.current.hovered local h = cx.active.current.hovered
if h and h.url == self.file.url then if h and h.url == self.file.url then
local step = math.floor(units * self.area.h / 10) 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
end end

View file

@ -2,10 +2,15 @@ local Folder_ = {}
function Folder_:peek() function Folder_:peek()
local folder = Folder:by_kind(Folder.PREVIEW) local folder = Folder:by_kind(Folder.PREVIEW)
if folder == nil then if folder == nil or folder.cwd ~= self.file.url then
return {} return {}
end 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 = {} local items = {}
for _, f in ipairs(folder.window) do for _, f in ipairs(folder.window) do
local item = ui.ListItem(ui.Line { Folder:icon(f), ui.Span(f.name) }) local item = ui.ListItem(ui.Line { Folder:icon(f), ui.Span(f.name) })
@ -16,11 +21,19 @@ function Folder_:peek()
end end
items[#items + 1] = item items[#items + 1] = item
end end
ya.preview_widgets(self.file, self.skip, { ui.List(self.area, items) }) ya.preview_widgets(self, { ui.List(self.area, items) })
end end
function Folder_:seek(units) 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 end
return Folder_ return Folder_

View file

@ -5,7 +5,7 @@ function Image:cache() return ya.cache_file(self.file.url .. tostring(self.file.
function Image:peek() function Image:peek()
local cache = self:cache() local cache = self:cache()
ya.image_show(fs.symlink_metadata(cache) and cache or self.file.url, self.area) 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 end
function Image:seek() end function Image:seek() end

View file

@ -28,10 +28,10 @@ function Json:peek()
child:start_kill() child:start_kill()
if self.skip > 0 and i < self.skip + limit then 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 else
lines = lines:gsub("\t", string.rep(" ", PREVIEW.tab_size)) 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
end end
@ -39,7 +39,10 @@ function Json:seek(units)
local h = cx.active.current.hovered local h = cx.active.current.hovered
if h and h.url == self.file.url then if h and h.url == self.file.url then
local step = math.floor(units * self.area.h / 10) 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
end end

View file

@ -0,0 +1,9 @@
local Noop = {}
function Noop:peek() end
function Noop:seek() end
function Noop:preload() return 1 end
return Noop

View file

@ -5,7 +5,7 @@ function Pdf:cache() return ya.cache_file(self.file.url .. self.skip .. tostring
function Pdf:peek() function Pdf:peek()
if self:preload() == 1 then if self:preload() == 1 then
ya.image_show(self:cache(), self.area) ya.image_show(self:cache(), self.area)
ya.preview_widgets(self.file, self.skip, {}) ya.preview_widgets(self, {})
end end
end end
@ -13,7 +13,7 @@ function Pdf:seek(units)
local h = cx.active.current.hovered local h = cx.active.current.hovered
if h and h.url == self.file.url then if h and h.url == self.file.url then
local step = ya.clamp(-1, units, 1) 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
end end
@ -32,7 +32,7 @@ function Pdf:preload()
if not output.status:success() then if not output.status:success() then
local pages = tonumber(output.stderr:match("the last page %((%d+)%)")) or 0 local pages = tonumber(output.stderr:match("the last page %((%d+)%)")) or 0
if self.skip > 0 and pages > 0 then 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 end
return 0 return 0
end end

View file

@ -5,21 +5,24 @@ function Video:cache() return ya.cache_file(self.file.url .. self.skip .. tostri
function Video:peek() function Video:peek()
if self:preload() == 1 then if self:preload() == 1 then
ya.image_show(self:cache(), self.area) ya.image_show(self:cache(), self.area)
ya.preview_widgets(self.file, self.skip, {}) ya.preview_widgets(self, {})
end end
end end
function Video:seek(units) function Video:seek(units)
local h = cx.active.current.hovered local h = cx.active.current.hovered
if h and h.url == self.file.url then 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
end end
function Video:preload() function Video:preload()
local percentage = 5 + self.skip local percentage = 5 + self.skip
if percentage > 95 then if percentage > 95 then
ya.manager_emit("peek", { "90" }) ya.manager_emit("peek", { "90", only_if = tostring(self.file.url), upper_bound = "" })
return 2 return 2
end end

View file

@ -5,12 +5,14 @@ mod cha;
mod file; mod file;
mod range; mod range;
mod url; mod url;
mod window;
pub use bindings::*; pub use bindings::*;
pub use cha::*; pub use cha::*;
pub use file::*; pub use file::*;
pub use range::*; pub use range::*;
pub use url::*; pub use url::*;
pub use window::*;
pub trait Cast<T> { pub trait Cast<T> {
fn cast(lua: &mlua::Lua, data: T) -> mlua::Result<mlua::AnyUserData>; fn cast(lua: &mlua::Lua, data: T) -> mlua::Result<mlua::AnyUserData>;

View file

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

View file

@ -6,7 +6,7 @@ use yazi_config::LAYOUT;
use yazi_shared::{emit, event::Exec, Layer}; use yazi_shared::{emit, event::Exec, Layer};
use super::slim_lua; 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 { pub fn peek(exec: &Exec, file: yazi_shared::fs::File, skip: usize) -> CancellationToken {
let ct = CancellationToken::new(); 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("file", File::cast(&lua, file)?)?;
plugin.set("skip", skip)?; plugin.set("skip", skip)?;
plugin.set("area", Rect::cast(&lua, LAYOUT.load().preview)?)?; 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 } 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("file", File::cast(&LUA, file)?)?;
plugin.set("skip", skip)?; plugin.set("skip", skip)?;
plugin.set("area", Rect::cast(&LUA, LAYOUT.load().preview)?)?; plugin.set("area", Rect::cast(&LUA, LAYOUT.load().preview)?)?;
plugin.set("window", Window::default())?;
plugin.call_method("peek", ()) plugin.call_method("peek", ())
})), })),
tx: None, tx: None,

View file

@ -5,10 +5,13 @@ use yazi_config::LAYOUT;
use super::slim_lua; use super::slim_lua;
use crate::{bindings::{Cast, File}, elements::Rect, LOADED}; use crate::{bindings::{Cast, File}, elements::Rect, LOADED};
pub async fn preload(name: String, files: Vec<yazi_shared::fs::File>) -> mlua::Result<u8> { pub async fn preload(
name: String,
files: Vec<yazi_shared::fs::File>,
multi: bool,
) -> mlua::Result<u8> {
LOADED.ensure(&name).await.into_lua_err()?; LOADED.ensure(&name).await.into_lua_err()?;
let multi = files.len() > 1;
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let lua = slim_lua()?; let lua = slim_lua()?;
let plugin: Table = if let Some(b) = LOADED.read().get(&name) { let plugin: Table = if let Some(b) = LOADED.read().get(&name) {

View file

@ -24,7 +24,7 @@ impl Loader {
let b = fs::read(BOOT.plugin_dir.join(name)).await.map(|v| v.into()).unwrap_or(Cow::from( let b = fs::read(BOOT.plugin_dir.join(name)).await.map(|v| v.into()).unwrap_or(Cow::from(
match name { match name {
"noop.lua" => &[] as &[u8], "noop.lua" => include_bytes!("../preset/plugins/noop.lua") as &[u8],
"archive.lua" => include_bytes!("../preset/plugins/archive.lua"), "archive.lua" => include_bytes!("../preset/plugins/archive.lua"),
"code.lua" => include_bytes!("../preset/plugins/code.lua"), "code.lua" => include_bytes!("../preset/plugins/code.lua"),
"folder.lua" => include_bytes!("../preset/plugins/folder.lua"), "folder.lua" => include_bytes!("../preset/plugins/folder.lua"),

View file

@ -20,7 +20,7 @@ impl Utils {
args.push(v.to_str()?.to_owned()); args.push(v.to_str()?.to_owned());
} }
Value::String(s) => { 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()), _ => return Err("invalid key in exec".into_lua_err()),
} }

View file

@ -4,87 +4,89 @@ use yazi_config::PREVIEW;
use yazi_shared::{emit, event::Exec, Layer, PeekError}; use yazi_shared::{emit, event::Exec, Layer, PeekError};
use super::Utils; 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 struct PreviewLock {
pub url: yazi_shared::fs::Url, pub url: yazi_shared::fs::Url,
pub cha: yazi_shared::fs::Cha, pub cha: yazi_shared::fs::Cha,
pub skip: usize, pub skip: usize,
pub data: Vec<Box<dyn Renderable + Send>>, pub window: Window,
pub data: Vec<Box<dyn Renderable + Send>>,
}
impl<'a> TryFrom<Table<'a>> for PreviewLock {
type Error = mlua::Error;
fn try_from(t: Table) -> Result<Self, Self::Error> {
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 { impl Utils {
pub(super) fn preview(lua: &Lua, ya: &Table) -> mlua::Result<()> { pub(super) fn preview(lua: &Lua, ya: &Table) -> mlua::Result<()> {
ya.set( ya.set(
"preview_code", "preview_code",
lua.create_async_function( lua.create_async_function(|lua, t: Table| async move {
|lua, (area, file, skip): (RectRef, FileRef, usize)| async move { let area: RectRef = t.get("area")?;
let s = match Highlighter::new(&file.url).highlight(skip, area.height as usize).await { let mut lock = PreviewLock::try_from(t)?;
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),
};
let lock = PreviewLock { let s = match Highlighter::new(&lock.url).highlight(lock.skip, area.height as usize).await {
url: file.url(), Ok(s) => s.replace('\t', &" ".repeat(PREVIEW.tab_size as usize)),
cha: file.cha, Err(PeekError::Exceed(max)) => return (false, max).into_lua_multi(lua),
skip, Err(_) => return (false, Value::Nil).into_lua_multi(lua),
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)); lock.data = vec![Box::new(Paragraph {
(true, Value::Nil).into_lua_multi(lua) 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( ya.set(
"preview_archive", "preview_archive",
lua.create_async_function( lua.create_async_function(|lua, t: Table| async move {
|lua, (area, file, skip): (RectRef, FileRef, usize)| async move { let area: RectRef = t.get("area")?;
let lines: Vec<_> = match external::lsar(&file.url, skip, area.height as usize).await { let mut lock = PreviewLock::try_from(t)?;
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),
};
let lock = PreviewLock { let lines: Vec<_> = match external::lsar(&lock.url, lock.skip, area.height as usize).await {
url: file.url(), Ok(items) => items.into_iter().map(|f| ratatui::text::Line::from(f.name)).collect(),
cha: file.cha, Err(PeekError::Exceed(max)) => return (false, max).into_lua_multi(lua),
skip, Err(_) => return (false, Value::Nil).into_lua_multi(lua),
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)); lock.data = vec![Box::new(Paragraph {
(true, Value::Nil).into_lua_multi(lua) 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( ya.set(
"preview_widgets", "preview_widgets",
lua.create_async_function( lua.create_async_function(|_, (t, widgets): (Table, Vec<AnyUserData>)| async move {
|_, (file, skip, widgets): (FileRef, usize, Vec<AnyUserData>)| async move { let mut lock = PreviewLock::try_from(t)?;
let lock = PreviewLock { lock.data = widgets.into_iter().filter_map(cast_to_renderable).collect();
url: file.url(),
cha: file.cha, emit!(Call(Exec::call("preview", vec![]).with_data(lock).vec(), Layer::Manager));
skip, Ok(())
data: widgets.into_iter().filter_map(cast_to_renderable).collect(), })?,
};
emit!(Call(Exec::call("preview", vec![]).with_data(lock).vec(), Layer::Manager));
Ok(())
},
)?,
)?; )?;
Ok(()) Ok(())

View file

@ -308,11 +308,11 @@ impl Scheduler {
_ = self.todo.send_blocking({ _ = self.todo.send_blocking({
let preload = self.preload.clone(); 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 cmd = rule.exec.cmd.clone();
let targets = targets.into_iter().cloned().collect(); let targets = targets.into_iter().cloned().collect();
async move { 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() .boxed()
}); });

View file

@ -24,10 +24,11 @@ pub struct PreloadOpSize {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PreloadOpRule { pub struct PreloadOpRule {
pub id: usize, pub id: usize,
pub rule_id: u8, pub rule_id: u8,
pub plugin: String, pub rule_multi: bool,
pub targets: Vec<yazi_shared::fs::File>, pub plugin: String,
pub targets: Vec<yazi_shared::fs::File>,
} }
impl Preload { impl Preload {
@ -39,7 +40,7 @@ impl Preload {
self.sch.send(TaskOp::New(task.id, 0))?; self.sch.send(TaskOp::New(task.id, 0))?;
let urls: Vec<_> = task.targets.iter().map(|f| f.url()).collect(); 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 { if let Err(e) = result {
self.fail(task.id, format!("Preload task failed:\n{e}"))?; self.fail(task.id, format!("Preload task failed:\n{e}"))?;
return Err(e.into()); return Err(e.into());