use mlua::{ExternalError, HookTriggers, IntoLua, Lua, ObjectLike, Table, VmState}; use tokio::{runtime::Handle, select}; use tokio_util::sync::CancellationToken; use tracing::error; use yazi_binding::{Error, File, elements::{Rect, Renderable}}; use yazi_config::LAYOUT; use yazi_dds::Sendable; use yazi_parser::{app::PluginOpt, mgr::{PreviewLock, UpdatePeekedOpt}}; use yazi_proxy::{AppProxy, MgrProxy}; use yazi_shared::{event::Cmd, pool::Symbol}; use super::slim_lua; use crate::loader::{LOADER, Loader}; pub fn peek( cmd: &'static Cmd, file: yazi_fs::File, mime: Symbol, skip: usize, ) -> Option { let (id, ..) = Loader::normalize_id(&cmd.name).ok()?; let ct = CancellationToken::new(); if let Some(c) = LOADER.read().get(id) { if let Err(e) = Loader::compatible_or_error(id, c) { peek_error(file, mime, skip, e); return None; } else if c.sync_peek { peek_sync(cmd, file, mime, skip); } else { peek_async(cmd, file, mime, skip, ct.clone()); } return Some(ct).filter(|_| !c.sync_peek); } let ct_ = ct.clone(); tokio::spawn(async move { select! { _ = ct_.cancelled() => {}, Ok(b) = LOADER.ensure(id, |c| c.sync_peek) => { if b { peek_sync(cmd, file, mime, skip); } else { peek_async(cmd, file, mime, skip, ct_); } }, else => {} } }); Some(ct) } fn peek_sync(cmd: &'static Cmd, file: yazi_fs::File, mime: Symbol, skip: usize) { let cb = move |lua: &Lua, plugin: Table| { let job = lua.create_table_from([ ("area", Rect::from(LAYOUT.get().preview).into_lua(lua)?), ("args", Sendable::args_to_table_ref(lua, &cmd.args)?.into_lua(lua)?), ("file", File::new(file).into_lua(lua)?), ("mime", mime.into_lua(lua)?), ("skip", skip.into_lua(lua)?), ])?; plugin.call_method("peek", job) }; AppProxy::plugin(PluginOpt::new_callback(&*cmd.name, cb)); } fn peek_async( cmd: &'static Cmd, file: yazi_fs::File, mime: Symbol, skip: usize, ct: CancellationToken, ) { let ct_ = ct.clone(); tokio::task::spawn_blocking(move || { let future = async { let lua = slim_lua(&cmd.name)?; lua.set_hook( HookTriggers::new().on_calls().on_returns().every_nth_instruction(2000), move |_, dbg| { if ct.is_cancelled() && dbg.source().what != "C" { Err("Peek task cancelled".into_lua_err()) } else { Ok(VmState::Continue) } }, )?; let plugin = LOADER.load(&lua, &cmd.name).await?; let job = lua.create_table_from([ ("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?), ("args", Sendable::args_to_table_ref(&lua, &cmd.args)?.into_lua(&lua)?), ("file", File::new(file).into_lua(&lua)?), ("mime", mime.into_lua(&lua)?), ("skip", skip.into_lua(&lua)?), ])?; if ct_.is_cancelled() { Ok(()) } else { plugin.call_async_method("peek", job).await } }; let result = Handle::current().block_on(async { select! { _ = ct_.cancelled() => Ok(()), r = future => r, } }); if let Err(e) = result && !e.to_string().contains("Peek task cancelled") { error!("{e}"); } }); } fn peek_error(file: yazi_fs::File, mime: Symbol, skip: usize, error: anyhow::Error) { let area = LAYOUT.get().preview; MgrProxy::update_peeked(UpdatePeekedOpt { lock: PreviewLock { url: file.url, cha: file.cha, mime: mime.to_string(), skip, area: area.into(), data: vec![ Renderable::Clear(yazi_binding::elements::Clear { area: area.into() }), Renderable::from(Error::custom(error.to_string())).with_area(area), ], }, }); }