feat: allow using ps.sub() in init.lua directly without a plugin (#3638)

This commit is contained in:
三咲雅 misaki masa 2026-01-30 10:12:21 +08:00 committed by GitHub
parent 1ea0ea072c
commit dfdb235d74
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 52 additions and 65 deletions

View file

@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Added ### 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 `cx.which` API to access the which component state ([#3617])
- New `ind-which-activate` DDS event to change the which component behavior ([#3608]) - 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 [#3617]: https://github.com/sxyazi/yazi/pull/3617
[#3633]: https://github.com/sxyazi/yazi/pull/3633 [#3633]: https://github.com/sxyazi/yazi/pull/3633
[#3634]: https://github.com/sxyazi/yazi/pull/3634 [#3634]: https://github.com/sxyazi/yazi/pull/3634
[#3638]: https://github.com/sxyazi/yazi/pull/3638

1
Cargo.lock generated
View file

@ -5596,6 +5596,7 @@ name = "yazi-binding"
version = "26.1.22" version = "26.1.22"
dependencies = [ dependencies = [
"ansi-to-tui", "ansi-to-tui",
"anyhow",
"crossterm 0.29.0", "crossterm 0.29.0",
"futures", "futures",
"hashbrown 0.16.1", "hashbrown 0.16.1",

View file

@ -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. 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. 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! 🙏
` `
} }

View file

@ -35,7 +35,7 @@ impl Actor for AcceptPayload {
if let Err(e) = cb.call::<()>(body.clone()) { if let Err(e) = cb.call::<()>(body.clone()) {
error!("Failed to run `{kind}` event handler in your `{id}` plugin: {e}"); 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(()) Ok(())
})?); })?);

View file

@ -20,7 +20,7 @@ impl Preflight {
for (id, cb) in handlers { for (id, cb) in handlers {
let blocking = runtime_mut!(LUA)?.critical_push(&id, true); let blocking = runtime_mut!(LUA)?.critical_push(&id, true);
let result = cb.call::<Value>(&body); let result = cb.call::<Value>(&body);
runtime_mut!(LUA)?.critical_pop(blocking); runtime_mut!(LUA)?.critical_pop(blocking)?;
match result { match result {
Ok(Value::Nil) => { Ok(Value::Nil) => {

View file

@ -27,6 +27,7 @@ yazi-widgets = { path = "../yazi-widgets", version = "26.1.22" }
# External dependencies # External dependencies
ansi-to-tui = { workspace = true } ansi-to-tui = { workspace = true }
anyhow = { workspace = true }
crossterm = { workspace = true } crossterm = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }

View file

@ -1,5 +1,6 @@
use std::{collections::VecDeque, mem}; use std::{collections::VecDeque, mem};
use anyhow::{Context, Result};
use hashbrown::{HashMap, hash_map::EntryRef}; use hashbrown::{HashMap, hash_map::EntryRef};
use mlua::Function; use mlua::Function;
@ -11,12 +12,12 @@ pub struct Runtime {
} }
#[derive(Debug)] #[derive(Debug)]
struct RuntimeFrame { pub struct RuntimeFrame {
id: String, id: String,
} }
impl Runtime { 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 { pub fn new_isolate(id: &str) -> Self {
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 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<RuntimeFrame> {
self.frames.pop_back().context("Runtime stack underflow")
}
pub fn critical_push(&mut self, id: &str, blocking: bool) -> bool { pub fn critical_push(&mut self, id: &str, blocking: bool) -> bool {
self.push(id); self.push(id);
mem::replace(&mut self.blocking, blocking) mem::replace(&mut self.blocking, blocking)
} }
pub fn critical_pop(&mut self, blocking: bool) { pub fn critical_pop(&mut self, blocking: bool) -> Result<RuntimeFrame> {
self.pop();
self.blocking = blocking; 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<String> { self.current().map(ToOwned::to_owned) } pub fn current_owned(&self) -> Result<String> { self.current().map(ToOwned::to_owned) }
pub fn get_block(&self, id: &str, calls: usize) -> Option<Function> { pub fn get_block(&self, id: &str, calls: usize) -> Option<Function> {
self.blocks.get(id).and_then(|v| v.get(calls)).cloned() self.blocks.get(id).and_then(|v| v.get(calls)).cloned()
} }
pub fn put_block(&mut self, f: &Function) -> Option<usize> { pub fn put_block(&mut self, f: &Function) -> Option<usize> {
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) { Some(match self.blocks.entry_ref(&cur.id) {
EntryRef::Occupied(mut oe) => { EntryRef::Occupied(mut oe) => {
oe.get_mut().push(f.clone()); oe.get_mut().push(f.clone());

View file

View file

@ -36,6 +36,7 @@ impl Default for Loader {
("font".to_owned(), preset!("plugins/font").into()), ("font".to_owned(), preset!("plugins/font").into()),
("fzf".to_owned(), preset!("plugins/fzf").into()), ("fzf".to_owned(), preset!("plugins/fzf").into()),
("image".to_owned(), preset!("plugins/image").into()), ("image".to_owned(), preset!("plugins/image").into()),
("init".to_owned(), preset!("plugins/init").into()),
("json".to_owned(), preset!("plugins/json").into()), ("json".to_owned(), preset!("plugins/json").into()),
("magick".to_owned(), preset!("plugins/magick").into()), ("magick".to_owned(), preset!("plugins/magick").into()),
("mime".to_owned(), preset!("plugins/mime").into()), ("mime".to_owned(), preset!("plugins/mime").into()),

View file

@ -18,7 +18,7 @@ impl Require {
runtime_mut!(lua)?.push(&id); runtime_mut!(lua)?.push(&id);
let mod_ = LOADER.load(&lua, &id).await; let mod_ = LOADER.load(&lua, &id).await;
runtime_mut!(lua)?.pop(); runtime_mut!(lua)?.pop()?;
Self::create_mt(&lua, id.into_owned(), mod_?) 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)?; let (r#mod, args) = Self::split_mod_and_args(&lua, &id, args)?;
runtime_mut!(lua)?.push(&id); runtime_mut!(lua)?.push(&id);
let result = r#mod.call_async_function::<MultiValue>(&f, args).await; let result = r#mod.call_async_function::<MultiValue>(&f, args).await;
runtime_mut!(lua)?.pop(); runtime_mut!(lua)?.pop()?;
result result
} }
}) })
@ -90,10 +90,9 @@ impl Require {
fn absolute_id<'a>(lua: &Lua, id: &'a str) -> mlua::Result<Cow<'a, str>> { fn absolute_id<'a>(lua: &Lua, id: &'a str) -> mlua::Result<Cow<'a, str>> {
let Some(stripped) = id.strip_prefix('.') else { return Ok(id.into()) }; 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() let rt = runtime!(lua)?;
} else { let cur = rt.current()?;
stripped.into() Ok(format!("{}.{stripped}", cur.split('.').next().unwrap_or(cur)).into())
})
} }
} }

View file

@ -65,9 +65,10 @@ fn stage_2(lua: &'static Lua) -> mlua::Result<()> {
lua.load(preset!("compat")).set_name("compat.lua").exec()?; lua.load(preset!("compat")).set_name("compat.lua").exec()?;
if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) { 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())?; block_on(lua.load(b).set_name("init.lua").exec_async())?;
runtime_mut!(lua)?.critical_pop(blocking)?;
} }
runtime_mut!(lua)?.blocking = false;
Ok(()) Ok(())
} }

View file

@ -21,10 +21,7 @@ impl Pubsub {
pub(super) fn sub(lua: &Lua) -> mlua::Result<Function> { pub(super) fn sub(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (kind, f): (mlua::String, Function)| { lua.create_function(|lua, (kind, f): (mlua::String, Function)| {
let rt = runtime!(lua)?; let rt = runtime!(lua)?;
let Some(cur) = rt.current() else { if !yazi_dds::Pubsub::sub(rt.current()?, &kind.to_str()?, f) {
return Err("`sub()` must be called in a sync plugin").into_lua_err();
};
if !yazi_dds::Pubsub::sub(cur, &kind.to_str()?, f) {
return Err("`sub()` called twice").into_lua_err(); return Err("`sub()` called twice").into_lua_err();
} }
Ok(()) Ok(())
@ -34,10 +31,7 @@ impl Pubsub {
pub(super) fn sub_remote(lua: &Lua) -> mlua::Result<Function> { pub(super) fn sub_remote(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (kind, f): (mlua::String, Function)| { lua.create_function(|lua, (kind, f): (mlua::String, Function)| {
let rt = runtime!(lua)?; let rt = runtime!(lua)?;
let Some(cur) = rt.current() else { if !yazi_dds::Pubsub::sub_remote(rt.current()?, &kind.to_str()?, f) {
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) {
return Err("`sub_remote()` called twice").into_lua_err(); return Err("`sub_remote()` called twice").into_lua_err();
} }
Ok(()) Ok(())
@ -46,21 +40,15 @@ impl Pubsub {
pub(super) fn unsub(lua: &Lua) -> mlua::Result<Function> { pub(super) fn unsub(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, kind: mlua::String| { lua.create_function(|lua, kind: mlua::String| {
if let Some(cur) = runtime!(lua)?.current() { let rt = runtime!(lua)?;
Ok(yazi_dds::Pubsub::unsub(cur, &kind.to_str()?)) Ok(yazi_dds::Pubsub::unsub(rt.current()?, &kind.to_str()?))
} else {
Err("`unsub()` must be called in a sync plugin").into_lua_err()
}
}) })
} }
pub(super) fn unsub_remote(lua: &Lua) -> mlua::Result<Function> { pub(super) fn unsub_remote(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, kind: mlua::String| { lua.create_function(|lua, kind: mlua::String| {
if let Some(cur) = runtime!(lua)?.current() { let rt = runtime!(lua)?;
Ok(yazi_dds::Pubsub::unsub_remote(cur, &kind.to_str()?)) Ok(yazi_dds::Pubsub::unsub_remote(rt.current()?, &kind.to_str()?))
} else {
Err("`unsub_remote()` must be called in a sync plugin").into_lua_err()
}
}) })
} }
} }

View file

@ -1,5 +1,3 @@
use std::mem;
use anyhow::Context; use anyhow::Context;
use futures::future::join_all; use futures::future::join_all;
use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, MultiValue, Table, Value, Variadic}; use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, MultiValue, Table, Value, Variadic};
@ -16,27 +14,26 @@ use crate::loader::LOADER;
impl Utils { impl Utils {
pub(super) fn sync(lua: &Lua) -> mlua::Result<Function> { pub(super) fn sync(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, f: Function| { 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(); 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| { lua.create_async_function(move |lua, mut args: MultiValue| {
let f = f.clone(); let (f, current) = (f.clone(), current.clone());
async move { async move {
let rt = runtime!(lua)?; let blocking = runtime!(lua)?.blocking;
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);
if blocking { if blocking {
args.push_front(Value::Table(LOADER.try_load(&lua, &cur)?)); args.push_front(Value::Table(LOADER.try_load(&lua, &current)?));
f.call::<MultiValue>(args) f.call::<MultiValue>(args)
} else { } else {
Self::retrieve(&lua, &cur, block, args) Self::retrieve(&lua, &current, block, args)
.await .await
.and_then(|data| Sendable::list_to_values(&lua, data)) .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() .into_lua_err()
} }
} }
@ -51,26 +48,18 @@ impl Utils {
}) })
} else { } else {
lua.create_function(|lua, (f, args): (Function, MultiValue)| { lua.create_function(|lua, (f, args): (Function, MultiValue)| {
let name = runtime!(lua)?.current_owned(); let name = runtime!(lua)?.current_owned()?;
let lua = lua.clone(); let lua = lua.clone();
Ok(Handle::AsyncFn(LOCAL_SET.spawn_local(async move { Ok(Handle::AsyncFn(LOCAL_SET.spawn_local(async move {
let blocking = mem::replace(&mut runtime_mut!(lua)?.blocking, false); let blocking = runtime_mut!(lua)?.critical_push(&name, false);
if let Some(s) = &name {
runtime_mut!(lua)?.push(s); // make `current_owned` always return Some to eliminate the check
}
let result = f.call_async::<MultiValue>(args).await; let result = f.call_async::<MultiValue>(args).await;
runtime_mut!(lua)?.critical_pop(blocking)?;
runtime_mut!(lua)?.blocking = blocking;
if name.is_some() {
runtime_mut!(lua)?.pop();
}
if let Err(ref e) = result { if let Err(ref e) = result {
match name { match name.as_str() {
Some(s) => tracing::error!("Failed to execute async block in `{s}` plugin: {e}"), "init" => tracing::error!("Failed to execute async block in `init.lua`: {e}"),
None => tracing::error!("Failed to execute async block in `init.lua`: {e}"), s => tracing::error!("Failed to execute async block in `{s}` plugin: {e}"),
} }
} }
result result