diff --git a/Cargo.lock b/Cargo.lock index e160058e..30d2c661 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5542,6 +5542,7 @@ dependencies = [ "libc", "mlua", "paste", + "ratatui", "scopeguard", "tokio", "tokio-stream", @@ -5551,6 +5552,7 @@ dependencies = [ "yazi-config", "yazi-core", "yazi-dds", + "yazi-emulator", "yazi-fs", "yazi-macro", "yazi-parser", @@ -5558,6 +5560,7 @@ dependencies = [ "yazi-proxy", "yazi-scheduler", "yazi-shared", + "yazi-term", "yazi-tty", "yazi-vfs", "yazi-watcher", @@ -5704,6 +5707,7 @@ dependencies = [ "indexmap 2.13.0", "parking_lot", "ratatui", + "serde", "tokio", "tokio-stream", "tokio-util", @@ -5868,6 +5872,7 @@ dependencies = [ "hashbrown 0.16.1", "mlua", "ordered-float 5.1.0", + "ratatui", "serde", "serde_with", "tokio", diff --git a/scripts/validate-form/main.js b/scripts/validate-form/main.js index 6413b4a5..5b231099 100644 --- a/scripts/validate-form/main.js +++ b/scripts/validate-form/main.js @@ -8,7 +8,7 @@ function bugReportBody(creator, content, hash) { return null } - return `Hey @${creator}, thank you for opening this issue to help us improve Yazi, appreciate it! + return `Hey @${creator}, thank you for opening the issue to help improve Yazi, appreciate it! I noticed that you did not correctly follow the issue template. Please ensure that: @@ -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 a personal capacity, and debug info helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thank you for the 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! ` } @@ -27,7 +27,7 @@ function featureRequestBody(creator, content) { return null } - return `Hey @${creator}, thank you for opening this issue to help us improve Yazi, appreciate it! + return `Hey @${creator}, thank you for opening the issue to help improve Yazi, appreciate it! I noticed that you did not correctly follow the issue template. Please ensure that: @@ -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 a personal capacity, and debug info helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thank you for the 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/Cargo.toml b/yazi-actor/Cargo.toml index 66a2e067..1c7f31a8 100644 --- a/yazi-actor/Cargo.toml +++ b/yazi-actor/Cargo.toml @@ -21,6 +21,7 @@ yazi-boot = { path = "../yazi-boot", version = "26.1.22" } yazi-config = { path = "../yazi-config", version = "26.1.22" } yazi-core = { path = "../yazi-core", version = "26.1.22" } yazi-dds = { path = "../yazi-dds", version = "26.1.22" } +yazi-emulator = { path = "../yazi-emulator", version = "26.1.22" } yazi-fs = { path = "../yazi-fs", version = "26.1.22" } yazi-macro = { path = "../yazi-macro", version = "26.1.22" } yazi-parser = { path = "../yazi-parser", version = "26.1.22" } @@ -28,6 +29,7 @@ yazi-plugin = { path = "../yazi-plugin", version = "26.1.22" } yazi-proxy = { path = "../yazi-proxy", version = "26.1.22" } yazi-scheduler = { path = "../yazi-scheduler", version = "26.1.22" } yazi-shared = { path = "../yazi-shared", version = "26.1.22" } +yazi-term = { path = "../yazi-term", version = "26.1.22" } yazi-tty = { path = "../yazi-tty", version = "26.1.22" } yazi-vfs = { path = "../yazi-vfs", version = "26.1.22" } yazi-watcher = { path = "../yazi-watcher", version = "26.1.22" } @@ -40,6 +42,7 @@ futures = { workspace = true } hashbrown = { workspace = true } mlua = { workspace = true } paste = { workspace = true } +ratatui = { workspace = true } scopeguard = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } diff --git a/yazi-fm/src/app/commands/accept_payload.rs b/yazi-actor/src/app/accept_payload.rs similarity index 70% rename from yazi-fm/src/app/commands/accept_payload.rs rename to yazi-actor/src/app/accept_payload.rs index 676782c6..b1a97d4c 100644 --- a/yazi-fm/src/app/commands/accept_payload.rs +++ b/yazi-actor/src/app/accept_payload.rs @@ -1,4 +1,4 @@ -use anyhow::{Result, bail}; +use anyhow::Result; use mlua::IntoLua; use tracing::error; use yazi_actor::lives::Lives; @@ -6,16 +6,18 @@ use yazi_binding::runtime_mut; use yazi_dds::{LOCAL, Payload, REMOTE}; use yazi_macro::succ; use yazi_plugin::LUA; -use yazi_shared::{data::Data, event::CmdCow}; +use yazi_shared::data::Data; -use crate::app::App; +use crate::{Actor, Ctx}; -impl App { - pub(crate) fn accept_payload(&self, mut c: CmdCow) -> Result { - let Some(payload) = c.take_any2::("payload").transpose()? else { - bail!("'payload' is required for accept_payload"); - }; +pub struct AcceptPayload; +impl Actor for AcceptPayload { + type Options = Payload<'static>; + + const NAME: &str = "accept_payload"; + + fn act(cx: &mut Ctx, payload: Payload) -> Result { let kind = payload.body.kind().to_owned(); let lock = if payload.receiver == 0 || payload.receiver != payload.sender { REMOTE.read() @@ -26,7 +28,7 @@ impl App { let Some(handlers) = lock.get(&kind).filter(|&m| !m.is_empty()).cloned() else { succ!() }; drop(lock); - succ!(Lives::scope(&self.core, || { + succ!(Lives::scope(&cx.core, || { let body = payload.body.into_lua(&LUA)?; for (id, cb) in handlers { let blocking = runtime_mut!(LUA)?.critical_push(&id, true); diff --git a/yazi-fm/src/app/commands/bootstrap.rs b/yazi-actor/src/app/bootstrap.rs similarity index 66% rename from yazi-fm/src/app/commands/bootstrap.rs rename to yazi-actor/src/app/bootstrap.rs index bb3e3bbb..b6c1210b 100644 --- a/yazi-fm/src/app/commands/bootstrap.rs +++ b/yazi-actor/src/app/bootstrap.rs @@ -1,30 +1,33 @@ use anyhow::Result; use yazi_actor::Ctx; use yazi_boot::BOOT; -use yazi_macro::act; +use yazi_macro::{act, succ}; use yazi_parser::{VoidOpt, mgr::CdSource}; use yazi_shared::{data::Data, strand::StrandLike, url::UrlLike}; -use crate::app::App; +use crate::Actor; -impl App { - pub fn bootstrap(&mut self, _: VoidOpt) -> Result { +pub struct Bootstrap; + +impl Actor for Bootstrap { + type Options = VoidOpt; + + const NAME: &str = "bootstrap"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { for (i, file) in BOOT.files.iter().enumerate() { - let tabs = &mut self.core.mgr.tabs; + let tabs = &mut cx.core.mgr.tabs; if tabs.len() <= i { tabs.push(Default::default()); } - let cx = &mut Ctx::active(&mut self.core); cx.tab = i; - if file.is_empty() { act!(mgr:cd, cx, (BOOT.cwds[i].clone(), CdSource::Tab))?; } else if let Ok(u) = BOOT.cwds[i].try_join(file) { act!(mgr:reveal, cx, (u, CdSource::Tab))?; } } - - act!(render, self) + succ!(); } } diff --git a/yazi-actor/src/app/deprecate.rs b/yazi-actor/src/app/deprecate.rs new file mode 100644 index 00000000..4f24b9b7 --- /dev/null +++ b/yazi-actor/src/app/deprecate.rs @@ -0,0 +1,23 @@ +use anyhow::Result; +use yazi_macro::act; +use yazi_parser::{app::DeprecateOpt, notify::{PushLevel, PushOpt}}; +use yazi_shared::data::Data; + +use crate::{Actor, Ctx}; + +pub struct Deprecate; + +impl Actor for Deprecate { + type Options = DeprecateOpt; + + const NAME: &str = "deprecate"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + act!(notify:push, cx, PushOpt { + title: "Deprecated API".to_owned(), + content: opt.content.into_owned(), + level: PushLevel::Warn, + timeout: std::time::Duration::from_secs(20), + }) + } +} diff --git a/yazi-actor/src/app/focus.rs b/yazi-actor/src/app/focus.rs new file mode 100644 index 00000000..fc5e0fa6 --- /dev/null +++ b/yazi-actor/src/app/focus.rs @@ -0,0 +1,17 @@ +use anyhow::Result; +use yazi_actor::Ctx; +use yazi_macro::act; +use yazi_parser::VoidOpt; +use yazi_shared::data::Data; + +use crate::Actor; + +pub struct Focus; + +impl Actor for Focus { + type Options = VoidOpt; + + const NAME: &str = "focus"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { act!(mgr:refresh, cx) } +} diff --git a/yazi-fm/src/app/commands/mod.rs b/yazi-actor/src/app/mod.rs similarity index 81% rename from yazi-fm/src/app/commands/mod.rs rename to yazi-actor/src/app/mod.rs index aed61646..9a954fd1 100644 --- a/yazi-fm/src/app/commands/mod.rs +++ b/yazi-actor/src/app/mod.rs @@ -4,14 +4,12 @@ yazi_macro::mod_flat!( deprecate focus mouse - notify plugin + plugin_do quit reflow - render resize resume stop - update_notify update_progress ); diff --git a/yazi-fm/src/app/commands/mouse.rs b/yazi-actor/src/app/mouse.rs similarity index 80% rename from yazi-fm/src/app/commands/mouse.rs rename to yazi-actor/src/app/mouse.rs index 2cba3fe2..77905fe2 100644 --- a/yazi-fm/src/app/commands/mouse.rs +++ b/yazi-actor/src/app/mouse.rs @@ -9,14 +9,20 @@ use yazi_parser::app::MouseOpt; use yazi_plugin::LUA; use yazi_shared::data::Data; -use crate::app::App; +use crate::{Actor, Ctx}; -impl App { - pub fn mouse(&mut self, opt: MouseOpt) -> Result { +pub struct Mouse; + +impl Actor for Mouse { + type Options = MouseOpt; + + const NAME: &str = "mouse"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { let event = yazi_plugin::bindings::MouseEvent::from(opt.event); - let Some(size) = self.term.as_ref().and_then(|t| t.size().ok()) else { succ!() }; + let Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() }; - let result = Lives::scope(&self.core, move || { + let result = Lives::scope(&cx.core, move || { let area = yazi_binding::elements::Rect::from(size); let root = LUA.globals().raw_get::("Root")?.call_method::
("new", area)?; diff --git a/yazi-actor/src/app/plugin.rs b/yazi-actor/src/app/plugin.rs new file mode 100644 index 00000000..0bb77a82 --- /dev/null +++ b/yazi-actor/src/app/plugin.rs @@ -0,0 +1,38 @@ +use anyhow::Result; +use yazi_macro::{act, succ}; +use yazi_parser::app::{PluginMode, PluginOpt}; +use yazi_plugin::loader::LOADER; +use yazi_proxy::{AppProxy, NotifyProxy}; +use yazi_shared::data::Data; + +use crate::{Actor, Ctx}; + +pub struct Plugin; + +impl Actor for Plugin { + type Options = PluginOpt; + + const NAME: &str = "plugin"; + + fn act(cx: &mut Ctx, mut opt: Self::Options) -> Result { + let mut hits = false; + if let Some(chunk) = LOADER.read().get(&*opt.id) { + hits = true; + opt.mode = opt.mode.auto_then(chunk.sync_entry); + } + + if opt.mode == PluginMode::Async { + succ!(cx.core.tasks.scheduler.plugin_entry(opt)); + } else if opt.mode == PluginMode::Sync && hits { + return act!(app:plugin_do, cx, opt); + } + + tokio::spawn(async move { + match LOADER.ensure(&opt.id, |_| ()).await { + Ok(()) => AppProxy::plugin_do(opt), + Err(e) => NotifyProxy::push_error("Plugin load failed", e), + } + }); + succ!(); + } +} diff --git a/yazi-fm/src/app/commands/plugin.rs b/yazi-actor/src/app/plugin_do.rs similarity index 56% rename from yazi-fm/src/app/commands/plugin.rs rename to yazi-actor/src/app/plugin_do.rs index a7f93356..518e22f8 100644 --- a/yazi-fm/src/app/commands/plugin.rs +++ b/yazi-actor/src/app/plugin_do.rs @@ -2,52 +2,35 @@ use anyhow::Result; use mlua::ObjectLike; use scopeguard::defer; use tracing::{error, warn}; -use yazi_actor::lives::Lives; use yazi_binding::runtime_mut; use yazi_dds::Sendable; use yazi_macro::succ; use yazi_parser::app::{PluginMode, PluginOpt}; use yazi_plugin::{LUA, loader::{LOADER, Loader}}; -use yazi_proxy::AppProxy; +use yazi_proxy::NotifyProxy; use yazi_shared::data::Data; -use crate::app::App; +use crate::{Actor, Ctx, lives::Lives}; -impl App { - pub(crate) fn plugin(&mut self, mut opt: PluginOpt) -> Result { - let mut hits = false; - if let Some(chunk) = LOADER.read().get(&*opt.id) { - hits = true; - opt.mode = opt.mode.auto_then(chunk.sync_entry); - } +pub struct PluginDo; - if opt.mode == PluginMode::Async { - succ!(self.core.tasks.plugin_entry(opt)); - } else if opt.mode == PluginMode::Sync && hits { - return self.plugin_do(opt); - } +impl Actor for PluginDo { + type Options = PluginOpt; - tokio::spawn(async move { - match LOADER.ensure(&opt.id, |_| ()).await { - Ok(()) => AppProxy::plugin_do(opt), - Err(e) => AppProxy::notify_error("Plugin load failed", e), - } - }); - succ!(); - } + const NAME: &str = "plugin_do"; - pub(crate) fn plugin_do(&mut self, opt: PluginOpt) -> Result { + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { let loader = LOADER.read(); let Some(chunk) = loader.get(&*opt.id) else { succ!(warn!("plugin `{}` not found", opt.id)); }; if let Err(e) = Loader::compatible_or_error(&opt.id, chunk) { - succ!(AppProxy::notify_error("Incompatible plugin", e)); + succ!(NotifyProxy::push_error("Incompatible plugin", e)); } if opt.mode.auto_then(chunk.sync_entry) != PluginMode::Sync { - succ!(self.core.tasks.plugin_entry(opt)); + succ!(cx.core.tasks.scheduler.plugin_entry(opt)); } let blocking = runtime_mut!(LUA)?.critical_push(&opt.id, true); @@ -59,7 +42,7 @@ impl App { }; drop(loader); - let result = Lives::scope(&self.core, || { + let result = Lives::scope(&cx.core, || { if let Some(cb) = opt.callback { cb(&LUA, plugin) } else { diff --git a/yazi-actor/src/app/quit.rs b/yazi-actor/src/app/quit.rs new file mode 100644 index 00000000..979b580a --- /dev/null +++ b/yazi-actor/src/app/quit.rs @@ -0,0 +1,47 @@ +use anyhow::Result; +use yazi_boot::ARGS; +use yazi_fs::provider::{Provider, local::Local}; +use yazi_parser::app::QuitOpt; +use yazi_shared::{data::Data, strand::{StrandBuf, StrandLike, ToStrand}}; +use yazi_term::Term; + +use crate::{Actor, Ctx}; + +pub struct Quit; + +impl Actor for Quit { + type Options = QuitOpt; + + const NAME: &str = "quit"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + cx.core.tasks.shutdown(); + cx.core.mgr.shutdown(); + + futures::executor::block_on(async { + _ = futures::join!( + yazi_dds::shutdown(), + yazi_dds::STATE.drain(), + Self::cwd_to_file(cx, opt.no_cwd_file), + Self::selected_to_file(opt.selected) + ); + }); + + Term::goodbye(|| opt.code); + } +} + +impl Quit { + async fn cwd_to_file(cx: &Ctx<'_>, no: bool) { + if let Some(p) = ARGS.cwd_file.as_ref().filter(|_| !no) { + let cwd = cx.core.mgr.cwd().to_strand(); + Local::regular(p).write(cwd.encoded_bytes()).await.ok(); + } + } + + async fn selected_to_file(selected: Option) { + if let (Some(s), Some(p)) = (selected, &ARGS.chooser_file) { + Local::regular(p).write(s.encoded_bytes()).await.ok(); + } + } +} diff --git a/yazi-fm/src/app/commands/reflow.rs b/yazi-actor/src/app/reflow.rs similarity index 70% rename from yazi-fm/src/app/commands/reflow.rs rename to yazi-actor/src/app/reflow.rs index a45a61b4..129bc2a3 100644 --- a/yazi-fm/src/app/commands/reflow.rs +++ b/yazi-actor/src/app/reflow.rs @@ -5,18 +5,24 @@ use tracing::error; use yazi_actor::lives::Lives; use yazi_config::LAYOUT; use yazi_macro::{render, succ}; -use yazi_parser::VoidOpt; +use yazi_parser::app::ReflowOpt; use yazi_shared::data::Data; -use crate::{Root, app::App}; +use crate::{Actor, Ctx}; -impl App { - pub fn reflow(&mut self, _: VoidOpt) -> Result { - let Some(size) = self.term.as_ref().and_then(|t| t.size().ok()) else { succ!() }; +pub struct Reflow; + +impl Actor for Reflow { + type Options = ReflowOpt; + + const NAME: &str = "reflow"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() }; let mut layout = LAYOUT.get(); - let result = Lives::scope(&self.core, || { - let comps = Root::reflow((Position::ORIGIN, size).into())?; + let result = Lives::scope(&cx.core, || { + let comps = (opt.reflow)((Position::ORIGIN, size).into())?; for v in comps.sequence_values::() { let Value::Table(t) = v? else { diff --git a/yazi-actor/src/app/resize.rs b/yazi-actor/src/app/resize.rs new file mode 100644 index 00000000..793f10fb --- /dev/null +++ b/yazi-actor/src/app/resize.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use yazi_actor::Ctx; +use yazi_macro::act; +use yazi_parser::app::ReflowOpt; +use yazi_shared::data::Data; + +use crate::Actor; + +pub struct Resize; + +impl Actor for Resize { + type Options = ReflowOpt; + + const NAME: &str = "resize"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + act!(app:reflow, cx, opt)?; + + cx.core.current_mut().arrow(0); + cx.core.parent_mut().map(|f| f.arrow(0)); + cx.core.current_mut().sync_page(true); + + act!(mgr:peek, cx) + } +} diff --git a/yazi-actor/src/app/resume.rs b/yazi-actor/src/app/resume.rs new file mode 100644 index 00000000..de217f8b --- /dev/null +++ b/yazi-actor/src/app/resume.rs @@ -0,0 +1,28 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::app::ResumeOpt; +use yazi_shared::data::Data; +use yazi_term::Term; + +use crate::{Actor, Ctx}; + +pub struct Resume; + +impl Actor for Resume { + type Options = ResumeOpt; + + const NAME: &str = "resume"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + cx.core.active_mut().preview.reset(); + *cx.term = Some(Term::start().unwrap()); + + // 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. + act!(app:resize, cx, opt.reflow)?; + + opt.tx.send((true, opt.token))?; + + succ!(render!()); + } +} diff --git a/yazi-fm/src/app/commands/stop.rs b/yazi-actor/src/app/stop.rs similarity index 51% rename from yazi-fm/src/app/commands/stop.rs rename to yazi-actor/src/app/stop.rs index 2456e619..30003482 100644 --- a/yazi-fm/src/app/commands/stop.rs +++ b/yazi-actor/src/app/stop.rs @@ -3,18 +3,24 @@ use yazi_macro::succ; use yazi_parser::app::StopOpt; use yazi_shared::data::Data; -use crate::app::App; +use crate::{Actor, Ctx}; -impl App { - pub fn stop(&mut self, opt: StopOpt) -> Result { - self.core.active_mut().preview.reset_image(); +pub struct Stop; + +impl Actor for Stop { + type Options = StopOpt; + + const NAME: &str = "stop"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + cx.core.active_mut().preview.reset_image(); // We need to destroy the `term` first before stopping the `signals` // to prevent any signal from triggering the term to render again // while the app is being suspended. - self.term = None; + *cx.term = None; - self.signals.stop(opt.token); + opt.tx.send((false, opt.token))?; succ!(); } diff --git a/yazi-fm/src/app/commands/update_progress.rs b/yazi-actor/src/app/update_progress.rs similarity index 63% rename from yazi-fm/src/app/commands/update_progress.rs rename to yazi-actor/src/app/update_progress.rs index 6931a12d..3af78984 100644 --- a/yazi-fm/src/app/commands/update_progress.rs +++ b/yazi-actor/src/app/update_progress.rs @@ -1,15 +1,21 @@ use anyhow::Result; use yazi_actor::Ctx; -use yazi_macro::{act, render, succ}; +use yazi_macro::{act, render, render_partial, succ}; use yazi_parser::app::UpdateProgressOpt; use yazi_shared::data::Data; -use crate::app::App; +use crate::Actor; -impl App { - pub(crate) fn update_progress(&mut self, opt: UpdateProgressOpt) -> Result { +pub struct UpdateProgress; + +impl Actor for UpdateProgress { + type Options = UpdateProgressOpt; + + const NAME: &str = "update_progress"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { // Update the progress of all tasks. - let tasks = &mut self.core.tasks; + let tasks = &mut cx.core.tasks; let progressed = tasks.summary != opt.summary; tasks.summary = opt.summary; @@ -18,7 +24,6 @@ impl App { let new = tasks.paginate(); if tasks.snaps != new { tasks.snaps = new; - let cx = &mut Ctx::active(&mut self.core); act!(tasks:arrow, cx)?; succ!(render!()); } @@ -29,7 +34,7 @@ impl App { } else if tasks.summary.total == 0 { succ!(render!()) } else { - act!(render_partially, self) + succ!(render_partial!()) } } } diff --git a/yazi-actor/src/context.rs b/yazi-actor/src/context.rs index 259c3160..6633428c 100644 --- a/yazi-actor/src/context.rs +++ b/yazi-actor/src/context.rs @@ -4,9 +4,11 @@ use anyhow::{Result, anyhow}; use yazi_core::{Core, mgr::Tabs, tab::{Folder, Tab}, tasks::Tasks}; use yazi_fs::File; use yazi_shared::{Id, Source, event::Cmd, url::UrlBuf}; +use yazi_term::Term; pub struct Ctx<'a> { pub core: &'a mut Core, + pub term: &'a mut Option, pub tab: usize, pub level: usize, pub source: Source, @@ -26,7 +28,7 @@ impl DerefMut for Ctx<'_> { impl<'a> Ctx<'a> { #[inline] - pub fn new(core: &'a mut Core, cmd: &Cmd) -> Result { + pub fn new(cmd: &Cmd, core: &'a mut Core, term: &'a mut Option) -> Result { let tab = if let Ok(id) = cmd.get::("tab") { core .mgr @@ -40,6 +42,7 @@ impl<'a> Ctx<'a> { Ok(Self { core, + term, tab, level: 0, source: cmd.source, @@ -53,6 +56,7 @@ impl<'a> Ctx<'a> { let tab = cx.core.mgr.tabs.cursor; Self { core: cx.core, + term: cx.term, tab, level: cx.level, source: cx.source, @@ -62,10 +66,11 @@ impl<'a> Ctx<'a> { } #[inline] - pub fn active(core: &'a mut Core) -> Self { + pub fn active(core: &'a mut Core, term: &'a mut Option) -> Self { let tab = core.mgr.tabs.cursor; Self { core, + term, tab, level: 0, source: Source::Unknown, diff --git a/yazi-actor/src/lib.rs b/yazi-actor/src/lib.rs index 95950dad..6706a76c 100644 --- a/yazi-actor/src/lib.rs +++ b/yazi-actor/src/lib.rs @@ -1,5 +1,5 @@ extern crate self as yazi_actor; -yazi_macro::mod_pub!(cmp confirm core help input lives mgr pick spot tasks which); +yazi_macro::mod_pub!(app cmp confirm core help input lives mgr notify pick spot tasks which); yazi_macro::mod_flat!(actor context); diff --git a/yazi-actor/src/mgr/bulk_rename.rs b/yazi-actor/src/mgr/bulk_rename.rs index ad0a99ca..db2c6a3b 100644 --- a/yazi-actor/src/mgr/bulk_rename.rs +++ b/yazi-actor/src/mgr/bulk_rename.rs @@ -11,7 +11,7 @@ use yazi_dds::Pubsub; use yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}}; use yazi_macro::{err, succ}; use yazi_parser::VoidOpt; -use yazi_proxy::{AppProxy, HIDER, TasksProxy}; +use yazi_proxy::{AppProxy, HIDER, NotifyProxy, TasksProxy}; use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, terminal_clear, url::{AsUrl, UrlBuf, UrlCow, UrlLike}}; use yazi_tty::TTY; use yazi_vfs::{VfsFile, maybe_exists, provider}; @@ -28,12 +28,12 @@ impl Actor for BulkRename { fn act(cx: &mut Ctx, _: Self::Options) -> Result { let Some(opener) = Self::opener() else { - succ!(AppProxy::notify_warn("Bulk rename", "No text opener found")); + succ!(NotifyProxy::push_warn("Bulk rename", "No text opener found")); }; let selected: Vec<_> = cx.tab().selected_or_hovered().cloned().collect(); if selected.is_empty() { - succ!(AppProxy::notify_warn("Bulk rename", "No files selected")); + succ!(NotifyProxy::push_warn("Bulk rename", "No files selected")); } let root = max_common_root(&selected); diff --git a/yazi-actor/src/mgr/escape.rs b/yazi-actor/src/mgr/escape.rs index ed4bbde0..d0b3db95 100644 --- a/yazi-actor/src/mgr/escape.rs +++ b/yazi-actor/src/mgr/escape.rs @@ -1,7 +1,7 @@ use anyhow::{Result, bail}; use yazi_macro::{act, render, render_and, succ}; use yazi_parser::{VoidOpt, mgr::EscapeOpt}; -use yazi_proxy::AppProxy; +use yazi_proxy::NotifyProxy; use yazi_shared::{data::Data, url::UrlLike}; use crate::{Actor, Ctx}; @@ -76,7 +76,7 @@ impl Actor for EscapeVisual { if !select { tab.selected.remove_many(urls); } else if urls.len() != tab.selected.add_many(urls) { - AppProxy::notify_warn( + NotifyProxy::push_warn( "Escape visual mode", "Some files cannot be selected, due to path nesting conflict.", ); diff --git a/yazi-actor/src/mgr/quit.rs b/yazi-actor/src/mgr/quit.rs index 37cb7428..70e72eb8 100644 --- a/yazi-actor/src/mgr/quit.rs +++ b/yazi-actor/src/mgr/quit.rs @@ -4,10 +4,10 @@ use anyhow::Result; use tokio::{select, time}; use yazi_config::popup::ConfirmCfg; use yazi_dds::spark::SparkKind; -use yazi_macro::{emit, succ}; -use yazi_parser::mgr::QuitOpt; -use yazi_proxy::ConfirmProxy; -use yazi_shared::{data::Data, event::EventQuit, strand::{Strand, StrandLike, ToStrandJoin}, url::AsUrl}; +use yazi_macro::{act, succ}; +use yazi_parser::app::QuitOpt; +use yazi_proxy::{AppProxy, ConfirmProxy}; +use yazi_shared::{data::Data, strand::{Strand, StrandLike, ToStrandJoin}, url::AsUrl}; use crate::{Actor, Ctx}; @@ -19,8 +19,6 @@ impl Actor for Quit { const NAME: &str = "quit"; fn act(cx: &mut Ctx, opt: Self::Options) -> Result { - let event = opt.into(); - let ongoing = cx.tasks().ongoing().clone(); let (left, left_names) = { let ongoing = ongoing.lock(); @@ -28,7 +26,7 @@ impl Actor for Quit { }; if left == 0 { - succ!(emit!(Quit(event))); + return act!(app:quit, cx, opt); } tokio::spawn(async move { @@ -40,13 +38,13 @@ impl Actor for Quit { i += 1; if i > 40 { break } else if ongoing.lock().is_empty() { - emit!(Quit(event)); + AppProxy::quit(opt); return; } } b = token.future() => { if b { - emit!(Quit(event)); + AppProxy::quit(opt); } return; } @@ -54,7 +52,7 @@ impl Actor for Quit { } if token.future().await { - emit!(Quit(event)); + AppProxy::quit(opt); } }); succ!(); @@ -73,7 +71,7 @@ impl Quit { { let paths = selected.into_iter().join(Strand::Utf8("\n")); if !paths.is_empty() { - emit!(Quit(EventQuit { selected: Some(paths), ..Default::default() })); + AppProxy::quit(QuitOpt { selected: Some(paths), ..Default::default() }); } } } diff --git a/yazi-actor/src/mgr/search.rs b/yazi-actor/src/mgr/search.rs index af526351..c9362254 100644 --- a/yazi-actor/src/mgr/search.rs +++ b/yazi-actor/src/mgr/search.rs @@ -8,7 +8,7 @@ use yazi_fs::{FilesOp, cha::Cha}; use yazi_macro::{act, succ}; use yazi_parser::{VoidOpt, mgr::{CdSource, SearchOpt, SearchOptVia}}; use yazi_plugin::external; -use yazi_proxy::{AppProxy, InputProxy, MgrProxy}; +use yazi_proxy::{InputProxy, MgrProxy, NotifyProxy}; use yazi_shared::{data::Data, url::UrlLike}; use crate::{Actor, Ctx}; @@ -54,7 +54,7 @@ impl Actor for SearchDo { let hidden = tab.pref.show_hidden; let Ok(cwd) = tab.cwd().to_search(&opt.subject) else { - succ!(AppProxy::notify_warn("Search", "Only local filesystem searches are supported")); + succ!(NotifyProxy::push_warn("Search", "Only local filesystem searches are supported")); }; tab.search = Some(tokio::spawn(async move { diff --git a/yazi-actor/src/mgr/tab_create.rs b/yazi-actor/src/mgr/tab_create.rs index 835baf46..1cda68a0 100644 --- a/yazi-actor/src/mgr/tab_create.rs +++ b/yazi-actor/src/mgr/tab_create.rs @@ -2,7 +2,7 @@ use anyhow::Result; use yazi_core::tab::Tab; use yazi_macro::{act, render, succ}; use yazi_parser::mgr::{CdSource, TabCreateOpt}; -use yazi_proxy::AppProxy; +use yazi_proxy::NotifyProxy; use yazi_shared::{data::Data, url::UrlLike}; use crate::{Actor, Ctx}; @@ -18,7 +18,7 @@ impl Actor for TabCreate { fn act(cx: &mut Ctx, opt: Self::Options) -> Result { if cx.tabs().len() >= MAX_TABS { - succ!(AppProxy::notify_warn( + succ!(NotifyProxy::push_warn( "Too many tabs", "You can only open up to 9 tabs at the same time." )); diff --git a/yazi-actor/src/mgr/toggle.rs b/yazi-actor/src/mgr/toggle.rs index 153b2296..8dd6b729 100644 --- a/yazi-actor/src/mgr/toggle.rs +++ b/yazi-actor/src/mgr/toggle.rs @@ -1,7 +1,7 @@ use anyhow::Result; use yazi_macro::{render_and, succ}; use yazi_parser::mgr::ToggleOpt; -use yazi_proxy::AppProxy; +use yazi_proxy::NotifyProxy; use yazi_shared::{data::Data, url::UrlCow}; use crate::{Actor, Ctx}; @@ -26,7 +26,7 @@ impl Actor for Toggle { }; if !b { - AppProxy::notify_warn( + NotifyProxy::push_warn( "Toggle", "This file cannot be selected, due to path nesting conflict.", ); diff --git a/yazi-actor/src/mgr/toggle_all.rs b/yazi-actor/src/mgr/toggle_all.rs index 54ea0e0d..79b0e9f5 100644 --- a/yazi-actor/src/mgr/toggle_all.rs +++ b/yazi-actor/src/mgr/toggle_all.rs @@ -1,7 +1,7 @@ use anyhow::Result; use yazi_macro::{render, succ}; use yazi_parser::mgr::ToggleAllOpt; -use yazi_proxy::AppProxy; +use yazi_proxy::NotifyProxy; use yazi_shared::data::Data; use crate::{Actor, Ctx}; @@ -39,7 +39,7 @@ impl Actor for ToggleAll { }; if warn { - AppProxy::notify_warn( + NotifyProxy::push_warn( "Toggle all", "Some files cannot be selected, due to path nesting conflict.", ); diff --git a/yazi-actor/src/mgr/unyank.rs b/yazi-actor/src/mgr/unyank.rs index 21f66e18..0f3fb723 100644 --- a/yazi-actor/src/mgr/unyank.rs +++ b/yazi-actor/src/mgr/unyank.rs @@ -3,7 +3,7 @@ use yazi_macro::{act, render, succ}; use yazi_parser::VoidOpt; use yazi_shared::data::Data; -use crate::Actor; +use crate::{Actor, Ctx}; pub struct Unyank; @@ -12,7 +12,7 @@ impl Actor for Unyank { const NAME: &str = "unyank"; - fn act(cx: &mut crate::Ctx, _: Self::Options) -> Result { + fn act(cx: &mut Ctx, _: Self::Options) -> Result { let repeek = cx.hovered().is_some_and(|f| f.is_dir() && cx.mgr.yanked.contains_in(&f.url)); cx.mgr.yanked.clear(); diff --git a/yazi-actor/src/mgr/update_peeked.rs b/yazi-actor/src/mgr/update_peeked.rs index 0fa01a6b..ab32a2e0 100644 --- a/yazi-actor/src/mgr/update_peeked.rs +++ b/yazi-actor/src/mgr/update_peeked.rs @@ -3,7 +3,7 @@ use yazi_macro::{render, succ}; use yazi_parser::mgr::UpdatePeekedOpt; use yazi_shared::data::Data; -use crate::Actor; +use crate::{Actor, Ctx}; pub struct UpdatePeeked; @@ -12,7 +12,7 @@ impl Actor for UpdatePeeked { const NAME: &str = "update_peeked"; - fn act(cx: &mut crate::Ctx, opt: Self::Options) -> Result { + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { let Some(hovered) = cx.hovered().map(|h| &h.url) else { succ!(cx.tab_mut().preview.reset()); }; diff --git a/yazi-core/src/notify/commands/mod.rs b/yazi-actor/src/notify/mod.rs similarity index 100% rename from yazi-core/src/notify/commands/mod.rs rename to yazi-actor/src/notify/mod.rs diff --git a/yazi-actor/src/notify/push.rs b/yazi-actor/src/notify/push.rs new file mode 100644 index 00000000..defe333a --- /dev/null +++ b/yazi-actor/src/notify/push.rs @@ -0,0 +1,31 @@ +use std::time::{Duration, Instant}; + +use anyhow::Result; +use yazi_core::notify::Message; +use yazi_macro::succ; +use yazi_parser::notify::PushOpt; +use yazi_proxy::NotifyProxy; +use yazi_shared::data::Data; + +use crate::{Actor, Ctx}; + +pub struct Push; + +impl Actor for Push { + type Options = PushOpt; + + const NAME: &str = "push"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let instant = Instant::now(); + + let mut msg = Message::from(opt); + msg.timeout += instant - cx.notify.messages.first().map_or(instant, |m| m.instant); + + if cx.notify.messages.iter().all(|m| m != &msg) { + cx.notify.messages.push(msg); + NotifyProxy::tick(Duration::ZERO); + } + succ!(); + } +} diff --git a/yazi-actor/src/notify/tick.rs b/yazi-actor/src/notify/tick.rs new file mode 100644 index 00000000..67964f91 --- /dev/null +++ b/yazi-actor/src/notify/tick.rs @@ -0,0 +1,64 @@ +use std::time::Duration; + +use anyhow::Result; +use ratatui::layout::Rect; +use yazi_core::notify::Notify; +use yazi_emulator::Dimension; +use yazi_macro::succ; +use yazi_parser::notify::TickOpt; +use yazi_proxy::NotifyProxy; +use yazi_shared::data::Data; + +use crate::{Actor, Ctx}; + +pub struct Tick; + +impl Actor for Tick { + type Options = TickOpt; + + const NAME: &str = "tick"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + cx.notify.tick_handle.take().map(|h| h.abort()); + + let Dimension { rows, columns, .. } = Dimension::available(); + let area = Notify::available(Rect { x: 0, y: 0, width: columns, height: rows }); + + let limit = cx.notify.limit(area); + if limit == 0 { + succ!(); + } + + for m in &mut cx.notify.messages[..limit] { + if m.timeout.is_zero() { + m.percent = m.percent.saturating_sub(20); + } else if m.percent < 100 { + m.percent += 20; + } else { + m.timeout = m.timeout.saturating_sub(opt.interval); + } + } + + cx.notify.messages.retain(|m| m.percent > 0 || !m.timeout.is_zero()); + let limit = cx.notify.limit(area); + let timeouts: Vec<_> = cx.notify.messages[..limit] + .iter() + .filter(|&m| m.percent == 100 && !m.timeout.is_zero()) + .map(|m| m.timeout) + .collect(); + + let interval = if timeouts.len() != limit { + Duration::from_millis(50) + } else if let Some(min) = timeouts.iter().min() { + *min + } else { + succ!(); + }; + + cx.notify.tick_handle = Some(tokio::spawn(async move { + tokio::time::sleep(interval).await; + NotifyProxy::tick(interval); + })); + succ!(); + } +} diff --git a/yazi-config/src/keymap/keymap.rs b/yazi-config/src/keymap/keymap.rs index 79de6303..4d922026 100644 --- a/yazi-config/src/keymap/keymap.rs +++ b/yazi-config/src/keymap/keymap.rs @@ -31,6 +31,7 @@ impl Keymap { Layer::Help => &self.help, Layer::Cmp => &self.cmp, Layer::Which => &[], + Layer::Notify => &[], } } } diff --git a/yazi-core/Cargo.toml b/yazi-core/Cargo.toml index cc64d624..ef6ce320 100644 --- a/yazi-core/Cargo.toml +++ b/yazi-core/Cargo.toml @@ -34,6 +34,7 @@ hashbrown = { workspace = true } indexmap = { workspace = true } parking_lot = { workspace = true } ratatui = { workspace = true } +serde = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } tokio-util = { workspace = true } diff --git a/yazi-core/src/notify/commands/push.rs b/yazi-core/src/notify/commands/push.rs deleted file mode 100644 index f52ac080..00000000 --- a/yazi-core/src/notify/commands/push.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::time::{Duration, Instant}; - -use yazi_parser::app::NotifyOpt; -use yazi_proxy::AppProxy; - -use crate::notify::{Message, Notify}; - -impl Notify { - pub fn push(&mut self, opt: NotifyOpt) { - let instant = Instant::now(); - - let mut msg = Message::from(opt); - msg.timeout += instant - self.messages.first().map_or(instant, |m| m.instant); - - if self.messages.iter().all(|m| m != &msg) { - self.messages.push(msg); - AppProxy::update_notify(Duration::ZERO); - } - } -} diff --git a/yazi-core/src/notify/commands/tick.rs b/yazi-core/src/notify/commands/tick.rs deleted file mode 100644 index 0a513105..00000000 --- a/yazi-core/src/notify/commands/tick.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::time::Duration; - -use ratatui::layout::Rect; -use yazi_parser::notify::TickOpt; -use yazi_proxy::AppProxy; - -use crate::notify::Notify; - -impl Notify { - pub fn tick(&mut self, opt: TickOpt, area: Rect) { - self.tick_handle.take().map(|h| h.abort()); - - let limit = self.limit(area); - if limit == 0 { - return; - } - - for m in &mut self.messages[..limit] { - if m.timeout.is_zero() { - m.percent = m.percent.saturating_sub(20); - } else if m.percent < 100 { - m.percent += 20; - } else { - m.timeout = m.timeout.saturating_sub(opt.interval); - } - } - - self.messages.retain(|m| m.percent > 0 || !m.timeout.is_zero()); - let limit = self.limit(area); - let timeouts: Vec<_> = self.messages[..limit] - .iter() - .filter(|&m| m.percent == 100 && !m.timeout.is_zero()) - .map(|m| m.timeout) - .collect(); - - let interval = if timeouts.len() != limit { - Duration::from_millis(50) - } else if let Some(min) = timeouts.iter().min() { - *min - } else { - return; - }; - - self.tick_handle = Some(tokio::spawn(async move { - tokio::time::sleep(interval).await; - AppProxy::update_notify(interval); - })); - } -} diff --git a/yazi-core/src/notify/message.rs b/yazi-core/src/notify/message.rs index 31ceb4df..a2a3499d 100644 --- a/yazi-core/src/notify/message.rs +++ b/yazi-core/src/notify/message.rs @@ -1,14 +1,14 @@ use std::time::{Duration, Instant}; use unicode_width::UnicodeWidthStr; -use yazi_parser::app::{NotifyLevel, NotifyOpt}; +use yazi_parser::notify::{PushLevel, PushOpt}; use super::NOTIFY_BORDER; pub struct Message { pub title: String, pub content: String, - pub level: NotifyLevel, + pub level: PushLevel, pub timeout: Duration, pub instant: Instant, @@ -16,8 +16,8 @@ pub struct Message { pub max_width: usize, } -impl From for Message { - fn from(opt: NotifyOpt) -> Self { +impl From for Message { + fn from(opt: PushOpt) -> Self { let title = opt.title.lines().next().unwrap_or_default(); let title_width = title.width() + (opt.level.icon().width() + /* Space */ 1); diff --git a/yazi-core/src/notify/mod.rs b/yazi-core/src/notify/mod.rs index d98ded95..e5e385b7 100644 --- a/yazi-core/src/notify/mod.rs +++ b/yazi-core/src/notify/mod.rs @@ -1,5 +1,3 @@ -yazi_macro::mod_pub!(commands); - yazi_macro::mod_flat!(message notify); pub const NOTIFY_BORDER: u16 = 2; diff --git a/yazi-core/src/notify/notify.rs b/yazi-core/src/notify/notify.rs index 27b7a227..44cbb515 100644 --- a/yazi-core/src/notify/notify.rs +++ b/yazi-core/src/notify/notify.rs @@ -1,17 +1,23 @@ use std::ops::ControlFlow; -use ratatui::layout::Rect; +use ratatui::layout::{Constraint, Layout, Rect}; use tokio::task::JoinHandle; use super::{Message, NOTIFY_SPACING}; #[derive(Default)] pub struct Notify { - pub(super) tick_handle: Option>, - pub messages: Vec, + pub tick_handle: Option>, + pub messages: Vec, } impl Notify { + pub fn available(area: Rect) -> Rect { + let chunks = Layout::horizontal([Constraint::Fill(1), Constraint::Min(80)]).split(area); + let chunks = Layout::vertical([Constraint::Fill(1), Constraint::Max(1)]).split(chunks[1]); + chunks[0] + } + pub fn limit(&self, area: Rect) -> usize { if self.messages.is_empty() { return 0; diff --git a/yazi-core/src/tasks/mod.rs b/yazi-core/src/tasks/mod.rs index 824981f0..96e1b79a 100644 --- a/yazi-core/src/tasks/mod.rs +++ b/yazi-core/src/tasks/mod.rs @@ -1,4 +1,4 @@ -yazi_macro::mod_flat!(file plugin prework process tasks); +yazi_macro::mod_flat!(file prework process tasks); pub const TASKS_BORDER: u16 = 2; pub const TASKS_PADDING: u16 = 2; diff --git a/yazi-core/src/tasks/plugin.rs b/yazi-core/src/tasks/plugin.rs deleted file mode 100644 index d8fd904f..00000000 --- a/yazi-core/src/tasks/plugin.rs +++ /dev/null @@ -1,7 +0,0 @@ -use yazi_parser::app::PluginOpt; - -use super::Tasks; - -impl Tasks { - pub fn plugin_entry(&self, opt: PluginOpt) { self.scheduler.plugin_entry(opt); } -} diff --git a/yazi-dds/src/payload.rs b/yazi-dds/src/payload.rs index 49edbf05..4dc5f43f 100644 --- a/yazi-dds/src/payload.rs +++ b/yazi-dds/src/payload.rs @@ -1,11 +1,12 @@ use std::{fmt::Display, io::Write, str::FromStr}; use anyhow::{Result, anyhow}; +use mlua::{IntoLua, Lua, Value}; use yazi_boot::BOOT; use yazi_macro::{emit, relay}; -use yazi_shared::Id; +use yazi_shared::{Id, event::CmdCow}; -use crate::{ID, ember::Ember}; +use crate::{ID, ember::Ember, spark::Spark}; #[derive(Clone, Debug)] pub struct Payload<'a> { @@ -76,6 +77,25 @@ impl<'a> From> for Payload<'a> { fn from(value: Ember<'a>) -> Self { Self::new(value) } } +impl<'a> TryFrom> for Payload<'a> { + type Error = (); + + fn try_from(value: Spark<'a>) -> Result { + match value { + Spark::AppAcceptPayload(payload) => Ok(payload), + _ => Err(()), + } + } +} + +impl TryFrom for Payload<'_> { + type Error = anyhow::Error; + + fn try_from(mut c: CmdCow) -> Result { + c.take_any2("payload").ok_or_else(|| anyhow!("Missing 'payload' in Payload"))? + } +} + impl Display for Payload<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let result = match &self.body { @@ -104,3 +124,15 @@ impl Display for Payload<'_> { } } } + +impl<'a> IntoLua for Payload<'a> { + fn into_lua(self, lua: &Lua) -> mlua::Result { + lua + .create_table_from([ + ("receiver", yazi_binding::Id(self.receiver).into_lua(lua)?), + ("sender", yazi_binding::Id(self.sender).into_lua(lua)?), + ("body", self.body.into_lua(lua)?), + ])? + .into_lua(lua) + } +} diff --git a/yazi-dds/src/spark/spark.rs b/yazi-dds/src/spark/spark.rs index fa0b11cd..53cd8a77 100644 --- a/yazi-dds/src/spark/spark.rs +++ b/yazi-dds/src/spark/spark.rs @@ -7,6 +7,21 @@ pub enum Spark<'a> { // Void Void(yazi_parser::VoidOpt), + // App + AppAcceptPayload(crate::Payload<'a>), + AppBootstrap(yazi_parser::VoidOpt), + AppDeprecate(yazi_parser::app::DeprecateOpt), + AppFocus(yazi_parser::VoidOpt), + AppMouse(yazi_parser::app::MouseOpt), + AppPlugin(yazi_parser::app::PluginOpt), + AppPluginDo(yazi_parser::app::PluginOpt), + AppQuit(yazi_parser::app::QuitOpt), + AppReflow(yazi_parser::app::ReflowOpt), + AppResize(yazi_parser::app::ReflowOpt), + AppResume(yazi_parser::app::ResumeOpt), + AppStop(yazi_parser::app::StopOpt), + AppUpdateProgress(yazi_parser::app::UpdateProgressOpt), + // Mgr Arrow(yazi_parser::ArrowOpt), Back(yazi_parser::VoidOpt), @@ -42,7 +57,7 @@ pub enum Spark<'a> { OpenDo(yazi_parser::mgr::OpenDoOpt), Paste(yazi_parser::mgr::PasteOpt), Peek(yazi_parser::mgr::PeekOpt), - Quit(yazi_parser::mgr::QuitOpt), + Quit(yazi_parser::app::QuitOpt), Refresh(yazi_parser::VoidOpt), Remove(yazi_parser::mgr::RemoveOpt), RemoveDo(yazi_parser::mgr::RemoveOpt), @@ -107,6 +122,7 @@ pub enum Spark<'a> { InputShow(yazi_parser::input::ShowOpt), // Notify + NotifyPush(yazi_parser::notify::PushOpt), NotifyTick(yazi_parser::notify::TickOpt), // Pick @@ -160,6 +176,21 @@ impl<'a> IntoLua for Spark<'a> { // Void Self::Void(b) => b.into_lua(lua), + // App + Self::AppAcceptPayload(b) => b.into_lua(lua), + Self::AppBootstrap(b) => b.into_lua(lua), + Self::AppDeprecate(b) => b.into_lua(lua), + Self::AppFocus(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), + Self::AppQuit(b) => b.into_lua(lua), + Self::AppReflow(b) => b.into_lua(lua), + Self::AppResize(b) => b.into_lua(lua), + Self::AppResume(b) => b.into_lua(lua), + Self::AppStop(b) => b.into_lua(lua), + Self::AppUpdateProgress(b) => b.into_lua(lua), + // Mgr Self::Arrow(b) => b.into_lua(lua), Self::Back(b) => b.into_lua(lua), @@ -260,6 +291,7 @@ impl<'a> IntoLua for Spark<'a> { Self::InputShow(b) => b.into_lua(lua), // Notify + Self::NotifyPush(b) => b.into_lua(lua), Self::NotifyTick(b) => b.into_lua(lua), // Pick @@ -292,6 +324,8 @@ impl<'a> IntoLua for Spark<'a> { try_from_spark!( VoidOpt, + app:bootstrap, + app:focus, mgr:back, mgr:bulk_rename, mgr:enter, @@ -309,7 +343,17 @@ try_from_spark!( mgr:unyank, mgr:watch ); + +// App try_from_spark!(ArrowOpt, mgr:arrow, mgr:tab_swap); +try_from_spark!(app::DeprecateOpt, app:deprecate); +try_from_spark!(app::MouseOpt, app:mouse); +try_from_spark!(app::PluginOpt, app:plugin, app:plugin_do); +try_from_spark!(app::QuitOpt, app:quit, mgr:quit); +try_from_spark!(app::ReflowOpt, app:reflow, app:resize); +try_from_spark!(app::ResumeOpt, app:resume); +try_from_spark!(app::StopOpt, app:stop); +try_from_spark!(app::UpdateProgressOpt, app:update_progress); try_from_spark!(cmp::CloseOpt, cmp:close); try_from_spark!(cmp::ShowOpt, cmp:show); try_from_spark!(cmp::TriggerOpt, cmp:trigger); @@ -347,7 +391,6 @@ try_from_spark!(mgr::OpenDoOpt, mgr:open_do); try_from_spark!(mgr::OpenOpt, mgr:open); try_from_spark!(mgr::PasteOpt, mgr:paste); try_from_spark!(mgr::PeekOpt, mgr:peek); -try_from_spark!(mgr::QuitOpt, mgr:quit); try_from_spark!(mgr::RemoveOpt, mgr:remove, mgr:remove_do); try_from_spark!(mgr::RenameOpt, mgr:rename); try_from_spark!(mgr::RevealOpt, mgr:reveal); @@ -371,11 +414,12 @@ try_from_spark!(mgr::UpdateYankedOpt<'a>, mgr:update_yanked); try_from_spark!(mgr::UploadOpt, mgr:upload); try_from_spark!(mgr::VisualModeOpt, mgr:visual_mode); try_from_spark!(mgr::YankOpt, mgr:yank); +try_from_spark!(notify::PushOpt, notify:push); try_from_spark!(notify::TickOpt, notify:tick); try_from_spark!(pick::CloseOpt, pick:close); try_from_spark!(pick::ShowOpt, pick:show); try_from_spark!(spot::CopyOpt, spot:copy); try_from_spark!(tasks::ProcessOpenOpt, tasks:process_open); try_from_spark!(tasks::UpdateSucceedOpt, tasks:update_succeed); -try_from_spark!(which::CallbackOpt, which:callback); try_from_spark!(which::ActivateOpt, which:activate); +try_from_spark!(which::CallbackOpt, which:callback); diff --git a/yazi-fm/src/app/app.rs b/yazi-fm/src/app/app.rs index 6bbade6d..9e14b296 100644 --- a/yazi-fm/src/app/app.rs +++ b/yazi-fm/src/app/app.rs @@ -2,9 +2,10 @@ use std::{sync::atomic::Ordering, time::{Duration, Instant}}; use anyhow::Result; use tokio::{select, time::sleep}; +use yazi_actor::Ctx; use yazi_core::Core; use yazi_macro::act; -use yazi_shared::event::{Event, NEED_RENDER}; +use yazi_shared::{data::Data, event::{Event, NEED_RENDER}}; use yazi_term::Term; use crate::{Dispatcher, Signals}; @@ -21,21 +22,23 @@ impl App { let (mut rx, signals) = (Event::take(), Signals::start()?); let mut app = Self { core: Core::make(), term: Some(term), signals }; - act!(bootstrap, app)?; + app.bootstrap()?; let mut events = Vec::with_capacity(50); - let (mut timeout, mut last_render) = (None, Instant::now()); + let (mut timeout, mut need_render, mut last_render) = (None, 0, Instant::now()); macro_rules! drain_events { () => { for event in events.drain(..) { Dispatcher::new(&mut app).dispatch(event)?; - if !NEED_RENDER.load(Ordering::Relaxed) { + + need_render = NEED_RENDER.load(Ordering::Relaxed); + if need_render == 0 { continue; } timeout = Duration::from_millis(10).checked_sub(last_render.elapsed()); if timeout.is_none() { - act!(render, app)?; + app.render(need_render == 2)?; last_render = Instant::now(); } } @@ -46,7 +49,7 @@ impl App { if let Some(t) = timeout.take() { select! { _ = sleep(t) => { - act!(render, app)?; + app.render(need_render == 2)?; last_render = Instant::now(); } n = rx.recv_many(&mut events, 50) => { @@ -62,4 +65,11 @@ impl App { } Ok(()) } + + fn bootstrap(&mut self) -> anyhow::Result { + let cx = &mut Ctx::active(&mut self.core, &mut self.term); + act!(app:bootstrap, cx)?; + + self.render(false) + } } diff --git a/yazi-fm/src/app/commands/deprecate.rs b/yazi-fm/src/app/commands/deprecate.rs deleted file mode 100644 index 5537a547..00000000 --- a/yazi-fm/src/app/commands/deprecate.rs +++ /dev/null @@ -1,17 +0,0 @@ -use anyhow::Result; -use yazi_macro::succ; -use yazi_parser::app::{DeprecateOpt, NotifyLevel, NotifyOpt}; -use yazi_shared::data::Data; - -use crate::app::App; - -impl App { - pub(crate) fn deprecate(&mut self, opt: DeprecateOpt) -> Result { - succ!(self.core.notify.push(NotifyOpt { - title: "Deprecated API".to_owned(), - content: opt.content.into_owned(), - level: NotifyLevel::Warn, - timeout: std::time::Duration::from_secs(20), - })); - } -} diff --git a/yazi-fm/src/app/commands/focus.rs b/yazi-fm/src/app/commands/focus.rs deleted file mode 100644 index 8bbff642..00000000 --- a/yazi-fm/src/app/commands/focus.rs +++ /dev/null @@ -1,15 +0,0 @@ -use anyhow::Result; -use yazi_actor::Ctx; -use yazi_macro::act; -use yazi_parser::VoidOpt; -use yazi_shared::data::Data; - -use crate::app::App; - -impl App { - pub fn focus(&mut self, _: VoidOpt) -> Result { - let cx = &mut Ctx::active(&mut self.core); - - act!(mgr:refresh, cx) - } -} diff --git a/yazi-fm/src/app/commands/notify.rs b/yazi-fm/src/app/commands/notify.rs deleted file mode 100644 index 49ad97dc..00000000 --- a/yazi-fm/src/app/commands/notify.rs +++ /dev/null @@ -1,12 +0,0 @@ -use anyhow::Result; -use yazi_macro::succ; -use yazi_parser::app::NotifyOpt; -use yazi_shared::data::Data; - -use crate::app::App; - -impl App { - pub(crate) fn notify(&mut self, opt: NotifyOpt) -> Result { - succ!(self.core.notify.push(opt)); - } -} diff --git a/yazi-fm/src/app/commands/quit.rs b/yazi-fm/src/app/commands/quit.rs deleted file mode 100644 index 302c504e..00000000 --- a/yazi-fm/src/app/commands/quit.rs +++ /dev/null @@ -1,37 +0,0 @@ -use yazi_boot::ARGS; -use yazi_fs::provider::{Provider, local::Local}; -use yazi_shared::{event::EventQuit, strand::{StrandBuf, StrandLike, ToStrand}}; -use yazi_term::Term; - -use crate::app::App; - -impl App { - pub(crate) fn quit(&mut self, opt: EventQuit) -> ! { - self.core.tasks.shutdown(); - self.core.mgr.shutdown(); - - futures::executor::block_on(async { - _ = futures::join!( - yazi_dds::shutdown(), - yazi_dds::STATE.drain(), - self.cwd_to_file(opt.no_cwd_file), - self.selected_to_file(opt.selected) - ); - }); - - Term::goodbye(|| opt.code); - } - - async fn cwd_to_file(&self, no: bool) { - if let Some(p) = ARGS.cwd_file.as_ref().filter(|_| !no) { - let cwd = self.core.mgr.cwd().to_strand(); - Local::regular(p).write(cwd.encoded_bytes()).await.ok(); - } - } - - async fn selected_to_file(&self, selected: Option) { - if let (Some(s), Some(p)) = (selected, &ARGS.chooser_file) { - Local::regular(p).write(s.encoded_bytes()).await.ok(); - } - } -} diff --git a/yazi-fm/src/app/commands/resize.rs b/yazi-fm/src/app/commands/resize.rs deleted file mode 100644 index 24ab0a42..00000000 --- a/yazi-fm/src/app/commands/resize.rs +++ /dev/null @@ -1,20 +0,0 @@ -use anyhow::Result; -use yazi_actor::Ctx; -use yazi_macro::act; -use yazi_parser::VoidOpt; -use yazi_shared::data::Data; - -use crate::app::App; - -impl App { - pub fn resize(&mut self, _: VoidOpt) -> Result { - act!(reflow, self)?; - - self.core.current_mut().arrow(0); - self.core.parent_mut().map(|f| f.arrow(0)); - self.core.current_mut().sync_page(true); - - let cx = &mut Ctx::active(&mut self.core); - act!(mgr:peek, cx) - } -} diff --git a/yazi-fm/src/app/commands/resume.rs b/yazi-fm/src/app/commands/resume.rs deleted file mode 100644 index f54db7bd..00000000 --- a/yazi-fm/src/app/commands/resume.rs +++ /dev/null @@ -1,22 +0,0 @@ -use anyhow::Result; -use yazi_macro::{act, render, succ}; -use yazi_parser::app::ResumeOpt; -use yazi_shared::data::Data; -use yazi_term::Term; - -use crate::app::App; - -impl App { - pub(crate) fn resume(&mut self, opt: ResumeOpt) -> Result { - self.core.active_mut().preview.reset(); - self.term = Some(Term::start().unwrap()); - - // 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. - act!(resize, self)?; - - self.signals.resume(opt.token); - - succ!(render!()); - } -} diff --git a/yazi-fm/src/app/commands/update_notify.rs b/yazi-fm/src/app/commands/update_notify.rs deleted file mode 100644 index 317adff7..00000000 --- a/yazi-fm/src/app/commands/update_notify.rs +++ /dev/null @@ -1,24 +0,0 @@ -use anyhow::Result; -use ratatui::layout::Rect; -use yazi_emulator::Dimension; -use yazi_macro::act; -use yazi_parser::notify::TickOpt; -use yazi_shared::data::Data; - -use crate::{app::App, notify}; - -impl App { - pub(crate) fn update_notify(&mut self, opt: TickOpt) -> Result { - let Dimension { rows, columns, .. } = Dimension::available(); - let area = - notify::Notify::available(Rect { x: 0, y: 0, width: columns, height: rows }); - - self.core.notify.tick(opt, area); - - if self.core.notify.messages.is_empty() { - act!(render, self) - } else { - act!(render_partially, self) - } - } -} diff --git a/yazi-fm/src/app/mod.rs b/yazi-fm/src/app/mod.rs index f3a7d810..723744fb 100644 --- a/yazi-fm/src/app/mod.rs +++ b/yazi-fm/src/app/mod.rs @@ -1,3 +1 @@ -yazi_macro::mod_pub!(commands); - -yazi_macro::mod_flat!(app); +yazi_macro::mod_flat!(app render); diff --git a/yazi-fm/src/app/commands/render.rs b/yazi-fm/src/app/render.rs similarity index 88% rename from yazi-fm/src/app/commands/render.rs rename to yazi-fm/src/app/render.rs index cc8836b5..1fc83ffa 100644 --- a/yazi-fm/src/app/commands/render.rs +++ b/yazi-fm/src/app/render.rs @@ -6,17 +6,20 @@ use ratatui::{CompletedFrame, backend::{Backend, CrosstermBackend}, buffer::Buff use yazi_actor::{Ctx, lives::Lives}; use yazi_binding::elements::COLLISION; use yazi_macro::{act, succ}; -use yazi_parser::VoidOpt; use yazi_shared::{data::Data, event::NEED_RENDER}; use yazi_tty::TTY; use crate::{app::App, root::Root}; impl App { - pub(crate) fn render(&mut self, _: VoidOpt) -> Result { - NEED_RENDER.store(false, Ordering::Relaxed); + pub(crate) fn render(&mut self, partial: bool) -> Result { + NEED_RENDER.store(0, Ordering::Relaxed); let Some(term) = &mut self.term else { succ!() }; + if partial { + return self.render_partially(); + } + Self::routine(true, None); let _guard = scopeguard::guard(self.core.cursor(), |c| Self::routine(false, c)); @@ -31,21 +34,21 @@ impl App { Self::patch(frame); } if !self.core.notify.messages.is_empty() { - act!(render_partially, self)?; + self.render_partially()?; } // Reload preview if collision is resolved if collision && !COLLISION.load(Ordering::Relaxed) { - let cx = &mut Ctx::active(&mut self.core); + let cx = &mut Ctx::active(&mut self.core, &mut self.term); act!(mgr:peek, cx, true)?; } succ!(); } - pub(crate) fn render_partially(&mut self, _: VoidOpt) -> Result { + pub(crate) fn render_partially(&mut self) -> Result { let Some(term) = &mut self.term else { succ!() }; if !term.can_partial() { - return act!(render, self); + return self.render(false); } Self::routine(true, None); diff --git a/yazi-fm/src/dispatcher.rs b/yazi-fm/src/dispatcher.rs index 4069e2aa..51893ceb 100644 --- a/yazi-fm/src/dispatcher.rs +++ b/yazi-fm/src/dispatcher.rs @@ -1,8 +1,9 @@ use std::sync::atomic::Ordering; use anyhow::Result; -use crossterm::event::KeyEvent; +use crossterm::event::{KeyEvent, MouseEvent}; use tracing::warn; +use yazi_actor::Ctx; use yazi_config::keymap::Key; use yazi_macro::{act, emit, succ}; use yazi_shared::{data::Data, event::{CmdCow, Event, NEED_RENDER}}; @@ -20,17 +21,15 @@ impl<'a> Dispatcher<'a> { #[inline] pub(super) fn dispatch(&mut self, event: Event) -> Result<()> { - // FIXME: handle errors let result = match event { Event::Call(cmd) => self.dispatch_call(cmd), Event::Seq(cmds) => self.dispatch_seq(cmds), - Event::Render => self.dispatch_render(), + Event::Render(partial) => self.dispatch_render(partial), Event::Key(key) => self.dispatch_key(key), - Event::Mouse(mouse) => act!(mouse, self.app, mouse), - Event::Resize => act!(resize, self.app), - Event::Focus => act!(focus, self.app), + Event::Mouse(mouse) => self.dispatch_mouse(mouse), + Event::Resize => self.dispatch_resize(), + Event::Focus => self.dispatch_focus(), Event::Paste(str) => self.dispatch_paste(str), - Event::Quit(opt) => act!(quit, self.app, opt), }; if let Err(err) = result { @@ -54,8 +53,13 @@ impl<'a> Dispatcher<'a> { } #[inline] - fn dispatch_render(&mut self) -> Result { - succ!(NEED_RENDER.store(true, Ordering::Relaxed)) + fn dispatch_render(&mut self, partial: bool) -> Result { + if partial { + _ = NEED_RENDER.compare_exchange(0, 2, Ordering::Relaxed, Ordering::Relaxed); + } else { + NEED_RENDER.store(1, Ordering::Relaxed); + } + succ!() } #[inline] @@ -64,6 +68,24 @@ impl<'a> Dispatcher<'a> { succ!(); } + #[inline] + fn dispatch_mouse(&mut self, mouse: MouseEvent) -> Result { + let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term); + act!(app:mouse, cx, mouse) + } + + #[inline] + fn dispatch_resize(&mut self) -> Result { + let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term); + act!(app:resize, cx, crate::Root::reflow as fn(_) -> _) + } + + #[inline] + fn dispatch_focus(&mut self) -> Result { + let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term); + act!(app:focus, cx) + } + #[inline] fn dispatch_paste(&mut self, str: String) -> Result { if self.app.core.input.visible { diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 291550b9..123f40f4 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use yazi_actor::Ctx; use yazi_macro::{act, succ}; use yazi_shared::{Layer, data::Data, event::CmdCow}; @@ -27,34 +27,45 @@ impl<'a> Executor<'a> { Layer::Help => self.help(cmd), Layer::Cmp => self.cmp(cmd), Layer::Which => self.which(cmd), + Layer::Notify => self.notify(cmd), } } - fn app(&mut self, cmd: CmdCow) -> Result { + fn app(&mut self, mut cmd: CmdCow) -> Result { + let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?; + macro_rules! on { ($name:ident) => { if cmd.name == stringify!($name) { - return act!($name, self.app, cmd); + return act!(app:$name, cx, cmd); } }; } on!(accept_payload); - on!(notify); on!(plugin); on!(plugin_do); - on!(update_notify); on!(update_progress); - on!(resize); - on!(stop); - on!(resume); on!(deprecate); + on!(quit); - succ!(); + match &*cmd.name { + "resize" => act!(app:resize, cx, crate::Root::reflow as fn(_) -> _), + "resume" => act!(app:resume, cx, yazi_parser::app::ResumeOpt { + tx: self.app.signals.tx.clone(), + token: cmd.take_any("token").context("Invalid 'token' in ResumeOpt")?, + reflow: crate::Root::reflow, + }), + "stop" => act!(app:stop, cx, yazi_parser::app::StopOpt { + tx: self.app.signals.tx.clone(), + token: cmd.take_any("token").context("Invalid 'token' in StopOpt")?, + }), + _ => succ!(), + } } fn mgr(&mut self, cmd: CmdCow) -> Result { - let cx = &mut Ctx::new(&mut self.app.core, &cmd)?; + let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?; macro_rules! on { ($name:ident) => { @@ -143,13 +154,13 @@ impl<'a> Executor<'a> { // Help "help" => act!(help:toggle, cx, Layer::Mgr), // Plugin - "plugin" => act!(plugin, self.app, cmd), + "plugin" => act!(app:plugin, cx, cmd), _ => succ!(), } } fn tasks(&mut self, cmd: CmdCow) -> Result { - let cx = &mut Ctx::new(&mut self.app.core, &cmd)?; + let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?; macro_rules! on { ($name:ident) => { @@ -173,13 +184,13 @@ impl<'a> Executor<'a> { // Help "help" => act!(help:toggle, cx, Layer::Tasks), // Plugin - "plugin" => act!(plugin, self.app, cmd), + "plugin" => act!(app:plugin, cx, cmd), _ => succ!(), } } fn spot(&mut self, cmd: CmdCow) -> Result { - let cx = &mut Ctx::new(&mut self.app.core, &cmd)?; + let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?; macro_rules! on { ($name:ident) => { @@ -198,13 +209,13 @@ impl<'a> Executor<'a> { // Help "help" => act!(help:toggle, cx, Layer::Spot), // Plugin - "plugin" => act!(plugin, self.app, cmd), + "plugin" => act!(app:plugin, cx, cmd), _ => succ!(), } } fn pick(&mut self, cmd: CmdCow) -> Result { - let cx = &mut Ctx::new(&mut self.app.core, &cmd)?; + let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?; macro_rules! on { ($name:ident) => { @@ -222,14 +233,14 @@ impl<'a> Executor<'a> { // Help "help" => act!(help:toggle, cx, Layer::Pick), // Plugin - "plugin" => act!(plugin, self.app, cmd), + "plugin" => act!(app:plugin, cx, cmd), _ => succ!(), } } fn input(&mut self, cmd: CmdCow) -> Result { let mode = self.app.core.input.mode(); - let cx = &mut Ctx::new(&mut self.app.core, &cmd)?; + let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?; macro_rules! on { ($name:ident) => { @@ -249,7 +260,7 @@ impl<'a> Executor<'a> { // Help "help" => return act!(help:toggle, cx, Layer::Input), // Plugin - "plugin" => return act!(plugin, self.app, cmd), + "plugin" => return act!(app:plugin, cx, cmd), _ => {} } } @@ -263,7 +274,7 @@ impl<'a> Executor<'a> { } fn confirm(&mut self, cmd: CmdCow) -> Result { - let cx = &mut Ctx::new(&mut self.app.core, &cmd)?; + let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?; macro_rules! on { ($name:ident) => { @@ -281,7 +292,7 @@ impl<'a> Executor<'a> { } fn help(&mut self, cmd: CmdCow) -> Result { - let cx = &mut Ctx::new(&mut self.app.core, &cmd)?; + let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?; macro_rules! on { ($name:ident) => { @@ -299,13 +310,13 @@ impl<'a> Executor<'a> { // Help "close" => act!(help:toggle, cx, Layer::Help), // Plugin - "plugin" => act!(plugin, self.app, cmd), + "plugin" => act!(app:plugin, cx, cmd), _ => succ!(), } } fn cmp(&mut self, cmd: CmdCow) -> Result { - let cx = &mut Ctx::new(&mut self.app.core, &cmd)?; + let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?; macro_rules! on { ($name:ident) => { @@ -324,13 +335,13 @@ impl<'a> Executor<'a> { // Help "help" => act!(help:toggle, cx, Layer::Cmp), // Plugin - "plugin" => act!(plugin, self.app, cmd), + "plugin" => act!(app:plugin, cx, cmd), _ => succ!(), } } fn which(&mut self, cmd: CmdCow) -> Result { - let cx = &mut Ctx::new(&mut self.app.core, &cmd)?; + let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?; macro_rules! on { ($name:ident) => { @@ -345,4 +356,21 @@ impl<'a> Executor<'a> { succ!(); } + + fn notify(&mut self, cmd: CmdCow) -> Result { + let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?; + + macro_rules! on { + ($name:ident) => { + if cmd.name == stringify!($name) { + return act!(notify:$name, cx, cmd); + } + }; + } + + on!(push); + on!(tick); + + succ!(); + } } diff --git a/yazi-fm/src/notify/notify.rs b/yazi-fm/src/notify/notify.rs index 5f1fb418..608292e7 100644 --- a/yazi-fm/src/notify/notify.rs +++ b/yazi-fm/src/notify/notify.rs @@ -8,15 +8,6 @@ pub(crate) struct Notify<'a> { impl<'a> Notify<'a> { pub(crate) fn new(core: &'a Core) -> Self { Self { core } } - pub(crate) fn available(area: Rect) -> Rect { - let chunks = layout::Layout::horizontal([Constraint::Fill(1), Constraint::Min(80)]).split(area); - - let chunks = - layout::Layout::vertical([Constraint::Fill(1), Constraint::Max(1)]).split(chunks[1]); - - chunks[0] - } - fn tiles<'m>(area: Rect, messages: impl Iterator + Clone) -> Vec { layout::Layout::vertical( [Constraint::Fill(1)] @@ -42,7 +33,7 @@ impl<'a> Notify<'a> { impl Widget for Notify<'_> { fn render(self, area: Rect, buf: &mut Buffer) { let notify = &self.core.notify; - let available = Self::available(area); + let available = yazi_core::notify::Notify::available(area); let messages = notify.messages.iter().take(notify.limit(available)).rev(); let tiles = Self::tiles(available, messages.clone()); diff --git a/yazi-fm/src/router.rs b/yazi-fm/src/router.rs index 039b913c..04cc12c8 100644 --- a/yazi-fm/src/router.rs +++ b/yazi-fm/src/router.rs @@ -26,7 +26,7 @@ impl<'a> Router<'a> { use Layer as L; Ok(match layer { - L::App => unreachable!(), + L::App | L::Notify => unreachable!(), L::Mgr | L::Tasks | L::Spot | L::Pick | L::Input | L::Confirm | L::Help => { self.matches(layer, key) } @@ -42,7 +42,7 @@ impl<'a> Router<'a> { } if on.len() > 1 { - let cx = &mut Ctx::active(&mut self.app.core); + let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term); act!(which:activate, cx, (layer, key)).ok(); } else { emit!(Seq(ChordCow::from(chord).into_seq())); diff --git a/yazi-fm/src/signals.rs b/yazi-fm/src/signals.rs index 6d733246..3afaff4a 100644 --- a/yazi-fm/src/signals.rs +++ b/yazi-fm/src/signals.rs @@ -6,7 +6,7 @@ use yazi_config::YAZI; use yazi_shared::{CompletionToken, event::Event}; pub(super) struct Signals { - tx: mpsc::UnboundedSender<(bool, CompletionToken)>, + pub(super) tx: mpsc::UnboundedSender<(bool, CompletionToken)>, } impl Signals { @@ -17,10 +17,6 @@ impl Signals { Ok(Self { tx }) } - pub(super) fn stop(&mut self, token: CompletionToken) { self.tx.send((false, token)).ok(); } - - pub(super) fn resume(&mut self, token: CompletionToken) { self.tx.send((true, token)).ok(); } - #[cfg(unix)] fn handle_sys(n: libc::c_int) -> bool { use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGSTOP, SIGTERM, SIGTSTP}; @@ -30,7 +26,7 @@ impl Signals { match n { SIGINT => { /* ignored */ } SIGQUIT | SIGHUP | SIGTERM => { - Event::Quit(Default::default()).emit(); + AppProxy::quit(Default::default()); return false; } SIGTSTP => { @@ -38,7 +34,7 @@ impl Signals { AppProxy::stop().await; if unsafe { libc::kill(0, SIGSTOP) } != 0 { error!("Failed to stop the process:\n{}", std::io::Error::last_os_error()); - Event::Quit(Default::default()).emit(); + AppProxy::quit(Default::default()); } }); } diff --git a/yazi-macro/src/event.rs b/yazi-macro/src/event.rs index 2dc0447e..2f307979 100644 --- a/yazi-macro/src/event.rs +++ b/yazi-macro/src/event.rs @@ -1,8 +1,5 @@ #[macro_export] macro_rules! emit { - (Quit($opt:expr)) => { - yazi_shared::event::Event::Quit($opt).emit(); - }; (Call($cmd:expr)) => { yazi_shared::event::Event::Call(yazi_shared::event::CmdCow::from($cmd)).emit(); }; diff --git a/yazi-macro/src/render.rs b/yazi-macro/src/render.rs index 5bdf9864..38c075f2 100644 --- a/yazi-macro/src/render.rs +++ b/yazi-macro/src/render.rs @@ -1,7 +1,7 @@ #[macro_export] macro_rules! render { () => { - yazi_shared::event::NEED_RENDER.store(true, std::sync::atomic::Ordering::Relaxed); + yazi_shared::event::NEED_RENDER.store(1, std::sync::atomic::Ordering::Relaxed); }; ($cond:expr) => { if $cond { @@ -28,3 +28,15 @@ macro_rules! render_and { } }; } + +#[macro_export] +macro_rules! render_partial { + () => {{ + _ = yazi_shared::event::NEED_RENDER.compare_exchange( + 0, + 2, + std::sync::atomic::Ordering::Relaxed, + std::sync::atomic::Ordering::Relaxed, + ); + }}; +} diff --git a/yazi-parser/Cargo.toml b/yazi-parser/Cargo.toml index e9965e49..f5e8060e 100644 --- a/yazi-parser/Cargo.toml +++ b/yazi-parser/Cargo.toml @@ -32,6 +32,7 @@ dyn-clone = { workspace = true } hashbrown = { workspace = true } mlua = { workspace = true } ordered-float = { workspace = true } +ratatui = { workspace = true } serde = { workspace = true } serde_with = { workspace = true } tokio = { workspace = true } diff --git a/yazi-parser/src/app/deprecate.rs b/yazi-parser/src/app/deprecate.rs index 10c72ed9..e35837d2 100644 --- a/yazi-parser/src/app/deprecate.rs +++ b/yazi-parser/src/app/deprecate.rs @@ -1,6 +1,8 @@ use anyhow::bail; +use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use yazi_shared::{SStr, event::CmdCow}; +#[derive(Debug)] pub struct DeprecateOpt { pub content: SStr, } @@ -16,3 +18,11 @@ impl TryFrom for DeprecateOpt { Ok(Self { content }) } } + +impl FromLua for DeprecateOpt { + fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} + +impl IntoLua for DeprecateOpt { + 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 9d5b8c11..5fcfae06 100644 --- a/yazi-parser/src/app/mod.rs +++ b/yazi-parser/src/app/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(deprecate mouse notify plugin resume stop update_progress); +yazi_macro::mod_flat!(deprecate mouse plugin quit reflow resume stop update_progress); diff --git a/yazi-parser/src/app/mouse.rs b/yazi-parser/src/app/mouse.rs index 0ce32d04..8eae8998 100644 --- a/yazi-parser/src/app/mouse.rs +++ b/yazi-parser/src/app/mouse.rs @@ -1,5 +1,7 @@ use crossterm::event::MouseEvent; +use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +#[derive(Debug)] pub struct MouseOpt { pub event: MouseEvent, } @@ -7,3 +9,11 @@ pub struct MouseOpt { impl From for MouseOpt { fn from(event: MouseEvent) -> Self { Self { event } } } + +impl FromLua for MouseOpt { + fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} + +impl IntoLua for MouseOpt { + fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} diff --git a/yazi-parser/src/app/plugin.rs b/yazi-parser/src/app/plugin.rs index 30184201..745f7a93 100644 --- a/yazi-parser/src/app/plugin.rs +++ b/yazi-parser/src/app/plugin.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, fmt::{self, Debug}, str::FromStr}; use anyhow::bail; use dyn_clone::DynClone; use hashbrown::HashMap; -use mlua::{Lua, Table}; +use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value}; use serde::Deserialize; use yazi_shared::{SStr, data::{Data, DataKey}, event::{Cmd, CmdCow}}; @@ -39,6 +39,14 @@ impl TryFrom for PluginOpt { } } +impl FromLua for PluginOpt { + fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} + +impl IntoLua for PluginOpt { + fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} + impl PluginOpt { pub fn new_callback(id: impl Into, f: impl PluginCallback) -> Self { Self { diff --git a/yazi-parser/src/app/quit.rs b/yazi-parser/src/app/quit.rs new file mode 100644 index 00000000..228e546b --- /dev/null +++ b/yazi-parser/src/app/quit.rs @@ -0,0 +1,35 @@ +use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value}; +use serde::{Deserialize, Serialize}; +use yazi_shared::{event::CmdCow, strand::StrandBuf}; + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct QuitOpt { + pub code: i32, + #[serde(skip)] // FIXME + pub selected: Option, + pub no_cwd_file: bool, +} + +impl TryFrom for QuitOpt { + type Error = anyhow::Error; + + fn try_from(mut c: CmdCow) -> Result { + if let Some(opt) = c.take_any2("opt") { + return opt; + } + + Ok(Self { + code: c.get("code").unwrap_or_default(), + selected: None, // FIXME + no_cwd_file: c.bool("no-cwd-file"), + }) + } +} + +impl FromLua for QuitOpt { + fn from_lua(value: Value, lua: &Lua) -> mlua::Result { lua.from_value(value) } +} + +impl IntoLua for QuitOpt { + fn into_lua(self, lua: &Lua) -> mlua::Result { lua.to_value(&self) } +} diff --git a/yazi-parser/src/app/reflow.rs b/yazi-parser/src/app/reflow.rs new file mode 100644 index 00000000..3c9b80a2 --- /dev/null +++ b/yazi-parser/src/app/reflow.rs @@ -0,0 +1,19 @@ +use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value}; +use ratatui::layout::Rect; + +#[derive(Debug)] +pub struct ReflowOpt { + pub reflow: fn(Rect) -> mlua::Result
, +} + +impl From mlua::Result
> for ReflowOpt { + fn from(f: fn(Rect) -> mlua::Result
) -> Self { Self { reflow: f } } +} + +impl FromLua for ReflowOpt { + fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} + +impl IntoLua for ReflowOpt { + fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} diff --git a/yazi-parser/src/app/resume.rs b/yazi-parser/src/app/resume.rs index 52e77c35..ea0c3f1e 100644 --- a/yazi-parser/src/app/resume.rs +++ b/yazi-parser/src/app/resume.rs @@ -1,18 +1,19 @@ -use anyhow::bail; -use yazi_shared::{CompletionToken, event::CmdCow}; +use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value}; +use ratatui::layout::Rect; +use tokio::sync::mpsc; +use yazi_shared::CompletionToken; +#[derive(Debug)] pub struct ResumeOpt { - pub token: CompletionToken, + pub tx: mpsc::UnboundedSender<(bool, CompletionToken)>, + pub token: CompletionToken, + pub reflow: fn(Rect) -> mlua::Result
, } -impl TryFrom for ResumeOpt { - type Error = anyhow::Error; - - fn try_from(mut c: CmdCow) -> Result { - let Some(token) = c.take_any("token") else { - bail!("Invalid 'token' in ResumeOpt"); - }; - - Ok(Self { token }) - } +impl FromLua for ResumeOpt { + fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} + +impl IntoLua for ResumeOpt { + fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } } diff --git a/yazi-parser/src/app/stop.rs b/yazi-parser/src/app/stop.rs index 683ddf8d..9280df8d 100644 --- a/yazi-parser/src/app/stop.rs +++ b/yazi-parser/src/app/stop.rs @@ -1,18 +1,17 @@ -use anyhow::bail; -use yazi_shared::{CompletionToken, event::CmdCow}; +use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +use tokio::sync::mpsc; +use yazi_shared::CompletionToken; +#[derive(Debug)] pub struct StopOpt { + pub tx: mpsc::UnboundedSender<(bool, CompletionToken)>, pub token: CompletionToken, } -impl TryFrom for StopOpt { - type Error = anyhow::Error; - - fn try_from(mut c: CmdCow) -> Result { - let Some(token) = c.take_any("token") else { - bail!("Invalid 'token' in StopOpt"); - }; - - Ok(Self { token }) - } +impl FromLua for StopOpt { + fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} + +impl IntoLua for StopOpt { + fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } } diff --git a/yazi-parser/src/app/update_progress.rs b/yazi-parser/src/app/update_progress.rs index 5138dc80..c90dba3c 100644 --- a/yazi-parser/src/app/update_progress.rs +++ b/yazi-parser/src/app/update_progress.rs @@ -1,8 +1,10 @@ use anyhow::bail; +use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use ordered_float::OrderedFloat; use serde::Serialize; use yazi_shared::event::CmdCow; +#[derive(Debug)] pub struct UpdateProgressOpt { pub summary: TaskSummary, } @@ -19,6 +21,14 @@ impl TryFrom for UpdateProgressOpt { } } +impl FromLua for UpdateProgressOpt { + fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} + +impl IntoLua for UpdateProgressOpt { + fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} + // --- Progress #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] pub struct TaskSummary { diff --git a/yazi-parser/src/mgr/close.rs b/yazi-parser/src/mgr/close.rs index 34304bcb..34d80e67 100644 --- a/yazi-parser/src/mgr/close.rs +++ b/yazi-parser/src/mgr/close.rs @@ -1,13 +1,15 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use yazi_shared::event::CmdCow; -use crate::mgr::QuitOpt; +use crate::app::QuitOpt; #[derive(Debug, Default)] pub struct CloseOpt(pub QuitOpt); -impl From for CloseOpt { - fn from(c: CmdCow) -> Self { Self(c.into()) } +impl TryFrom for CloseOpt { + type Error = anyhow::Error; + + fn try_from(c: CmdCow) -> Result { c.try_into().map(Self) } } impl FromLua for CloseOpt { diff --git a/yazi-parser/src/mgr/mod.rs b/yazi-parser/src/mgr/mod.rs index 085ae19e..8fe73145 100644 --- a/yazi-parser/src/mgr/mod.rs +++ b/yazi-parser/src/mgr/mod.rs @@ -19,7 +19,6 @@ yazi_macro::mod_flat!( open_do paste peek - quit remove rename reveal diff --git a/yazi-parser/src/mgr/quit.rs b/yazi-parser/src/mgr/quit.rs deleted file mode 100644 index 057b40e8..00000000 --- a/yazi-parser/src/mgr/quit.rs +++ /dev/null @@ -1,29 +0,0 @@ -use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value}; -use serde::{Deserialize, Serialize}; -use yazi_shared::event::{CmdCow, EventQuit}; - -#[derive(Debug, Default, Deserialize, Serialize)] -pub struct QuitOpt { - pub code: i32, - pub no_cwd_file: bool, -} - -impl From for QuitOpt { - fn from(c: CmdCow) -> Self { - Self { code: c.get("code").unwrap_or_default(), no_cwd_file: c.bool("no-cwd-file") } - } -} - -impl From for EventQuit { - fn from(value: QuitOpt) -> Self { - Self { code: value.code, no_cwd_file: value.no_cwd_file, ..Default::default() } - } -} - -impl FromLua for QuitOpt { - fn from_lua(value: Value, lua: &Lua) -> mlua::Result { lua.from_value(value) } -} - -impl IntoLua for QuitOpt { - fn into_lua(self, lua: &Lua) -> mlua::Result { lua.to_value(&self) } -} diff --git a/yazi-parser/src/notify/mod.rs b/yazi-parser/src/notify/mod.rs index 842c315a..b7ec7334 100644 --- a/yazi-parser/src/notify/mod.rs +++ b/yazi-parser/src/notify/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(tick); +yazi_macro::mod_flat!(push tick); diff --git a/yazi-parser/src/app/notify.rs b/yazi-parser/src/notify/push.rs similarity index 80% rename from yazi-parser/src/app/notify.rs rename to yazi-parser/src/notify/push.rs index b18ff32a..7056db38 100644 --- a/yazi-parser/src/app/notify.rs +++ b/yazi-parser/src/notify/push.rs @@ -8,16 +8,16 @@ use yazi_config::{Style, THEME}; use yazi_shared::event::CmdCow; #[serde_as] -#[derive(Clone, Deserialize, Serialize)] -pub struct NotifyOpt { +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PushOpt { pub title: String, pub content: String, - pub level: NotifyLevel, + pub level: PushLevel, #[serde_as(as = "DurationSeconds")] // FIXME pub timeout: Duration, } -impl TryFrom for NotifyOpt { +impl TryFrom for PushOpt { type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { @@ -25,25 +25,25 @@ impl TryFrom for NotifyOpt { } } -impl FromLua for NotifyOpt { +impl FromLua for PushOpt { fn from_lua(value: Value, lua: &Lua) -> mlua::Result { lua.from_value(value) } } -impl IntoLua for NotifyOpt { +impl IntoLua for PushOpt { fn into_lua(self, lua: &Lua) -> mlua::Result { lua.to_value(&self) } } // --- Level -#[derive(Clone, Copy, Default, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "kebab-case")] -pub enum NotifyLevel { +pub enum PushLevel { #[default] Info, Warn, Error, } -impl NotifyLevel { +impl PushLevel { pub fn icon(self) -> &'static str { match self { Self::Info => &THEME.notify.icon_info, @@ -61,7 +61,7 @@ impl NotifyLevel { } } -impl FromStr for NotifyLevel { +impl FromStr for PushLevel { type Err = serde::de::value::Error; fn from_str(s: &str) -> Result { diff --git a/yazi-plugin/src/utils/layer.rs b/yazi-plugin/src/utils/layer.rs index c829b627..2c4359fe 100644 --- a/yazi-plugin/src/utils/layer.rs +++ b/yazi-plugin/src/utils/layer.rs @@ -6,8 +6,8 @@ use tokio_stream::wrappers::UnboundedReceiverStream; use yazi_binding::{elements::{Line, Pos, Text}, runtime}; use yazi_config::{keymap::{Chord, ChordCow, Key}, popup::{ConfirmCfg, InputCfg}}; use yazi_macro::relay; -use yazi_parser::{app::NotifyOpt, which::ActivateOpt}; -use yazi_proxy::{AppProxy, ConfirmProxy, InputProxy, WhichProxy}; +use yazi_parser::{notify::PushOpt, which::ActivateOpt}; +use yazi_proxy::{ConfirmProxy, InputProxy, NotifyProxy, WhichProxy}; use yazi_shared::Debounce; use super::Utils; @@ -100,7 +100,7 @@ impl Utils { } pub(super) fn notify(lua: &Lua) -> mlua::Result { - lua.create_function(|_, opt: NotifyOpt| Ok(AppProxy::notify(opt))) + lua.create_function(|_, opt: PushOpt| Ok(NotifyProxy::push(opt))) } fn parse_keys(value: Value) -> mlua::Result> { diff --git a/yazi-proxy/src/app.rs b/yazi-proxy/src/app.rs index 92442ba1..e69d8ae5 100644 --- a/yazi-proxy/src/app.rs +++ b/yazi-proxy/src/app.rs @@ -1,12 +1,14 @@ -use std::time::Duration; - use yazi_macro::{emit, relay}; -use yazi_parser::app::{NotifyLevel, NotifyOpt, PluginOpt, TaskSummary}; +use yazi_parser::app::{PluginOpt, QuitOpt, TaskSummary}; use yazi_shared::CompletionToken; pub struct AppProxy; impl AppProxy { + pub fn quit(opt: QuitOpt) { + emit!(Call(relay!(app:quit).with_any("opt", opt))); + } + pub async fn stop() { let token = CompletionToken::default(); emit!(Call(relay!(app:stop).with_any("token", token.clone()))); @@ -19,32 +21,6 @@ impl AppProxy { token.future().await; } - pub fn notify(opt: NotifyOpt) { - emit!(Call(relay!(app:notify).with_any("opt", opt))); - } - - pub fn update_notify(dur: Duration) { - emit!(Call(relay!(app:update_notify, [dur.as_secs_f64()]))); - } - - pub fn notify_warn(title: impl Into, content: impl Into) { - Self::notify(NotifyOpt { - title: title.into(), - content: content.into(), - level: NotifyLevel::Warn, - timeout: Duration::from_secs(5), - }); - } - - pub fn notify_error(title: &str, content: impl ToString) { - Self::notify(NotifyOpt { - title: title.to_owned(), - content: content.to_string(), - level: NotifyLevel::Error, - timeout: Duration::from_secs(10), - }); - } - pub fn plugin(opt: PluginOpt) { emit!(Call(relay!(app:plugin).with_any("opt", opt))); } diff --git a/yazi-proxy/src/lib.rs b/yazi-proxy/src/lib.rs index 50f0d758..c7e1b3f9 100644 --- a/yazi-proxy/src/lib.rs +++ b/yazi-proxy/src/lib.rs @@ -1,5 +1,5 @@ mod macros; -yazi_macro::mod_flat!(app cmp confirm input mgr pick semaphore tasks which); +yazi_macro::mod_flat!(app cmp confirm input mgr notify pick semaphore tasks which); pub fn init() { crate::init_semaphore(); } diff --git a/yazi-proxy/src/notify.rs b/yazi-proxy/src/notify.rs new file mode 100644 index 00000000..7fad698d --- /dev/null +++ b/yazi-proxy/src/notify.rs @@ -0,0 +1,34 @@ +use std::time::Duration; + +use yazi_macro::{emit, relay}; +use yazi_parser::notify::{PushLevel, PushOpt}; + +pub struct NotifyProxy; + +impl NotifyProxy { + pub fn push(opt: PushOpt) { + emit!(Call(relay!(notify:push).with_any("opt", opt))); + } + + pub fn push_warn(title: impl Into, content: impl Into) { + Self::push(PushOpt { + title: title.into(), + content: content.into(), + level: PushLevel::Warn, + timeout: Duration::from_secs(5), + }); + } + + pub fn push_error(title: &str, content: impl ToString) { + Self::push(PushOpt { + title: title.to_owned(), + content: content.to_string(), + level: PushLevel::Error, + timeout: Duration::from_secs(10), + }); + } + + pub fn tick(dur: Duration) { + emit!(Call(relay!(notify:tick, [dur.as_secs_f64()]))); + } +} diff --git a/yazi-scheduler/src/process/process.rs b/yazi-scheduler/src/process/process.rs index 2262bf7b..8a4c7d29 100644 --- a/yazi-scheduler/src/process/process.rs +++ b/yazi-scheduler/src/process/process.rs @@ -1,7 +1,7 @@ use anyhow::{Result, anyhow}; use tokio::{io::{AsyncBufReadExt, BufReader}, select, sync::mpsc}; use yazi_binding::Permit; -use yazi_proxy::{AppProxy, HIDER}; +use yazi_proxy::{AppProxy, HIDER, NotifyProxy}; use super::{ProcessInBg, ProcessInBlock, ProcessInOrphan, ShellOpt}; use crate::{TaskOp, TaskOps, process::{ProcessOutBg, ProcessOutBlock, ProcessOutOrphan}}; @@ -20,7 +20,7 @@ impl Process { let (id, cmd) = (task.id, task.cmd.clone()); let result = super::shell(task.into()).await; if let Err(e) = result { - AppProxy::notify_warn(cmd.to_string_lossy(), format!("Failed to start process: {e}")); + NotifyProxy::push_warn(cmd.to_string_lossy(), format!("Failed to start process: {e}")); return Ok(self.ops.out(id, ProcessOutBlock::Succ)); } @@ -31,7 +31,7 @@ impl Process { Some(code) => format!("Process exited with status code: {code}"), None => "Process terminated by signal".to_string(), }; - AppProxy::notify_warn(cmd.to_string_lossy(), content); + NotifyProxy::push_warn(cmd.to_string_lossy(), content); } Ok(self.ops.out(id, ProcessOutBlock::Succ)) diff --git a/yazi-shared/src/event/event.rs b/yazi-shared/src/event/event.rs index 7dca880e..edf10871 100644 --- a/yazi-shared/src/event/event.rs +++ b/yazi-shared/src/event/event.rs @@ -2,7 +2,7 @@ use crossterm::event::{KeyEvent, MouseEvent}; use tokio::sync::mpsc; use super::CmdCow; -use crate::{RoCell, strand::StrandBuf}; +use crate::RoCell; static TX: RoCell> = RoCell::new(); static RX: RoCell> = RoCell::new(); @@ -11,20 +11,12 @@ static RX: RoCell> = RoCell::new(); pub enum Event { Call(CmdCow), Seq(Vec), - Render, + Render(bool), Key(KeyEvent), Mouse(MouseEvent), Resize, Focus, Paste(String), - Quit(EventQuit), -} - -#[derive(Debug, Default)] -pub struct EventQuit { - pub code: i32, - pub no_cwd_file: bool, - pub selected: Option, } impl Event { diff --git a/yazi-shared/src/event/mod.rs b/yazi-shared/src/event/mod.rs index d6fc14a1..420a2d6c 100644 --- a/yazi-shared/src/event/mod.rs +++ b/yazi-shared/src/event/mod.rs @@ -1,3 +1,3 @@ yazi_macro::mod_flat!(cmd cow event); -pub static NEED_RENDER: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); +pub static NEED_RENDER: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0); diff --git a/yazi-shared/src/layer.rs b/yazi-shared/src/layer.rs index 18fc36a0..b5d50d3a 100644 --- a/yazi-shared/src/layer.rs +++ b/yazi-shared/src/layer.rs @@ -16,6 +16,7 @@ pub enum Layer { Help, Cmp, Which, + Notify, } impl Display for Layer { @@ -31,6 +32,7 @@ impl Display for Layer { Self::Help => "help", Self::Cmp => "cmp", Self::Which => "which", + Self::Notify => "notify", }) } }