From dfdb235d74e096902f496b4300d4ad134e775ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20misaki=20masa?= Date: Fri, 30 Jan 2026 10:12:21 +0800 Subject: [PATCH] feat: allow using `ps.sub()` in `init.lua` directly without a plugin (#3638) --- CHANGELOG.md | 2 ++ Cargo.lock | 1 + scripts/validate-form/main.js | 4 +-- yazi-actor/src/app/accept_payload.rs | 2 +- yazi-actor/src/core/preflight.rs | 2 +- yazi-binding/Cargo.toml | 1 + yazi-binding/src/runtime.rs | 21 ++++++++------ yazi-plugin/preset/plugins/init.lua | 0 yazi-plugin/src/loader/loader.rs | 1 + yazi-plugin/src/loader/require.rs | 13 ++++----- yazi-plugin/src/lua.rs | 3 +- yazi-plugin/src/pubsub/pubsub.rs | 24 ++++------------ yazi-plugin/src/utils/sync.rs | 43 +++++++++++----------------- 13 files changed, 52 insertions(+), 65 deletions(-) create mode 100644 yazi-plugin/preset/plugins/init.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index d39139de..0bf6a4a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/): ### Added +- Allow using `ps.sub()` in `init.lua` directly without a plugin ([#3638]) - New `cx.which` API to access the which component state ([#3617]) - New `ind-which-activate` DDS event to change the which component behavior ([#3608]) @@ -1634,3 +1635,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/): [#3617]: https://github.com/sxyazi/yazi/pull/3617 [#3633]: https://github.com/sxyazi/yazi/pull/3633 [#3634]: https://github.com/sxyazi/yazi/pull/3634 +[#3638]: https://github.com/sxyazi/yazi/pull/3638 diff --git a/Cargo.lock b/Cargo.lock index ca083519..8bfbf28f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5596,6 +5596,7 @@ name = "yazi-binding" version = "26.1.22" dependencies = [ "ansi-to-tui", + "anyhow", "crossterm 0.29.0", "futures", "hashbrown 0.16.1", diff --git a/scripts/validate-form/main.js b/scripts/validate-form/main.js index 5b231099..925b4e16 100644 --- a/scripts/validate-form/main.js +++ b/scripts/validate-form/main.js @@ -18,7 +18,7 @@ I noticed that you did not correctly follow the issue template. Please ensure th Issues with \`${LABEL_NAME}\` will be marked ready once edited with the proper content, or closed after 2 days of inactivity. -Our maintainers work on Yazi in their free time, this helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thanks for your understanding! +Our maintainers work on Yazi in their free time, this helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thanks for your understanding! 🙏 ` } @@ -37,7 +37,7 @@ I noticed that you did not correctly follow the issue template. Please ensure th Issues with \`${LABEL_NAME}\` will be marked ready once edited with the proper content, or closed after 2 days of inactivity. -Our maintainers work on Yazi in their free time, this helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thanks for your understanding! +Our maintainers work on Yazi in their free time, this helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thanks for your understanding! 🙏 ` } diff --git a/yazi-actor/src/app/accept_payload.rs b/yazi-actor/src/app/accept_payload.rs index b1a97d4c..2f548ec0 100644 --- a/yazi-actor/src/app/accept_payload.rs +++ b/yazi-actor/src/app/accept_payload.rs @@ -35,7 +35,7 @@ impl Actor for AcceptPayload { if let Err(e) = cb.call::<()>(body.clone()) { error!("Failed to run `{kind}` event handler in your `{id}` plugin: {e}"); } - runtime_mut!(LUA)?.critical_pop(blocking); + runtime_mut!(LUA)?.critical_pop(blocking)?; } Ok(()) })?); diff --git a/yazi-actor/src/core/preflight.rs b/yazi-actor/src/core/preflight.rs index 30564ba2..843941b2 100644 --- a/yazi-actor/src/core/preflight.rs +++ b/yazi-actor/src/core/preflight.rs @@ -20,7 +20,7 @@ impl Preflight { for (id, cb) in handlers { let blocking = runtime_mut!(LUA)?.critical_push(&id, true); let result = cb.call::(&body); - runtime_mut!(LUA)?.critical_pop(blocking); + runtime_mut!(LUA)?.critical_pop(blocking)?; match result { Ok(Value::Nil) => { diff --git a/yazi-binding/Cargo.toml b/yazi-binding/Cargo.toml index 6b701db1..4df43500 100644 --- a/yazi-binding/Cargo.toml +++ b/yazi-binding/Cargo.toml @@ -27,6 +27,7 @@ yazi-widgets = { path = "../yazi-widgets", version = "26.1.22" } # External dependencies ansi-to-tui = { workspace = true } +anyhow = { workspace = true } crossterm = { workspace = true } futures = { workspace = true } hashbrown = { workspace = true } diff --git a/yazi-binding/src/runtime.rs b/yazi-binding/src/runtime.rs index f9652941..0301c1cb 100644 --- a/yazi-binding/src/runtime.rs +++ b/yazi-binding/src/runtime.rs @@ -1,5 +1,6 @@ use std::{collections::VecDeque, mem}; +use anyhow::{Context, Result}; use hashbrown::{HashMap, hash_map::EntryRef}; use mlua::Function; @@ -11,12 +12,12 @@ pub struct Runtime { } #[derive(Debug)] -struct RuntimeFrame { +pub struct RuntimeFrame { id: String, } impl Runtime { - pub fn new() -> Self { Self { frames: <_>::default(), blocks: <_>::default(), blocking: true } } + pub fn new() -> Self { Self { frames: <_>::default(), blocks: <_>::default(), blocking: false } } pub fn new_isolate(id: &str) -> Self { Self { @@ -28,28 +29,32 @@ impl Runtime { pub fn push(&mut self, id: &str) { self.frames.push_back(RuntimeFrame { id: id.to_owned() }); } - pub fn pop(&mut self) { self.frames.pop_back(); } + pub fn pop(&mut self) -> Result { + self.frames.pop_back().context("Runtime stack underflow") + } pub fn critical_push(&mut self, id: &str, blocking: bool) -> bool { self.push(id); mem::replace(&mut self.blocking, blocking) } - pub fn critical_pop(&mut self, blocking: bool) { - self.pop(); + pub fn critical_pop(&mut self, blocking: bool) -> Result { self.blocking = blocking; + self.pop() } - pub fn current(&self) -> Option<&str> { self.frames.back().map(|f| f.id.as_str()) } + pub fn current(&self) -> Result<&str> { + self.frames.back().map(|f| f.id.as_str()).context("No current runtime frame") + } - pub fn current_owned(&self) -> Option { self.current().map(ToOwned::to_owned) } + pub fn current_owned(&self) -> Result { self.current().map(ToOwned::to_owned) } pub fn get_block(&self, id: &str, calls: usize) -> Option { self.blocks.get(id).and_then(|v| v.get(calls)).cloned() } pub fn put_block(&mut self, f: &Function) -> Option { - let Some(cur) = self.frames.back() else { return None }; + let Some(cur) = self.frames.back().filter(|f| f.id != "init") else { return None }; Some(match self.blocks.entry_ref(&cur.id) { EntryRef::Occupied(mut oe) => { oe.get_mut().push(f.clone()); diff --git a/yazi-plugin/preset/plugins/init.lua b/yazi-plugin/preset/plugins/init.lua new file mode 100644 index 00000000..e69de29b diff --git a/yazi-plugin/src/loader/loader.rs b/yazi-plugin/src/loader/loader.rs index b689a695..4287508e 100644 --- a/yazi-plugin/src/loader/loader.rs +++ b/yazi-plugin/src/loader/loader.rs @@ -36,6 +36,7 @@ impl Default for Loader { ("font".to_owned(), preset!("plugins/font").into()), ("fzf".to_owned(), preset!("plugins/fzf").into()), ("image".to_owned(), preset!("plugins/image").into()), + ("init".to_owned(), preset!("plugins/init").into()), ("json".to_owned(), preset!("plugins/json").into()), ("magick".to_owned(), preset!("plugins/magick").into()), ("mime".to_owned(), preset!("plugins/mime").into()), diff --git a/yazi-plugin/src/loader/require.rs b/yazi-plugin/src/loader/require.rs index 9b81d10e..601c66de 100644 --- a/yazi-plugin/src/loader/require.rs +++ b/yazi-plugin/src/loader/require.rs @@ -18,7 +18,7 @@ impl Require { runtime_mut!(lua)?.push(&id); let mod_ = LOADER.load(&lua, &id).await; - runtime_mut!(lua)?.pop(); + runtime_mut!(lua)?.pop()?; Self::create_mt(&lua, id.into_owned(), mod_?) })?, @@ -61,7 +61,7 @@ impl Require { let (r#mod, args) = Self::split_mod_and_args(&lua, &id, args)?; runtime_mut!(lua)?.push(&id); let result = r#mod.call_async_function::(&f, args).await; - runtime_mut!(lua)?.pop(); + runtime_mut!(lua)?.pop()?; result } }) @@ -90,10 +90,9 @@ impl Require { fn absolute_id<'a>(lua: &Lua, id: &'a str) -> mlua::Result> { let Some(stripped) = id.strip_prefix('.') else { return Ok(id.into()) }; - Ok(if let Some(cur) = runtime!(lua)?.current() { - format!("{}.{stripped}", cur.split('.').next().unwrap_or(cur)).into() - } else { - stripped.into() - }) + + let rt = runtime!(lua)?; + let cur = rt.current()?; + Ok(format!("{}.{stripped}", cur.split('.').next().unwrap_or(cur)).into()) } } diff --git a/yazi-plugin/src/lua.rs b/yazi-plugin/src/lua.rs index 08ee42e1..dca05068 100644 --- a/yazi-plugin/src/lua.rs +++ b/yazi-plugin/src/lua.rs @@ -65,9 +65,10 @@ fn stage_2(lua: &'static Lua) -> mlua::Result<()> { lua.load(preset!("compat")).set_name("compat.lua").exec()?; if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) { + let blocking = runtime_mut!(lua)?.critical_push("init", true); block_on(lua.load(b).set_name("init.lua").exec_async())?; + runtime_mut!(lua)?.critical_pop(blocking)?; } - runtime_mut!(lua)?.blocking = false; Ok(()) } diff --git a/yazi-plugin/src/pubsub/pubsub.rs b/yazi-plugin/src/pubsub/pubsub.rs index 0b345603..4b1768e8 100644 --- a/yazi-plugin/src/pubsub/pubsub.rs +++ b/yazi-plugin/src/pubsub/pubsub.rs @@ -21,10 +21,7 @@ impl Pubsub { pub(super) fn sub(lua: &Lua) -> mlua::Result { lua.create_function(|lua, (kind, f): (mlua::String, Function)| { let rt = runtime!(lua)?; - let Some(cur) = rt.current() else { - return Err("`sub()` must be called in a sync plugin").into_lua_err(); - }; - if !yazi_dds::Pubsub::sub(cur, &kind.to_str()?, f) { + if !yazi_dds::Pubsub::sub(rt.current()?, &kind.to_str()?, f) { return Err("`sub()` called twice").into_lua_err(); } Ok(()) @@ -34,10 +31,7 @@ impl Pubsub { pub(super) fn sub_remote(lua: &Lua) -> mlua::Result { lua.create_function(|lua, (kind, f): (mlua::String, Function)| { let rt = runtime!(lua)?; - let Some(cur) = rt.current() else { - return Err("`sub_remote()` must be called in a sync plugin").into_lua_err(); - }; - if !yazi_dds::Pubsub::sub_remote(cur, &kind.to_str()?, f) { + if !yazi_dds::Pubsub::sub_remote(rt.current()?, &kind.to_str()?, f) { return Err("`sub_remote()` called twice").into_lua_err(); } Ok(()) @@ -46,21 +40,15 @@ impl Pubsub { pub(super) fn unsub(lua: &Lua) -> mlua::Result { lua.create_function(|lua, kind: mlua::String| { - if let Some(cur) = runtime!(lua)?.current() { - Ok(yazi_dds::Pubsub::unsub(cur, &kind.to_str()?)) - } else { - Err("`unsub()` must be called in a sync plugin").into_lua_err() - } + let rt = runtime!(lua)?; + Ok(yazi_dds::Pubsub::unsub(rt.current()?, &kind.to_str()?)) }) } pub(super) fn unsub_remote(lua: &Lua) -> mlua::Result { lua.create_function(|lua, kind: mlua::String| { - if let Some(cur) = runtime!(lua)?.current() { - Ok(yazi_dds::Pubsub::unsub_remote(cur, &kind.to_str()?)) - } else { - Err("`unsub_remote()` must be called in a sync plugin").into_lua_err() - } + let rt = runtime!(lua)?; + Ok(yazi_dds::Pubsub::unsub_remote(rt.current()?, &kind.to_str()?)) }) } } diff --git a/yazi-plugin/src/utils/sync.rs b/yazi-plugin/src/utils/sync.rs index b9b1ba49..b624f542 100644 --- a/yazi-plugin/src/utils/sync.rs +++ b/yazi-plugin/src/utils/sync.rs @@ -1,5 +1,3 @@ -use std::mem; - use anyhow::Context; use futures::future::join_all; use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, MultiValue, Table, Value, Variadic}; @@ -16,27 +14,26 @@ use crate::loader::LOADER; impl Utils { pub(super) fn sync(lua: &Lua) -> mlua::Result { lua.create_function(|lua, f: Function| { - let Some(block) = runtime_mut!(lua)?.put_block(&f) else { + let mut rt = runtime_mut!(lua)?; + let Some(block) = rt.put_block(&f) else { return Err("`ya.sync()` must be called in a plugin").into_lua_err(); }; + let current = rt.current_owned()?; lua.create_async_function(move |lua, mut args: MultiValue| { - let f = f.clone(); + let (f, current) = (f.clone(), current.clone()); async move { - let rt = runtime!(lua)?; - let (Some(cur), blocking) = (rt.current_owned(), rt.blocking) else { - return Err("`ya.sync()` block must be used within a plugin").into_lua_err(); - }; - drop(rt); - + let blocking = runtime!(lua)?.blocking; if blocking { - args.push_front(Value::Table(LOADER.try_load(&lua, &cur)?)); + args.push_front(Value::Table(LOADER.try_load(&lua, ¤t)?)); f.call::(args) } else { - Self::retrieve(&lua, &cur, block, args) + Self::retrieve(&lua, ¤t, block, args) .await .and_then(|data| Sendable::list_to_values(&lua, data)) - .with_context(|| format!("Failed to execute sync block-{block} in `{cur}` plugin")) + .with_context(|| { + format!("Failed to execute sync block-{block} in `{current}` plugin") + }) .into_lua_err() } } @@ -51,26 +48,18 @@ impl Utils { }) } else { lua.create_function(|lua, (f, args): (Function, MultiValue)| { - let name = runtime!(lua)?.current_owned(); + let name = runtime!(lua)?.current_owned()?; let lua = lua.clone(); Ok(Handle::AsyncFn(LOCAL_SET.spawn_local(async move { - let blocking = mem::replace(&mut runtime_mut!(lua)?.blocking, false); - if let Some(s) = &name { - runtime_mut!(lua)?.push(s); // make `current_owned` always return Some to eliminate the check - } - + let blocking = runtime_mut!(lua)?.critical_push(&name, false); let result = f.call_async::(args).await; - - runtime_mut!(lua)?.blocking = blocking; - if name.is_some() { - runtime_mut!(lua)?.pop(); - } + runtime_mut!(lua)?.critical_pop(blocking)?; if let Err(ref e) = result { - match name { - Some(s) => tracing::error!("Failed to execute async block in `{s}` plugin: {e}"), - None => tracing::error!("Failed to execute async block in `init.lua`: {e}"), + match name.as_str() { + "init" => tracing::error!("Failed to execute async block in `init.lua`: {e}"), + s => tracing::error!("Failed to execute async block in `{s}` plugin: {e}"), } } result