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
- 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

1
Cargo.lock generated
View file

@ -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",

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

View file

@ -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(())
})?);

View file

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

View file

@ -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 }

View file

@ -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<RuntimeFrame> {
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<RuntimeFrame> {
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> {
self.blocks.get(id).and_then(|v| v.get(calls)).cloned()
}
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) {
EntryRef::Occupied(mut oe) => {
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()),
("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()),

View file

@ -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::<MultiValue>(&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<Cow<'a, str>> {
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())
}
}

View file

@ -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(())
}

View file

@ -21,10 +21,7 @@ impl Pubsub {
pub(super) fn sub(lua: &Lua) -> mlua::Result<Function> {
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<Function> {
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<Function> {
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<Function> {
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()?))
})
}
}

View file

@ -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<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();
};
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, &current)?));
f.call::<MultiValue>(args)
} else {
Self::retrieve(&lua, &cur, block, args)
Self::retrieve(&lua, &current, 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::<MultiValue>(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