yazi/yazi-plugin/src/isolate/peek.rs

134 lines
3.5 KiB
Rust

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<str>,
skip: usize,
) -> Option<CancellationToken> {
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<str>, 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<str>,
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<str>, 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),
],
},
});
}