From 68ffd82c0d703a8dd0f13c755b3f13e6046a2ad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Sun, 28 Jan 2024 00:30:41 +0800 Subject: [PATCH] feat: plugin-specific state persistence (#590) --- yazi-fm/src/app/app.rs | 8 +++--- yazi-fm/src/app/commands/plugin.rs | 29 +++++++++----------- yazi-fm/src/app/commands/render.rs | 32 +++++++++++------------ yazi-fm/src/app/commands/resize.rs | 17 +++++++++--- yazi-fm/src/app/commands/resume.rs | 2 +- yazi-fm/src/executor.rs | 1 + yazi-fm/src/lives/lives.rs | 12 ++++++--- yazi-fm/src/main.rs | 1 + yazi-plugin/preset/components/folder.lua | 22 +++++++++++++++- yazi-plugin/preset/components/header.lua | 8 ++++-- yazi-plugin/preset/components/manager.lua | 8 ++++-- yazi-plugin/preset/state.lua | 16 ++++++++++++ yazi-plugin/preset/ui.lua | 21 --------------- yazi-plugin/preset/ya.lua | 2 +- yazi-plugin/src/elements/elements.rs | 3 +-- yazi-plugin/src/elements/layout.rs | 4 +-- yazi-plugin/src/isolate/entry.rs | 3 ++- yazi-plugin/src/plugin.rs | 2 +- yazi-plugin/src/utils/call.rs | 28 ++++++++++++++------ 19 files changed, 130 insertions(+), 89 deletions(-) create mode 100644 yazi-plugin/preset/state.lua delete mode 100644 yazi-plugin/preset/ui.lua diff --git a/yazi-fm/src/app/app.rs b/yazi-fm/src/app/app.rs index a2805e65..1c46cf88 100644 --- a/yazi-fm/src/app/app.rs +++ b/yazi-fm/src/app/app.rs @@ -23,7 +23,7 @@ impl App { Lives::register()?; let mut app = Self { cx: Ctx::make(), term: Some(term), signals }; - app.render()?; + app.render(); let mut times = 0; let mut events = Vec::with_capacity(200); @@ -39,13 +39,13 @@ impl App { if times >= 50 { times = 0; - app.render()?; + app.render(); } else if let Ok(event) = app.signals.rx.try_recv() { events.push(event); emit!(Render); } else { times = 0; - app.render()?; + app.render(); } } Ok(()) @@ -58,7 +58,7 @@ impl App { Event::Seq(execs, layer) => self.dispatch_seq(execs, layer), Event::Render => self.dispatch_render(), Event::Key(key) => self.dispatch_key(key), - Event::Resize => self.resize()?, + Event::Resize => self.resize(()), Event::Paste(str) => self.dispatch_paste(str), Event::Quit(opt) => self.quit(opt), } diff --git a/yazi-fm/src/app/commands/plugin.rs b/yazi-fm/src/app/commands/plugin.rs index 4443d049..3e5aa514 100644 --- a/yazi-fm/src/app/commands/plugin.rs +++ b/yazi-fm/src/app/commands/plugin.rs @@ -1,7 +1,7 @@ use std::fmt::Display; -use mlua::{ExternalError, ExternalResult, IntoLua, Table, TableExt, Value, Variadic}; -use tracing::{error, warn}; +use mlua::{ExternalError, ExternalResult, IntoLua, Table, TableExt, Variadic}; +use tracing::warn; use yazi_plugin::{LOADED, LUA}; use yazi_shared::{emit, event::Exec, Layer}; @@ -36,31 +36,26 @@ impl App { }; let args = Variadic::from_iter(opt.data.args.into_iter().filter_map(|v| v.into_lua(&LUA).ok())); - let mut ret: mlua::Result = Err("uninitialized plugin".into_lua_err()); + let result = Lives::scope(&self.cx, |_| { + LUA.globals().set("YAZI_PLUGIN_NAME", LUA.create_string(&opt.name)?)?; - Lives::scope(&self.cx, |_| { let mut plugin: Option = None; if let Some(b) = LOADED.read().get(&opt.name) { - match LUA.load(b).call(args) { - Ok(t) => plugin = Some(t), - Err(e) => ret = Err(e), - } + plugin = LUA.load(b).call(args)?; } - if let Some(plugin) = plugin { - ret = if let Some(cb) = opt.data.cb { cb(plugin) } else { plugin.call_method("entry", ()) }; - } - }); - if let Err(e) = ret { - error!("{e}"); - return; - } + let Some(plugin) = plugin else { + return Err("plugin not found".into_lua_err()); + }; + + if let Some(cb) = opt.data.cb { cb(plugin) } else { plugin.call_method("entry", ()) } + }); let Some(tx) = opt.data.tx else { return; }; - if let Ok(v) = ret.and_then(|v| v.try_into().into_lua_err()) { + if let Ok(v) = result.and_then(|v| v.try_into().into_lua_err()) { tx.send(v).ok(); } } diff --git a/yazi-fm/src/app/commands/render.rs b/yazi-fm/src/app/commands/render.rs index aca7d710..baf6ba38 100644 --- a/yazi-fm/src/app/commands/render.rs +++ b/yazi-fm/src/app/commands/render.rs @@ -1,32 +1,31 @@ use std::sync::atomic::Ordering; -use anyhow::Result; use ratatui::backend::Backend; use crate::{app::App, lives::Lives, root::{Root, COLLISION}}; impl App { - pub(crate) fn render(&mut self) -> Result<()> { + pub(crate) fn render(&mut self) { let Some(term) = &mut self.term else { - return Ok(()); + return; }; let collision = COLLISION.swap(false, Ordering::Relaxed); - let frame = term.draw(|f| { - Lives::scope(&self.cx, |_| { - f.render_widget(Root::new(&self.cx), f.size()); - }); + let frame = term + .draw(|f| { + _ = Lives::scope(&self.cx, |_| Ok(f.render_widget(Root::new(&self.cx), f.size()))); + if let Some((x, y)) = self.cx.cursor() { + f.set_cursor(x, y); + } + }) + .unwrap(); - if let Some((x, y)) = self.cx.cursor() { - f.set_cursor(x, y); - } - })?; if !COLLISION.load(Ordering::Relaxed) { if collision { // Reload preview if collision is resolved self.cx.manager.peek(true); } - return Ok(()); + return; } let mut patch = vec![]; @@ -39,12 +38,11 @@ impl App { } } - term.backend_mut().draw(patch.iter().map(|(x, y, cell)| (*x, *y, cell)))?; + term.backend_mut().draw(patch.iter().map(|(x, y, cell)| (*x, *y, cell))).ok(); if let Some((x, y)) = self.cx.cursor() { - term.show_cursor()?; - term.set_cursor(x, y)?; + term.show_cursor().ok(); + term.set_cursor(x, y).ok(); } - term.backend_mut().flush()?; - Ok(()) + term.backend_mut().flush().ok(); } } diff --git a/yazi-fm/src/app/commands/resize.rs b/yazi-fm/src/app/commands/resize.rs index b1c2d15c..11ec2ad8 100644 --- a/yazi-fm/src/app/commands/resize.rs +++ b/yazi-fm/src/app/commands/resize.rs @@ -1,14 +1,23 @@ -use anyhow::Result; +use yazi_shared::event::Exec; use crate::app::App; +pub struct Opt; + +impl From for Opt { + fn from(_: Exec) -> Self { Self } +} + +impl From<()> for Opt { + fn from(_: ()) -> Self { Self } +} + impl App { - pub(crate) fn resize(&mut self) -> Result<()> { + pub(crate) fn resize(&mut self, _: impl Into) { self.cx.manager.active_mut().preview.reset(); - self.render()?; + self.render(); self.cx.manager.current_mut().sync_page(true); self.cx.manager.hover(None); - Ok(()) } } diff --git a/yazi-fm/src/app/commands/resume.rs b/yazi-fm/src/app/commands/resume.rs index e299c5a6..1fab8ccb 100644 --- a/yazi-fm/src/app/commands/resume.rs +++ b/yazi-fm/src/app/commands/resume.rs @@ -9,7 +9,7 @@ impl App { // While the app resumes, it's possible that the terminal size has changed. // We need to trigger a resize, and render the UI based on the resized area. - self.resize().unwrap(); + self.resize(()); self.signals.resume(); } diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index c643a51f..d988b0af 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -37,6 +37,7 @@ impl<'a> Executor<'a> { on!(plugin); on!(plugin_do); on!(update_progress); + on!(resize); on!(stop); on!(resume); } diff --git a/yazi-fm/src/lives/lives.rs b/yazi-fm/src/lives/lives.rs index c5c6d2e6..de3ca8c4 100644 --- a/yazi-fm/src/lives/lives.rs +++ b/yazi-fm/src/lives/lives.rs @@ -23,7 +23,10 @@ impl Lives { Ok(()) } - pub(crate) fn scope<'a>(cx: &'a Ctx, f: impl FnOnce(&Scope<'a, 'a>)) { + pub(crate) fn scope<'a, T>( + cx: &'a Ctx, + f: impl FnOnce(&Scope<'a, 'a>) -> mlua::Result, + ) -> mlua::Result { let result = LUA.scope(|scope| { LUA.set_named_registry_value("cx", scope.create_any_userdata_ref(cx)?)?; @@ -37,7 +40,7 @@ impl Lives { ])?, )?; - f(scope); + let ret = f(scope)?; LAYOUT.store(Arc::new(yazi_config::Layout { header: *global.get::<_, Table>("Header")?.get::<_, RectRef>("area")?, @@ -47,12 +50,13 @@ impl Lives { status: *global.get::<_, Table>("Status")?.get::<_, RectRef>("area")?, })); - Ok(()) + Ok(ret) }); - if let Err(e) = result { + if let Err(ref e) = result { error!("{e}"); } + result } pub(crate) fn partial_scope<'a>(cx: &'a Ctx, f: impl FnOnce(&Scope<'a, 'a>)) { diff --git a/yazi-fm/src/main.rs b/yazi-fm/src/main.rs index 0657a219..7e4776b8 100644 --- a/yazi-fm/src/main.rs +++ b/yazi-fm/src/main.rs @@ -1,4 +1,5 @@ #![allow(clippy::module_inception)] +#![allow(clippy::unit_arg)] mod app; mod completion; diff --git a/yazi-plugin/preset/components/folder.lua b/yazi-plugin/preset/components/folder.lua index 8be22250..129f4674 100644 --- a/yazi-plugin/preset/components/folder.lua +++ b/yazi-plugin/preset/components/folder.lua @@ -19,6 +19,26 @@ function Folder:icon(file) return icon and ui.Span(" " .. icon.text .. " "):style(icon.style) or ui.Span("") end +function Folder:highlight_ranges(s, ranges) + if ranges == nil or #ranges == 0 then + return { ui.Span(s) } + end + + local spans = {} + local last = 0 + for _, r in ipairs(ranges) do + if r[1] > last then + spans[#spans + 1] = ui.Span(s:sub(last + 1, r[1])) + end + spans[#spans + 1] = ui.Span(s:sub(r[1] + 1, r[2])):style(THEME.manager.find_keyword) + last = r[2] + end + if last < #s then + spans[#spans + 1] = ui.Span(s:sub(last + 1)) + end + return spans +end + function Folder:highlighted_name(file) -- Complete prefix when searching across directories local prefix = file:prefix() or "" @@ -28,7 +48,7 @@ function Folder:highlighted_name(file) -- Range highlighting for filenames local highlights = file:highlights() - local spans = ui.highlight_ranges(prefix .. file.name, highlights) + local spans = self:highlight_ranges(prefix .. file.name, highlights) -- Show symlink target if MANAGER.show_symlink and file.link_to ~= nil then diff --git a/yazi-plugin/preset/components/header.lua b/yazi-plugin/preset/components/header.lua index 0642d9da..db119820 100644 --- a/yazi-plugin/preset/components/header.lua +++ b/yazi-plugin/preset/components/header.lua @@ -30,13 +30,17 @@ function Header:tabs() return ui.Line(spans) end -function Header:render(area) +function Header:layout(area) self.area = area - local chunks = ui.Layout() + return ui.Layout() :direction(ui.Layout.HORIZONTAL) :constraints({ ui.Constraint.Percentage(50), ui.Constraint.Percentage(50) }) :split(area) +end + +function Header:render(area) + local chunks = self:layout(area) local left = ui.Line { self:cwd() } local right = ui.Line { self:tabs() } diff --git a/yazi-plugin/preset/components/manager.lua b/yazi-plugin/preset/components/manager.lua index 4dcb4047..72633ada 100644 --- a/yazi-plugin/preset/components/manager.lua +++ b/yazi-plugin/preset/components/manager.lua @@ -2,10 +2,10 @@ Manager = { area = ui.Rect.default, } -function Manager:render(area) +function Manager:layout(area) self.area = area - local chunks = ui.Layout() + return ui.Layout() :direction(ui.Layout.HORIZONTAL) :constraints({ ui.Constraint.Ratio(MANAGER.ratio.parent, MANAGER.ratio.all), @@ -13,6 +13,10 @@ function Manager:render(area) ui.Constraint.Ratio(MANAGER.ratio.preview, MANAGER.ratio.all), }) :split(area) +end + +function Manager:render(area) + local chunks = self:layout(area) return ya.flat { -- Borders diff --git a/yazi-plugin/preset/state.lua b/yazi-plugin/preset/state.lua new file mode 100644 index 00000000..82eef48f --- /dev/null +++ b/yazi-plugin/preset/state.lua @@ -0,0 +1,16 @@ +local cache = {} + +state = setmetatable({ + clear = function() cache[YAZI_PLUGIN_NAME] = nil end, +}, { + __index = function(_, k) + local bucket = YAZI_PLUGIN_NAME + return cache[bucket] and cache[bucket][k] + end, + + __newindex = function(_, k, v) + local bucket = YAZI_PLUGIN_NAME + cache[bucket] = cache[bucket] or {} + cache[bucket][k] = v + end, +}) diff --git a/yazi-plugin/preset/ui.lua b/yazi-plugin/preset/ui.lua deleted file mode 100644 index 90082554..00000000 --- a/yazi-plugin/preset/ui.lua +++ /dev/null @@ -1,21 +0,0 @@ -ui = {} - -function ui.highlight_ranges(s, ranges) - if ranges == nil or #ranges == 0 then - return { ui.Span(s) } - end - - local spans = {} - local last = 0 - for _, r in ipairs(ranges) do - if r[1] > last then - spans[#spans + 1] = ui.Span(s:sub(last + 1, r[1])) - end - spans[#spans + 1] = ui.Span(s:sub(r[1] + 1, r[2])):style(THEME.manager.find_keyword) - last = r[2] - end - if last < #s then - spans[#spans + 1] = ui.Span(s:sub(last + 1)) - end - return spans -end diff --git a/yazi-plugin/preset/ya.lua b/yazi-plugin/preset/ya.lua index d21e89f1..a7e3bebe 100644 --- a/yazi-plugin/preset/ya.lua +++ b/yazi-plugin/preset/ya.lua @@ -39,7 +39,7 @@ function ya.sync(f) end local calls = ya.SYNC_CALLS - return function(...) return plugin_retrieve(ya.PLUGIN_NAME, calls, ...) end + return function(...) return plugin_retrieve(YAZI_PLUGIN_NAME, calls, ...) end end function ya.basename(str) return string.gsub(str, "(.*[/\\])(.*)", "%2") end diff --git a/yazi-plugin/src/elements/elements.rs b/yazi-plugin/src/elements/elements.rs index fa90c362..c9238bd5 100644 --- a/yazi-plugin/src/elements/elements.rs +++ b/yazi-plugin/src/elements/elements.rs @@ -3,8 +3,7 @@ use mlua::{AnyUserData, Lua, Table}; use crate::cast_to_renderable; pub fn init(lua: &Lua) -> mlua::Result<()> { - let globals = lua.globals(); - let ui: Table = globals.get("ui").or_else(|_| lua.create_table())?; + let ui: Table = lua.create_table()?; // Register super::Padding::register(lua)?; diff --git a/yazi-plugin/src/elements/layout.rs b/yazi-plugin/src/elements/layout.rs index ed336a74..718e5aaa 100644 --- a/yazi-plugin/src/elements/layout.rs +++ b/yazi-plugin/src/elements/layout.rs @@ -64,9 +64,7 @@ impl UserData for Layout { ud.borrow_mut::()?.constraints = value.into_iter().map(|c| c.0).collect(); Ok(ud) }); - methods.add_function("split", |lua, (ud, value): (AnyUserData, RectRef)| { - let me = ud.borrow::()?; - + methods.add_method("split", |lua, me, value: RectRef| { let mut layout = ratatui::layout::Layout::new( if me.direction == VERTICAL { ratatui::layout::Direction::Vertical diff --git a/yazi-plugin/src/isolate/entry.rs b/yazi-plugin/src/isolate/entry.rs index 1fee4eb8..142c0ed5 100644 --- a/yazi-plugin/src/isolate/entry.rs +++ b/yazi-plugin/src/isolate/entry.rs @@ -9,8 +9,9 @@ pub async fn entry(name: String, args: Vec) -> mlua::Result<()> { tokio::task::spawn_blocking(move || { let lua = slim_lua()?; - let args = Variadic::from_iter(args.into_iter().filter_map(|v| v.into_lua(&lua).ok())); + lua.globals().set("YAZI_PLUGIN_NAME", lua.create_string(&name)?)?; + let args = Variadic::from_iter(args.into_iter().filter_map(|v| v.into_lua(&lua).ok())); let plugin: Table = if let Some(b) = LOADED.read().get(&name) { lua.load(b).call(args)? } else { diff --git a/yazi-plugin/src/plugin.rs b/yazi-plugin/src/plugin.rs index b7940f18..a8b7cda1 100644 --- a/yazi-plugin/src/plugin.rs +++ b/yazi-plugin/src/plugin.rs @@ -14,7 +14,7 @@ pub fn init() { // Base lua.load(include_str!("../preset/inspect/inspect.lua")).exec()?; - lua.load(include_str!("../preset/ui.lua")).exec()?; + lua.load(include_str!("../preset/state.lua")).exec()?; lua.load(include_str!("../preset/ya.lua")).exec()?; crate::elements::init(lua)?; diff --git a/yazi-plugin/src/utils/call.rs b/yazi-plugin/src/utils/call.rs index a979339e..056dcd02 100644 --- a/yazi-plugin/src/utils/call.rs +++ b/yazi-plugin/src/utils/call.rs @@ -28,6 +28,17 @@ impl Utils { Ok((args, named)) } + #[inline] + fn create_exec(cmd: String, table: Table, data: Option) -> mlua::Result { + let (args, named) = Self::parse_args(table)?; + let mut exec = Exec { cmd, args, named, ..Default::default() }; + + if let Some(data) = data.and_then(|v| ValueSendable::try_from(v).ok()) { + exec = exec.with_data(data); + } + Ok(exec) + } + pub(super) fn call(lua: &Lua, ya: &Table) -> mlua::Result<()> { ya.set( "render", @@ -37,17 +48,18 @@ impl Utils { })?, )?; + ya.set( + "app_emit", + lua.create_function(|_, (cmd, table, data): (String, Table, Option)| { + emit!(Call(Self::create_exec(cmd, table, data)?, Layer::App)); + Ok(()) + })?, + )?; + ya.set( "manager_emit", lua.create_function(|_, (cmd, table, data): (String, Table, Option)| { - let (args, named) = Self::parse_args(table)?; - let mut exec = Exec { cmd, args, named, ..Default::default() }; - - if let Some(data) = data.and_then(|v| ValueSendable::try_from(v).ok()) { - exec = exec.with_data(data); - } - - emit!(Call(exec, Layer::Manager)); + emit!(Call(Self::create_exec(cmd, table, data)?, Layer::Manager)); Ok(()) })?, )?;