From b67798ea199a28fbe6cc210305d5db64918d0970 Mon Sep 17 00:00:00 2001 From: sxyazi Date: Wed, 25 Mar 2026 19:57:31 +0800 Subject: [PATCH] feat: Vim-like `lua` action that runs an inline Lua snippet (#3813) --- CHANGELOG.md | 2 ++ Cargo.lock | 32 ++++++++++++++++---------------- Cargo.toml | 2 +- yazi-actor/src/app/lua.rs | 25 +++++++++++++++++++++++++ yazi-actor/src/app/mod.rs | 1 + yazi-dds/src/spark/spark.rs | 3 +++ yazi-fm/src/executor.rs | 15 +++++++++++++++ yazi-parser/src/app/lua.rs | 24 ++++++++++++++++++++++++ yazi-parser/src/app/mod.rs | 2 +- yazi-plugin/src/loader/loader.rs | 2 ++ 10 files changed, 90 insertions(+), 18 deletions(-) create mode 100644 yazi-actor/src/app/lua.rs create mode 100644 yazi-parser/src/app/lua.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c3ea546..5c7ce80d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/): - Custom tab name ([#3666]) - New `--in` for `search` action to set search directory ([#3696]) - Multi-file spotter ([#3733]) +- Vim-like `lua` action that runs an inline Lua snippet ([#3813]) - Certificate authentication for SFTP VFS provider ([#3716]) - New `hovered` condition specifying different icons for hovered files ([#3728]) - Allow using `ps.sub()` in `init.lua` directly without a plugin ([#3638]) @@ -1692,3 +1693,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/): [#3781]: https://github.com/sxyazi/yazi/pull/3781 [#3792]: https://github.com/sxyazi/yazi/pull/3792 [#3804]: https://github.com/sxyazi/yazi/pull/3804 +[#3813]: https://github.com/sxyazi/yazi/pull/3813 diff --git a/Cargo.lock b/Cargo.lock index b3ce943e..5c275da5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2269,9 +2269,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ "libc", ] @@ -2608,9 +2608,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-derive" @@ -3972,9 +3972,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" dependencies = [ "serde_core", ] @@ -4615,9 +4615,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.0.7+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd28d57d8a6f6e458bc0b8784f8fdcc4b99a437936056fa122cb234f18656a96" +checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc" dependencies = [ "indexmap 2.13.0", "serde_core", @@ -4630,27 +4630,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.1+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.10+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.7+spec-1.1.0" +version = "1.1.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" +checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" [[package]] name = "tracing" @@ -4784,9 +4784,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "da36089a805484bcccfffe0739803392c8298778a2d2f09febf76fac5ad9025b" [[package]] name = "unicode-truncate" diff --git a/Cargo.toml b/Cargo.toml index 2d8073c9..4b423321 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,7 +72,7 @@ thiserror = "2.0.18" tokio = { version = "1.50.0", features = [ "full" ] } tokio-stream = "0.1.18" tokio-util = "0.7.18" -toml = { version = "1.0.7" } +toml = { version = "1.1.0" } tracing = { version = "0.1.44", features = [ "max_level_debug", "release_max_level_debug" ] } twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] } typed-path = "0.12.3" diff --git a/yazi-actor/src/app/lua.rs b/yazi-actor/src/app/lua.rs new file mode 100644 index 00000000..553c504b --- /dev/null +++ b/yazi-actor/src/app/lua.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use yazi_binding::runtime_scope; +use yazi_dds::Sendable; +use yazi_macro::succ; +use yazi_parser::app::LuaOpt; +use yazi_plugin::LUA; +use yazi_shared::data::Data; + +use crate::{Actor, Ctx, lives::Lives}; + +pub struct Lua; + +impl Actor for Lua { + type Options = LuaOpt; + + const NAME: &str = "lua"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let chunk = LUA.load(&*opt.code).set_name("anonymous"); + let result = Lives::scope(cx.core, || { + runtime_scope!(LUA, "inline", Sendable::value_to_data(&LUA, chunk.eval()?)) + }); + succ!(result?); + } +} diff --git a/yazi-actor/src/app/mod.rs b/yazi-actor/src/app/mod.rs index 36cab3fb..931cacbf 100644 --- a/yazi-actor/src/app/mod.rs +++ b/yazi-actor/src/app/mod.rs @@ -3,6 +3,7 @@ yazi_macro::mod_flat!( bootstrap deprecate focus + lua mouse plugin plugin_do diff --git a/yazi-dds/src/spark/spark.rs b/yazi-dds/src/spark/spark.rs index a7a1ba9e..9fddb178 100644 --- a/yazi-dds/src/spark/spark.rs +++ b/yazi-dds/src/spark/spark.rs @@ -12,6 +12,7 @@ pub enum Spark<'a> { AppBootstrap(yazi_parser::VoidOpt), AppDeprecate(yazi_parser::app::DeprecateOpt), AppFocus(yazi_parser::VoidOpt), + AppLua(yazi_parser::app::LuaOpt), AppMouse(yazi_parser::app::MouseOpt), AppPlugin(yazi_parser::app::PluginOpt), AppPluginDo(yazi_parser::app::PluginOpt), @@ -194,6 +195,7 @@ impl<'a> IntoLua for Spark<'a> { Self::AppBootstrap(b) => b.into_lua(lua), Self::AppDeprecate(b) => b.into_lua(lua), Self::AppFocus(b) => b.into_lua(lua), + Self::AppLua(b) => b.into_lua(lua), Self::AppMouse(b) => b.into_lua(lua), Self::AppPlugin(b) => b.into_lua(lua), Self::AppPluginDo(b) => b.into_lua(lua), @@ -364,6 +366,7 @@ try_from_spark!( // App try_from_spark!(yazi_parser::ArrowOpt, mgr:arrow, mgr:tab_swap); try_from_spark!(yazi_parser::app::DeprecateOpt, app:deprecate); +try_from_spark!(yazi_parser::app::LuaOpt, app:lua); try_from_spark!(yazi_parser::app::MouseOpt, app:mouse); try_from_spark!(yazi_parser::app::PluginOpt, app:plugin, app:plugin_do); try_from_spark!(yazi_parser::app::QuitOpt, app:quit, mgr:quit); diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 05205532..d9a0460b 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -45,6 +45,7 @@ impl<'a> Executor<'a> { on!(plugin); on!(plugin_do); on!(update_progress); + on!(lua); on!(deprecate); on!(quit); @@ -156,6 +157,8 @@ impl<'a> Executor<'a> { "help" => act!(help:toggle, cx, Layer::Mgr), // Plugin "plugin" => act!(app:plugin, cx, action), + // Lua + "lua" => act!(app:lua, cx, action), _ => succ!(), } } @@ -186,6 +189,8 @@ impl<'a> Executor<'a> { "help" => act!(help:toggle, cx, Layer::Tasks), // Plugin "plugin" => act!(app:plugin, cx, action), + // Lua + "lua" => act!(app:lua, cx, action), _ => succ!(), } } @@ -211,6 +216,8 @@ impl<'a> Executor<'a> { "help" => act!(help:toggle, cx, Layer::Spot), // Plugin "plugin" => act!(app:plugin, cx, action), + // Lua + "lua" => act!(app:lua, cx, action), _ => succ!(), } } @@ -235,6 +242,8 @@ impl<'a> Executor<'a> { "help" => act!(help:toggle, cx, Layer::Pick), // Plugin "plugin" => act!(app:plugin, cx, action), + // Lua + "lua" => act!(app:lua, cx, action), _ => succ!(), } } @@ -262,6 +271,8 @@ impl<'a> Executor<'a> { "help" => return act!(help:toggle, cx, Layer::Input), // Plugin "plugin" => return act!(app:plugin, cx, action), + // Lua + "lua" => return act!(app:lua, cx, action), _ => {} } } @@ -312,6 +323,8 @@ impl<'a> Executor<'a> { "close" => act!(help:toggle, cx, Layer::Help), // Plugin "plugin" => act!(app:plugin, cx, action), + // Lua + "lua" => act!(app:lua, cx, action), _ => succ!(), } } @@ -337,6 +350,8 @@ impl<'a> Executor<'a> { "help" => act!(help:toggle, cx, Layer::Cmp), // Plugin "plugin" => act!(app:plugin, cx, action), + // Lua + "lua" => act!(app:lua, cx, action), _ => succ!(), } } diff --git a/yazi-parser/src/app/lua.rs b/yazi-parser/src/app/lua.rs new file mode 100644 index 00000000..b6ece619 --- /dev/null +++ b/yazi-parser/src/app/lua.rs @@ -0,0 +1,24 @@ +use std::fmt::Debug; + +use anyhow::Result; +use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +use yazi_shared::{SStr, event::ActionCow}; + +#[derive(Clone, Debug, Default)] +pub struct LuaOpt { + pub code: SStr, +} + +impl TryFrom for LuaOpt { + type Error = anyhow::Error; + + fn try_from(mut a: ActionCow) -> Result { Ok(Self { code: a.take_first()? }) } +} + +impl FromLua for LuaOpt { + fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} + +impl IntoLua for LuaOpt { + fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} diff --git a/yazi-parser/src/app/mod.rs b/yazi-parser/src/app/mod.rs index 8f7ac2c4..ce380fb9 100644 --- a/yazi-parser/src/app/mod.rs +++ b/yazi-parser/src/app/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(deprecate mouse plugin quit reflow resume stop title update_progress); +yazi_macro::mod_flat!(deprecate lua mouse plugin quit reflow resume stop title update_progress); diff --git a/yazi-plugin/src/loader/loader.rs b/yazi-plugin/src/loader/loader.rs index e6748c2c..1c500f4e 100644 --- a/yazi-plugin/src/loader/loader.rs +++ b/yazi-plugin/src/loader/loader.rs @@ -69,6 +69,8 @@ impl Default for Loader { ("tab".to_owned(), [][..].into()), ("tabs".to_owned(), [][..].into()), ("tasks".to_owned(), [][..].into()), + // Reserved + ("inline".to_owned(), [][..].into()), ]); Self { cache: RwLock::new(cache) } }