feat!: deprecate --sync option for the plugin command (#1891)

This commit is contained in:
三咲雅 · Misaki Masa 2024-11-07 23:08:39 +08:00 committed by GitHub
parent 6001f3c619
commit baf062f3b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 258 additions and 156 deletions

View file

@ -4,56 +4,63 @@ use mlua::TableExt;
use scopeguard::defer;
use tracing::warn;
use yazi_dds::Sendable;
use yazi_macro::emit;
use yazi_plugin::{LUA, RtRef, loader::LOADER};
use yazi_shared::{Layer, event::Cmd};
use yazi_proxy::{AppProxy, options::{PluginMode, PluginOpt}};
use crate::{app::App, lives::Lives};
impl App {
pub(crate) fn plugin(&mut self, opt: impl TryInto<yazi_plugin::Opt, Error = impl Display>) {
let opt = match opt.try_into() {
Ok(opt) => opt as yazi_plugin::Opt,
pub(crate) fn plugin(&mut self, opt: impl TryInto<PluginOpt, Error = impl Display>) {
let mut opt = match opt.try_into() {
Ok(opt) => opt as PluginOpt,
Err(e) => return warn!("{e}"),
};
if !opt.sync {
return self.cx.tasks.plugin_micro(opt.id, opt.args);
let mut hits = false;
if let Some(chunk) = LOADER.read().get(&opt.id) {
hits = true;
opt.mode = opt.mode.auto_then(chunk.sync_entry);
}
if LOADER.read().contains_key(&opt.id) {
if opt.mode == PluginMode::Async {
return self.cx.tasks.plugin_micro(opt.id, opt.args);
} else if opt.mode == PluginMode::Sync && hits {
return self.plugin_do(opt);
}
tokio::spawn(async move {
if LOADER.ensure(&opt.id).await.is_ok() {
Self::_plugin_do(opt);
AppProxy::plugin_do(opt);
}
});
}
#[inline]
pub(crate) fn _plugin_do(opt: yazi_plugin::Opt) {
let cmd: Cmd = opt.into();
emit!(Call(cmd.with_name("plugin_do"), Layer::App));
}
pub(crate) fn plugin_do(&mut self, opt: impl TryInto<yazi_plugin::Opt, Error = impl Display>) {
pub(crate) fn plugin_do(&mut self, opt: impl TryInto<PluginOpt, Error = impl Display>) {
let opt = match opt.try_into() {
Ok(opt) => opt as yazi_plugin::Opt,
Ok(opt) => opt as PluginOpt,
Err(e) => return warn!("{e}"),
};
let loader = LOADER.read();
let Some(chunk) = loader.get(&opt.id) else {
return warn!("plugin `{}` not found", opt.id);
};
if opt.mode.auto_then(chunk.sync_entry) != PluginMode::Sync {
return self.cx.tasks.plugin_micro(opt.id, opt.args);
}
match LUA.named_registry_value::<RtRef>("rt") {
Ok(mut r) => r.push(&opt.id),
Err(e) => return warn!("{e}"),
}
defer! { _ = LUA.named_registry_value::<RtRef>("rt").map(|mut r| r.pop()) }
let plugin = match LOADER.load(&LUA, &opt.id) {
let plugin = match LOADER.load_with(&LUA, &opt.id, chunk) {
Ok(plugin) => plugin,
Err(e) => return warn!("{e}"),
};
drop(loader);
_ = Lives::scope(&self.cx, |_| {
if let Some(cb) = opt.cb {

View file

@ -141,13 +141,13 @@ impl<'a> Executor<'a> {
on!(TABS, switch);
on!(TABS, swap);
match cmd.name.as_bytes() {
match cmd.name.as_str() {
// Tasks
b"tasks_show" => self.app.cx.tasks.toggle(()),
"tasks_show" => self.app.cx.tasks.toggle(()),
// Help
b"help" => self.app.cx.help.toggle(Layer::Manager),
"help" => self.app.cx.help.toggle(Layer::Manager),
// Plugin
b"plugin" => self.app.plugin(cmd),
"plugin" => self.app.plugin(cmd),
_ => {}
}
}

View file

@ -35,7 +35,7 @@ impl Highlighter {
};
let (theme, syntaxes) = SYNTECT.get_or_init(|| fut).await;
(&theme, &syntaxes)
(theme, syntaxes)
}
#[inline]

View file

@ -12,7 +12,7 @@ pub async fn entry(name: String, args: Vec<Data>) -> mlua::Result<()> {
tokio::task::spawn_blocking(move || {
let lua = slim_lua(&name)?;
let plugin: Table = if let Some(b) = LOADER.read().get(&name) {
lua.load(b.as_ref()).set_name(name).call(())?
lua.load(b.as_bytes()).set_name(name).call(())?
} else {
return Err("unloaded plugin".into_lua_err());
};

View file

@ -15,7 +15,7 @@ pub async fn fetch(name: &str, files: Vec<yazi_shared::fs::File>) -> mlua::Resul
tokio::task::spawn_blocking(move || {
let lua = slim_lua(&name)?;
let plugin: Table = if let Some(b) = LOADER.read().get(&name) {
lua.load(b.as_ref()).set_name(name).call(())?
lua.load(b.as_bytes()).set_name(name).call(())?
} else {
return Err("unloaded plugin".into_lua_err());
};

View file

@ -5,11 +5,11 @@ use tokio::{runtime::Handle, select};
use tokio_util::sync::CancellationToken;
use tracing::error;
use yazi_config::LAYOUT;
use yazi_macro::emit;
use yazi_shared::{Layer, event::Cmd};
use yazi_proxy::{AppProxy, options::{PluginCallback, PluginOpt}};
use yazi_shared::event::Cmd;
use super::slim_lua;
use crate::{LUA, Opt, OptCallback, bindings::{Cast, Window}, elements::Rect, file::File, loader::LOADER};
use crate::{LUA, bindings::{Cast, Window}, elements::Rect, file::File, loader::LOADER};
pub fn peek(
cmd: &Cmd,
@ -34,7 +34,7 @@ pub fn peek(
);
let plugin: Table = if let Some(b) = LOADER.read().get(&name) {
lua.load(b.as_ref()).set_name(name).call(())?
lua.load(b.as_bytes()).set_name(name).call(())?
} else {
return Err("unloaded plugin".into_lua_err());
};
@ -65,7 +65,7 @@ pub fn peek(
}
pub fn peek_sync(cmd: &Cmd, file: yazi_shared::fs::File, mime: Cow<'static, str>, skip: usize) {
let cb: OptCallback = Box::new(move |_, plugin| {
let cb: PluginCallback = Box::new(move |_, plugin| {
plugin.raw_set("file", File::cast(&LUA, file)?)?;
plugin.raw_set("_mime", mime)?;
plugin.raw_set("skip", skip)?;
@ -74,8 +74,5 @@ pub fn peek_sync(cmd: &Cmd, file: yazi_shared::fs::File, mime: Cow<'static, str>
plugin.call_method("peek", ())
});
let cmd: Cmd =
Opt { id: cmd.name.to_owned(), sync: true, cb: Some(cb), ..Default::default() }.into();
emit!(Call(cmd.with_name("plugin"), Layer::App));
AppProxy::plugin(PluginOpt::new_callback(&cmd.name, cb));
}

View file

@ -12,7 +12,7 @@ pub async fn preload(name: &str, file: yazi_shared::fs::File) -> mlua::Result<u8
tokio::task::spawn_blocking(move || {
let lua = slim_lua(&name)?;
let plugin: Table = if let Some(b) = LOADER.read().get(&name) {
lua.load(b.as_ref()).set_name(name).call(())?
lua.load(b.as_bytes()).set_name(name).call(())?
} else {
return Err("unloaded plugin".into_lua_err());
};

View file

@ -1,19 +1,16 @@
use mlua::TableExt;
use yazi_config::LAYOUT;
use yazi_macro::emit;
use yazi_shared::{Layer, event::Cmd};
use yazi_proxy::{AppProxy, options::{PluginCallback, PluginOpt}};
use yazi_shared::event::Cmd;
use crate::{LUA, Opt, OptCallback, bindings::Cast, elements::Rect, file::File};
use crate::{LUA, bindings::Cast, elements::Rect, file::File};
pub fn seek_sync(cmd: &Cmd, file: yazi_shared::fs::File, units: i16) {
let cb: OptCallback = Box::new(move |_, plugin| {
let cb: PluginCallback = Box::new(move |_, plugin| {
plugin.raw_set("file", File::cast(&LUA, file)?)?;
plugin.raw_set("area", Rect::from(LAYOUT.get().preview))?;
plugin.call_method("seek", units)
});
let cmd: Cmd =
Opt { id: cmd.name.to_owned(), sync: true, cb: Some(cb), ..Default::default() }.into();
emit!(Call(cmd.with_name("plugin"), Layer::App));
AppProxy::plugin(PluginOpt::new_callback(&cmd.name, cb));
}

View file

@ -6,7 +6,7 @@ yazi_macro::mod_pub!(
bindings, cha, elements, external, file, fs, isolate, loader, process, pubsub, url, utils
);
yazi_macro::mod_flat!(cast clipboard config lua opt runtime);
yazi_macro::mod_flat!(cast clipboard config lua runtime);
pub fn init() -> anyhow::Result<()> {
CLIPBOARD.with(<_>::default);

View file

@ -0,0 +1,43 @@
use std::borrow::Cow;
pub struct Chunk {
pub bytes: Cow<'static, [u8]>,
pub sync_entry: bool,
}
impl Chunk {
#[inline]
pub fn as_bytes(&self) -> &[u8] { &self.bytes }
fn analyze(&mut self) {
for line in self.bytes.split(|&b| b == b'\n') {
let Some(rest) = line.strip_prefix(b"---") else { break };
let rest = rest.trim_ascii();
let Some(pos) = rest.iter().position(|&b| b == b' ' || b == b'\t') else { break };
match (rest[..pos].trim_ascii(), rest[pos..].trim_ascii()) {
(b"@sync", b"entry") => self.sync_entry = true,
(_, []) => break,
(b, _) if b.strip_prefix(b"@").unwrap_or(b"").is_empty() => break,
_ => continue,
}
}
}
}
impl From<Cow<'static, [u8]>> for Chunk {
fn from(b: Cow<'static, [u8]>) -> Self {
let mut chunk = Self { bytes: b, sync_entry: false };
chunk.analyze();
chunk
}
}
impl From<&'static [u8]> for Chunk {
fn from(b: &'static [u8]) -> Self { Self::from(Cow::Borrowed(b)) }
}
impl From<Vec<u8>> for Chunk {
fn from(b: Vec<u8>) -> Self { Self::from(Cow::Owned(b)) }
}

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, collections::HashMap, ops::Deref};
use std::{collections::HashMap, ops::Deref};
use anyhow::{Context, Result};
use mlua::{ExternalError, Lua, Table};
@ -8,11 +8,45 @@ use yazi_boot::BOOT;
use yazi_macro::plugin_preset as preset;
use yazi_shared::RoCell;
use super::Chunk;
pub static LOADER: RoCell<Loader> = RoCell::new();
#[derive(Default)]
pub struct Loader {
cache: RwLock<HashMap<String, Cow<'static, [u8]>>>,
cache: RwLock<HashMap<String, Chunk>>,
}
impl Deref for Loader {
type Target = RwLock<HashMap<String, Chunk>>;
#[inline]
fn deref(&self) -> &Self::Target { &self.cache }
}
impl Default for Loader {
fn default() -> Self {
let cache = HashMap::from_iter([
("archive".to_owned(), preset!("plugins/archive").into()),
("code".to_owned(), preset!("plugins/code").into()),
("dds".to_owned(), preset!("plugins/dds").into()),
("empty".to_owned(), preset!("plugins/empty").into()),
("extract".to_owned(), preset!("plugins/extract").into()),
("file".to_owned(), preset!("plugins/file").into()),
("folder".to_owned(), preset!("plugins/folder").into()),
("font".to_owned(), preset!("plugins/font").into()),
("fzf".to_owned(), preset!("plugins/fzf").into()),
("image".to_owned(), preset!("plugins/image").into()),
("json".to_owned(), preset!("plugins/json").into()),
("magick".to_owned(), preset!("plugins/magick").into()),
("mime".to_owned(), preset!("plugins/mime").into()),
("noop".to_owned(), preset!("plugins/noop").into()),
("pdf".to_owned(), preset!("plugins/pdf").into()),
("session".to_owned(), preset!("plugins/session").into()),
("video".to_owned(), preset!("plugins/video").into()),
("zoxide".to_owned(), preset!("plugins/zoxide").into()),
]);
Self { cache: RwLock::new(cache) }
}
}
impl Loader {
@ -21,36 +55,11 @@ impl Loader {
return Ok(());
}
let preset = match name {
"archive" => preset!("plugins/archive"),
"code" => preset!("plugins/code"),
"dds" => preset!("plugins/dds"),
"empty" => preset!("plugins/empty"),
"extract" => preset!("plugins/extract"),
"file" => preset!("plugins/file"),
"folder" => preset!("plugins/folder"),
"font" => preset!("plugins/font"),
"fzf" => preset!("plugins/fzf"),
"image" => preset!("plugins/image"),
"json" => preset!("plugins/json"),
"magick" => preset!("plugins/magick"),
"mime" => preset!("plugins/mime"),
"noop" => preset!("plugins/noop"),
"pdf" => preset!("plugins/pdf"),
"session" => preset!("plugins/session"),
"video" => preset!("plugins/video"),
"zoxide" => preset!("plugins/zoxide"),
_ => Default::default(),
};
let p = BOOT.plugin_dir.join(format!("{name}.yazi/init.lua"));
let chunk =
fs::read(&p).await.with_context(|| format!("failed to load plugin from {p:?}"))?.into();
let b = if preset.is_empty() {
let p = BOOT.plugin_dir.join(format!("{name}.yazi/init.lua"));
Cow::Owned(fs::read(&p).await.with_context(|| format!("failed to load plugin from {p:?}"))?)
} else {
preset.into()
};
self.cache.write().insert(name.to_owned(), b);
self.cache.write().insert(name.to_owned(), chunk);
Ok(())
}
@ -61,7 +70,7 @@ impl Loader {
}
let t: Table = match self.read().get(id) {
Some(b) => lua.load(b.as_ref()).set_name(id).call(())?,
Some(c) => lua.load(c.as_bytes()).set_name(id).call(())?,
None => Err(format!("plugin `{id}` not found").into_lua_err())?,
};
@ -69,11 +78,22 @@ impl Loader {
loaded.raw_set(id, t.clone())?;
Ok(t)
}
}
impl Deref for Loader {
type Target = RwLock<HashMap<String, Cow<'static, [u8]>>>;
pub fn try_load<'a>(&self, lua: &'a Lua, id: &str) -> mlua::Result<Table<'a>> {
let loaded: Table = lua.globals().raw_get::<_, Table>("package")?.raw_get("loaded")?;
loaded.raw_get(id)
}
#[inline]
fn deref(&self) -> &Self::Target { &self.cache }
pub fn load_with<'a>(&self, lua: &'a Lua, id: &str, chunk: &Chunk) -> mlua::Result<Table<'a>> {
let loaded: Table = lua.globals().raw_get::<_, Table>("package")?.raw_get("loaded")?;
if let Ok(t) = loaded.raw_get::<_, Table>(id) {
return Ok(t);
}
let t: Table = lua.load(chunk.as_bytes()).set_name(id).call(())?;
t.raw_set("_id", lua.create_string(id)?)?;
loaded.raw_set(id, t.clone())?;
Ok(t)
}
}

View file

@ -1,6 +1,6 @@
#![allow(clippy::module_inception)]
yazi_macro::mod_flat!(loader require);
yazi_macro::mod_flat!(chunk loader require);
pub(super) fn init() { LOADER.with(<_>::default); }

View file

@ -103,18 +103,18 @@ impl Require {
mut args: MultiValue<'a>,
) -> mlua::Result<(Table<'a>, MultiValue<'a>)> {
let Some(front) = args.pop_front() else {
return Ok((LOADER.load(lua, id)?, args));
return Ok((LOADER.try_load(lua, id)?, args));
};
let Value::Table(tbl) = front else {
args.push_front(front);
return Ok((LOADER.load(lua, id)?, args));
return Ok((LOADER.try_load(lua, id)?, args));
};
Ok(if let Ok(mod_) = tbl.raw_get::<_, Table>("__mod") {
args.push_front(Value::Table(mod_.clone()));
(mod_, args)
} else {
args.push_front(Value::Table(tbl));
(LOADER.load(lua, id)?, args)
(LOADER.try_load(lua, id)?, args)
})
}
}

View file

@ -1,43 +0,0 @@
use anyhow::bail;
use mlua::{Lua, Table};
use yazi_shared::event::{Cmd, Data};
pub(super) type OptCallback = Box<dyn FnOnce(&Lua, Table) -> mlua::Result<()> + Send>;
#[derive(Default)]
pub struct Opt {
pub id: String,
pub sync: bool,
pub args: Vec<Data>,
pub cb: Option<OptCallback>,
}
impl TryFrom<Cmd> for Opt {
type Error = anyhow::Error;
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
let Some(id) = c.take_first_str().filter(|s| !s.is_empty()) else {
bail!("plugin id cannot be empty");
};
let args = if let Some(s) = c.str("args") {
shell_words::split(s)?.into_iter().map(Data::String).collect()
} else {
c.take_any::<Vec<Data>>("args").unwrap_or_default()
};
Ok(Self { id, sync: c.bool("sync"), args, cb: c.take_any("callback") })
}
}
impl From<Opt> for Cmd {
fn from(value: Opt) -> Self {
let mut cmd =
Cmd::args("", &[value.id]).with_bool("sync", value.sync).with_any("args", value.args);
if let Some(cb) = value.cb {
cmd = cmd.with_any("callback", cb);
}
cmd
}
}

View file

@ -1,11 +1,11 @@
use mlua::{ExternalError, ExternalResult, Function, Lua, MultiValue, Table, Value};
use tokio::sync::oneshot;
use yazi_dds::Sendable;
use yazi_macro::emit;
use yazi_shared::{Layer, event::{Cmd, Data}};
use yazi_proxy::{AppProxy, options::{PluginCallback, PluginOpt}};
use yazi_shared::event::Data;
use super::Utils;
use crate::{OptCallback, loader::LOADER, runtime::RtRef};
use crate::{loader::LOADER, runtime::RtRef};
impl Utils {
pub(super) fn sync(lua: &'static Lua, ya: &Table) -> mlua::Result<()> {
@ -19,7 +19,7 @@ impl Utils {
let cur = rt.current().unwrap().to_owned();
lua.create_function(move |lua, mut args: MultiValue| {
args.push_front(Value::Table(LOADER.load(lua, &cur)?));
args.push_front(Value::Table(LOADER.try_load(lua, &cur)?));
f.call::<_, MultiValue>(args)
})
})?,
@ -49,14 +49,14 @@ impl Utils {
Ok(())
}
async fn retrieve(name: &str, calls: usize, args: MultiValue<'_>) -> mlua::Result<Vec<Data>> {
async fn retrieve(id: &str, calls: usize, args: MultiValue<'_>) -> mlua::Result<Vec<Data>> {
let args = Sendable::values_to_vec(args)?;
let (tx, rx) = oneshot::channel::<Vec<Data>>();
let callback: OptCallback = {
let name = name.to_owned();
let callback: PluginCallback = {
let id = id.to_owned();
Box::new(move |lua, plugin| {
let Some(block) = lua.named_registry_value::<RtRef>("rt")?.get_block(&name, calls) else {
let Some(block) = lua.named_registry_value::<RtRef>("rt")?.get_block(&id, calls) else {
return Err("sync block not found".into_lua_err());
};
@ -70,13 +70,9 @@ impl Utils {
})
};
emit!(Call(
Cmd::args("plugin", &[name]).with_bool("sync", true).with_any("callback", callback),
Layer::App
));
AppProxy::plugin(PluginOpt::new_callback(id, callback));
rx.await.map_err(|_| {
format!("Failed to execute sync block-{calls} in `{name}` plugin").into_lua_err()
})
rx.await
.map_err(|_| format!("Failed to execute sync block-{calls} in `{id}` plugin").into_lua_err())
}
}

View file

@ -4,7 +4,7 @@ use tokio::sync::oneshot;
use yazi_macro::emit;
use yazi_shared::{Layer, event::Cmd};
use crate::options::{NotifyLevel, NotifyOpt};
use crate::options::{NotifyLevel, NotifyOpt, PluginOpt};
pub struct AppProxy;
@ -45,4 +45,14 @@ impl AppProxy {
timeout: Duration::from_secs(10),
});
}
#[inline]
pub fn plugin(opt: PluginOpt) {
emit!(Call(Cmd::new("plugin").with_any("opt", opt), Layer::App));
}
#[inline]
pub fn plugin_do(opt: PluginOpt) {
emit!(Call(Cmd::new("plugin_do").with_any("opt", opt), Layer::App));
}
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(notify open process search);
yazi_macro::mod_flat!(notify open plugin process search);

View file

@ -0,0 +1,83 @@
use anyhow::bail;
use mlua::{Lua, Table};
use yazi_shared::event::{Cmd, Data};
pub type PluginCallback = Box<dyn FnOnce(&Lua, Table) -> mlua::Result<()> + Send>;
#[derive(Default)]
pub struct PluginOpt {
pub id: String,
pub mode: PluginMode,
pub args: Vec<Data>,
pub cb: Option<PluginCallback>,
}
impl TryFrom<Cmd> for PluginOpt {
type Error = anyhow::Error;
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
if let Some(opt) = c.take_any("opt") {
return Ok(opt);
}
let Some(id) = c.take_first_str().filter(|s| !s.is_empty()) else {
bail!("plugin id cannot be empty");
};
let args = if let Some(s) = c.str("args") {
shell_words::split(s)?.into_iter().map(Data::String).collect()
} else {
vec![]
};
let mut mode = c.str("mode").map(Into::into).unwrap_or_default();
if c.bool("sync") {
mode = PluginMode::Sync;
let s = "The `--sync` option for the `plugin` command has been deprecated in Yazi v0.4.
Please add `--- @sync entry` metadata at the head of your `{id}` plugin instead. See #1891 for details: https://github.com/sxyazi/yazi/pull/1891";
crate::AppProxy::notify(crate::options::NotifyOpt {
title: "Deprecated API".to_owned(),
content: s.replace("{id}", &id),
level: crate::options::NotifyLevel::Warn,
timeout: std::time::Duration::from_secs(20),
});
}
Ok(Self { id, mode, args, cb: c.take_any("callback") })
}
}
impl PluginOpt {
pub fn new_callback(id: &str, cb: PluginCallback) -> Self {
Self { id: id.to_owned(), mode: PluginMode::Sync, cb: Some(cb), ..Default::default() }
}
}
// --- Mode
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum PluginMode {
#[default]
Auto,
Sync,
Async,
}
impl From<&str> for PluginMode {
fn from(s: &str) -> Self {
match s {
"sync" => Self::Sync,
"async" => Self::Async,
_ => Self::Auto,
}
}
}
impl PluginMode {
pub fn auto_then(self, sync: bool) -> Self {
if self != Self::Auto {
return self;
}
if sync { Self::Sync } else { Self::Async }
}
}

View file

@ -32,7 +32,7 @@ impl Plugin {
if let Err(e) = isolate::entry(task.name, task.args).await {
self.fail(task.id, format!("Micro plugin failed:\n{e}"))?;
return Err(e.into());
return Ok(());
}
self.prog.send(TaskProg::Adv(task.id, 1, 0))?;

View file

@ -36,9 +36,7 @@ pub fn replace_to_printable(s: &[String], tab_size: u8) -> String {
match b {
b'\n' => buf.push(b'\n'),
b'\t' => {
for _ in 0..tab_size {
buf.push(b' ');
}
buf.extend((0..tab_size).map(|_| b' '));
}
b'\0'..=b'\x1F' => {
buf.push(b'^');

View file

@ -46,12 +46,6 @@ impl Cmd {
self
}
#[inline]
pub fn with_name(mut self, name: impl ToString) -> Self {
self.name = name.to_string();
self
}
// --- Get
#[inline]
pub fn get(&self, name: &str) -> Option<&Data> { self.args.get(name) }