diff --git a/Cargo.lock b/Cargo.lock index 8286087d..a03d6d29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -340,9 +340,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "castaway" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] @@ -528,9 +528,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -2207,9 +2207,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rgb" -version = "0.8.51" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a457e416a0f90d246a4c3288bd7a25b2304ca727f253f95be383dd17af56be8f" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" [[package]] name = "rustc-demangle" @@ -2739,9 +2739,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0207d6ed1852c2a124c1fbec61621acb8330d2bf969a5d0643131e9affd985a5" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" dependencies = [ "indexmap", "serde", @@ -2785,18 +2785,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c1c469eda89749d2230d8156a5969a69ffe0d6d01200581cdc6110674d293e" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b679217f2848de74cabd3e8fc5e6d66f40b7da40f8e1954d92054d9010690fd5" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" [[package]] name = "tracing" @@ -3440,9 +3440,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -3471,6 +3471,33 @@ dependencies = [ "tap", ] +[[package]] +name = "yazi-actor" +version = "25.6.11" +dependencies = [ + "anyhow", + "crossterm 0.29.0", + "futures", + "libc", + "paste", + "scopeguard", + "tokio", + "tokio-stream", + "tracing", + "yazi-boot", + "yazi-config", + "yazi-core", + "yazi-dds", + "yazi-fs", + "yazi-macro", + "yazi-parser", + "yazi-plugin", + "yazi-proxy", + "yazi-shared", + "yazi-term", + "yazi-widgets", +] + [[package]] name = "yazi-adapter" version = "25.6.11" @@ -3544,7 +3571,7 @@ dependencies = [ "serde", "serde_json", "tokio", - "toml 0.9.1", + "toml 0.9.2", "twox-hash", "vergen-gitcl", "yazi-boot", @@ -3575,7 +3602,7 @@ dependencies = [ "ratatui", "regex", "serde", - "toml 0.9.1", + "toml 0.9.2", "tracing", "yazi-codegen", "yazi-fs", @@ -3596,16 +3623,12 @@ dependencies = [ "notify", "parking_lot", "ratatui", - "scopeguard", - "serde", "tokio", "tokio-stream", "tokio-util", "tracing", "unicode-width 0.2.0", "yazi-adapter", - "yazi-boot", - "yazi-codegen", "yazi-config", "yazi-dds", "yazi-fs", @@ -3615,7 +3638,6 @@ dependencies = [ "yazi-proxy", "yazi-scheduler", "yazi-shared", - "yazi-term", "yazi-widgets", ] @@ -3678,15 +3700,16 @@ dependencies = [ "tracing", "tracing-appender", "tracing-subscriber", + "yazi-actor", "yazi-adapter", "yazi-binding", "yazi-boot", - "yazi-codegen", "yazi-config", "yazi-core", "yazi-dds", "yazi-fs", "yazi-macro", + "yazi-parser", "yazi-plugin", "yazi-proxy", "yazi-shared", @@ -3734,7 +3757,9 @@ version = "25.6.11" dependencies = [ "anyhow", "bitflags 2.9.1", + "crossterm 0.29.0", "mlua", + "serde", "tokio", "yazi-binding", "yazi-boot", @@ -3744,7 +3769,6 @@ dependencies = [ "yazi-macro", "yazi-proxy", "yazi-shared", - "yazi-widgets", ] [[package]] @@ -3752,7 +3776,6 @@ name = "yazi-plugin" version = "25.6.11" dependencies = [ "anyhow", - "base64", "crossterm 0.29.0", "futures", "globset", @@ -3782,7 +3805,6 @@ dependencies = [ "yazi-prebuilt", "yazi-proxy", "yazi-shared", - "yazi-term", "yazi-widgets", ] @@ -3824,6 +3846,7 @@ dependencies = [ "yazi-dds", "yazi-fs", "yazi-macro", + "yazi-parser", "yazi-plugin", "yazi-proxy", "yazi-shared", @@ -3866,6 +3889,7 @@ dependencies = [ name = "yazi-widgets" version = "25.6.11" dependencies = [ + "anyhow", "base64", "clipboard-win", "crossterm 0.29.0", @@ -3875,10 +3899,9 @@ dependencies = [ "tokio", "unicode-width 0.2.0", "yazi-binding", - "yazi-codegen", "yazi-config", "yazi-macro", - "yazi-proxy", + "yazi-parser", "yazi-shared", "yazi-term", ] diff --git a/Cargo.toml b/Cargo.toml index 8d54c226..89b3e83d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ syntect = { version = "5.2.0", default-features = false, features = tokio = { version = "1.46.1", features = [ "full" ] } tokio-stream = "0.1.17" tokio-util = "0.7.15" -toml = { version = "0.9.1" } +toml = { version = "0.9.2" } tracing = { version = "0.1.41", features = [ "max_level_debug", "release_max_level_debug" ] } twox-hash = { version = "2.1.1", default-features = false, features = [ "std", "random", "xxhash3_128" ] } unicode-width = { version = "0.2.0", default-features = false } diff --git a/yazi-actor/Cargo.toml b/yazi-actor/Cargo.toml new file mode 100644 index 00000000..d19ad551 --- /dev/null +++ b/yazi-actor/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "yazi-actor" +version = "25.6.11" +edition = "2024" +license = "MIT" +authors = [ "sxyazi " ] +description = "Yazi actor model" +homepage = "https://yazi-rs.github.io" +repository = "https://github.com/sxyazi/yazi" + +[dependencies] +yazi-boot = { path = "../yazi-boot", version = "25.6.11" } +yazi-config = { path = "../yazi-config", version = "25.6.11" } +yazi-core = { path = "../yazi-core", version = "25.6.11" } +yazi-dds = { path = "../yazi-dds", version = "25.6.11" } +yazi-fs = { path = "../yazi-fs", version = "25.6.11" } +yazi-macro = { path = "../yazi-macro", version = "25.6.11" } +yazi-parser = { path = "../yazi-parser", version = "25.6.11" } +yazi-plugin = { path = "../yazi-plugin", version = "25.6.11" } +yazi-proxy = { path = "../yazi-proxy", version = "25.6.11" } +yazi-shared = { path = "../yazi-shared", version = "25.6.11" } +yazi-term = { path = "../yazi-term", version = "25.6.11" } +yazi-widgets = { path = "../yazi-widgets", version = "25.6.11" } + +# External dependencies +anyhow = { workspace = true } +crossterm = { workspace = true } +futures = { workspace = true } +paste = { workspace = true } +scopeguard = { workspace = true } +tokio = { workspace = true } +tokio-stream = { workspace = true } +tracing = { workspace = true } + +[target."cfg(unix)".dependencies] +libc = { workspace = true } + +[target.'cfg(target_os = "macos")'.dependencies] +crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] } diff --git a/yazi-actor/src/actor.rs b/yazi-actor/src/actor.rs new file mode 100644 index 00000000..4e349981 --- /dev/null +++ b/yazi-actor/src/actor.rs @@ -0,0 +1,12 @@ +use anyhow::Result; +use yazi_shared::event::Data; + +use crate::Ctx; + +pub trait Actor { + type Options; + + const NAME: &'static str; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result; +} diff --git a/yazi-actor/src/cmp/arrow.rs b/yazi-actor/src/cmp/arrow.rs new file mode 100644 index 00000000..f4dca5c6 --- /dev/null +++ b/yazi-actor/src/cmp/arrow.rs @@ -0,0 +1,19 @@ +use anyhow::Result; +use yazi_macro::{render, succ}; +use yazi_parser::ArrowOpt; +use yazi_shared::event::Data; +use yazi_widgets::Scrollable; + +use crate::{Actor, Ctx}; + +pub struct Arrow; + +impl Actor for Arrow { + type Options = ArrowOpt; + + const NAME: &'static str = "arrow"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + succ!(render!(cx.cmp.scroll(opt.step))); + } +} diff --git a/yazi-actor/src/cmp/close.rs b/yazi-actor/src/cmp/close.rs new file mode 100644 index 00000000..aa40ab0e --- /dev/null +++ b/yazi-actor/src/cmp/close.rs @@ -0,0 +1,26 @@ +use std::mem; + +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::{cmp::CloseOpt, input::CompleteOpt}; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Close; + +impl Actor for Close { + type Options = CloseOpt; + + const NAME: &'static str = "close"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let cmp = &mut cx.core.cmp; + if let Some(item) = cmp.selected().filter(|_| opt.submit).cloned() { + return act!(complete, cx.core.input, CompleteOpt { item, _ticket: cmp.ticket }); + } + + cmp.caches.clear(); + succ!(render!(mem::replace(&mut cmp.visible, false))); + } +} diff --git a/yazi-core/src/cmp/commands/mod.rs b/yazi-actor/src/cmp/mod.rs similarity index 100% rename from yazi-core/src/cmp/commands/mod.rs rename to yazi-actor/src/cmp/mod.rs diff --git a/yazi-core/src/cmp/commands/show.rs b/yazi-actor/src/cmp/show.rs similarity index 57% rename from yazi-core/src/cmp/commands/show.rs rename to yazi-actor/src/cmp/show.rs index 1ede77d8..e209eeda 100644 --- a/yazi-core/src/cmp/commands/show.rs +++ b/yazi-actor/src/cmp/show.rs @@ -1,40 +1,49 @@ use std::{mem, ops::ControlFlow}; -use yazi_macro::render; +use anyhow::Result; +use yazi_macro::{render, succ}; use yazi_parser::cmp::ShowOpt; use yazi_proxy::options::CmpItem; -use yazi_shared::{osstr_contains, osstr_starts_with}; +use yazi_shared::{event::Data, osstr_contains, osstr_starts_with}; -use crate::cmp::Cmp; +use crate::{Actor, Ctx}; const LIMIT: usize = 30; -impl Cmp { - #[yazi_codegen::command] - pub fn show(&mut self, opt: ShowOpt) { - if self.ticket != opt.ticket { - return; +pub struct Show; + +impl Actor for Show { + type Options = ShowOpt; + + const NAME: &'static str = "show"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let cmp = &mut cx.cmp; + if cmp.ticket != opt.ticket { + succ!(); } if !opt.cache.is_empty() { - self.caches.insert(opt.cache_name.clone(), opt.cache); + cmp.caches.insert(opt.cache_name.clone(), opt.cache); } - let Some(cache) = self.caches.get(&opt.cache_name) else { - return; + let Some(cache) = cmp.caches.get(&opt.cache_name) else { + succ!(); }; - self.ticket = opt.ticket; - self.cands = Self::match_candidates(&opt.word, cache); - if self.cands.is_empty() { - return render!(mem::replace(&mut self.visible, false)); + cmp.ticket = opt.ticket; + cmp.cands = Self::match_candidates(&opt.word, cache); + if cmp.cands.is_empty() { + succ!(render!(mem::replace(&mut cmp.visible, false))); } - self.offset = 0; - self.cursor = 0; - self.visible = true; - render!(); + cmp.offset = 0; + cmp.cursor = 0; + cmp.visible = true; + succ!(render!()); } +} +impl Show { fn match_candidates(word: &str, cache: &[CmpItem]) -> Vec { let smart = !word.bytes().any(|c| c.is_ascii_uppercase()); diff --git a/yazi-core/src/cmp/commands/trigger.rs b/yazi-actor/src/cmp/trigger.rs similarity index 76% rename from yazi-core/src/cmp/commands/trigger.rs rename to yazi-actor/src/cmp/trigger.rs index 94b8c9c5..5f066b62 100644 --- a/yazi-core/src/cmp/commands/trigger.rs +++ b/yazi-actor/src/cmp/trigger.rs @@ -1,38 +1,41 @@ use std::{ffi::OsString, mem, path::{MAIN_SEPARATOR_STR, Path, PathBuf}}; +use anyhow::Result; use tokio::fs; use yazi_fs::{CWD, expand_path}; -use yazi_macro::{emit, render}; -use yazi_parser::cmp::TriggerOpt; +use yazi_macro::{act, emit, render, succ}; +use yazi_parser::cmp::{ShowOpt, TriggerOpt}; use yazi_proxy::options::CmpItem; -use yazi_shared::{event::Cmd, natsort}; +use yazi_shared::{event::{Cmd, Data}, natsort}; -use crate::cmp::Cmp; +use crate::{Actor, Ctx}; -impl Cmp { - #[yazi_codegen::command] - pub fn trigger(&mut self, opt: TriggerOpt) { +pub struct Trigger; + +impl Actor for Trigger { + type Options = TriggerOpt; + + const NAME: &'static str = "trigger"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let cmp = &mut cx.cmp; if let Some(t) = opt.ticket { - if t < self.ticket { - return; + if t < cmp.ticket { + succ!(); } - self.ticket = t; + cmp.ticket = t; } let Some((parent, word)) = Self::split_path(&opt.word) else { - return self.close(false); + return act!(cmp:close, cx, false); }; - if self.caches.contains_key(&parent) { - return self.show( - Cmd::default() - .with_any("cache-name", parent) - .with("word", word) - .with("ticket", self.ticket), - ); + if cmp.caches.contains_key(&parent) { + let ticket = cmp.ticket; + return act!(cmp:show, cx, ShowOpt { cache_name: parent, word: word.into(), ticket, ..Default::default() }); } - let ticket = self.ticket; + let ticket = cmp.ticket; tokio::spawn(async move { let mut dir = fs::read_dir(&parent).await?; let mut cache = vec![]; @@ -65,9 +68,11 @@ impl Cmp { Ok::<_, anyhow::Error>(()) }); - render!(mem::replace(&mut self.visible, false)); + succ!(render!(mem::replace(&mut cmp.visible, false))); } +} +impl Trigger { fn split_path(s: &str) -> Option<(PathBuf, String)> { if s == "~" { return None; // We don't autocomplete a `~`, but `~/` @@ -93,7 +98,7 @@ mod tests { use super::*; fn compare(s: &str, parent: &str, child: &str) { - let (p, c) = Cmp::split_path(s).unwrap(); + let (p, c) = Trigger::split_path(s).unwrap(); let p = p.strip_prefix(yazi_fs::CWD.load().as_ref()).unwrap_or(&p); assert_eq!((p, c.as_str()), (Path::new(parent), child)); } diff --git a/yazi-actor/src/confirm/arrow.rs b/yazi-actor/src/confirm/arrow.rs new file mode 100644 index 00000000..fa38848d --- /dev/null +++ b/yazi-actor/src/confirm/arrow.rs @@ -0,0 +1,26 @@ +use anyhow::Result; +use yazi_macro::{render, succ}; +use yazi_parser::ArrowOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Arrow; + +impl Actor for Arrow { + type Options = ArrowOpt; + + const NAME: &'static str = "arrow"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let confirm = &mut cx.core.confirm; + + let area = cx.core.mgr.area(confirm.position); + let len = confirm.list.line_count(area.width); + + let old = confirm.offset; + confirm.offset = opt.step.add(confirm.offset, len, area.height as _); + + succ!(render!(old != confirm.offset)); + } +} diff --git a/yazi-actor/src/confirm/close.rs b/yazi-actor/src/confirm/close.rs new file mode 100644 index 00000000..fcbb4e2b --- /dev/null +++ b/yazi-actor/src/confirm/close.rs @@ -0,0 +1,23 @@ +use anyhow::Result; +use yazi_macro::{render, succ}; +use yazi_parser::confirm::CloseOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Close; + +impl Actor for Close { + type Options = CloseOpt; + + const NAME: &'static str = "close"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + if let Some(cb) = cx.confirm.callback.take() { + _ = cb.send(opt.submit); + } + + cx.confirm.visible = false; + succ!(render!()); + } +} diff --git a/yazi-core/src/confirm/commands/mod.rs b/yazi-actor/src/confirm/mod.rs similarity index 100% rename from yazi-core/src/confirm/commands/mod.rs rename to yazi-actor/src/confirm/mod.rs diff --git a/yazi-actor/src/confirm/show.rs b/yazi-actor/src/confirm/show.rs new file mode 100644 index 00000000..66e9c13e --- /dev/null +++ b/yazi-actor/src/confirm/show.rs @@ -0,0 +1,31 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::confirm::ShowOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Show; + +impl Actor for Show { + type Options = ShowOpt; + + const NAME: &'static str = "show"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + act!(confirm:close, cx)?; + + let confirm = &mut cx.confirm; + confirm.title = opt.cfg.title; + confirm.body = opt.cfg.body; + confirm.list = opt.cfg.list; + + confirm.position = opt.cfg.position; + confirm.offset = 0; + + confirm.callback = Some(opt.tx); + confirm.visible = true; + + succ!(render!()); + } +} diff --git a/yazi-actor/src/context.rs b/yazi-actor/src/context.rs new file mode 100644 index 00000000..47fe887e --- /dev/null +++ b/yazi-actor/src/context.rs @@ -0,0 +1,83 @@ +use std::ops::{Deref, DerefMut}; + +use anyhow::{Result, anyhow}; +use yazi_core::{Core, mgr::Tabs, tab::{Folder, Tab}, tasks::Tasks}; +use yazi_fs::File; +use yazi_shared::{Id, url::Url}; + +pub struct Ctx<'a> { + pub core: &'a mut Core, + pub tab: usize, +} + +impl Deref for Ctx<'_> { + type Target = Core; + + #[inline] + fn deref(&self) -> &Self::Target { self.core } +} + +impl DerefMut for Ctx<'_> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { self.core } +} + +impl<'a> Ctx<'a> { + #[inline] + pub fn new(core: &'a mut Core, tab: Option) -> Result { + let tab = if let Some(id) = tab { + core + .mgr + .tabs + .iter() + .position(|t| t.id == id) + .ok_or_else(|| anyhow!("Tab with id {id} not found"))? + } else { + core.mgr.tabs.cursor + }; + + Ok(Self { core, tab }) + } + + #[inline] + pub fn active(core: &'a mut Core) -> Self { + let tab = core.mgr.tabs.cursor; + Self { core, tab } + } + + #[inline] + pub fn tabs(&self) -> &Tabs { &self.mgr.tabs } + + #[inline] + pub fn tabs_mut(&mut self) -> &mut Tabs { &mut self.mgr.tabs } + + #[inline] + pub fn tab(&self) -> &Tab { &self.tabs()[self.tab] } + + #[inline] + pub fn tab_mut(&mut self) -> &mut Tab { &mut self.core.mgr.tabs[self.tab] } + + #[inline] + pub fn cwd(&self) -> &Url { self.tab().cwd() } + + #[inline] + pub fn parent(&self) -> Option<&Folder> { self.tab().parent.as_ref() } + + #[inline] + pub fn parent_mut(&mut self) -> Option<&mut Folder> { self.tab_mut().parent.as_mut() } + + #[inline] + pub fn current(&self) -> &Folder { &self.tab().current } + + #[inline] + pub fn current_mut(&mut self) -> &mut Folder { &mut self.tab_mut().current } + + #[inline] + pub fn hovered(&self) -> Option<&File> { self.tab().hovered() } + + #[inline] + pub fn hovered_folder(&self) -> Option<&Folder> { self.tab().hovered_folder() } + + #[inline] + pub fn tasks(&self) -> &Tasks { &self.tasks } +} diff --git a/yazi-actor/src/help/arrow.rs b/yazi-actor/src/help/arrow.rs new file mode 100644 index 00000000..ab59d822 --- /dev/null +++ b/yazi-actor/src/help/arrow.rs @@ -0,0 +1,19 @@ +use anyhow::Result; +use yazi_macro::{render, succ}; +use yazi_parser::ArrowOpt; +use yazi_shared::event::Data; +use yazi_widgets::Scrollable; + +use crate::{Actor, Ctx}; + +pub struct Arrow; + +impl Actor for Arrow { + type Options = ArrowOpt; + + const NAME: &'static str = "arrow"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + succ!(render!(cx.help.scroll(opt.step))); + } +} diff --git a/yazi-actor/src/help/escape.rs b/yazi-actor/src/help/escape.rs new file mode 100644 index 00000000..e03c84a5 --- /dev/null +++ b/yazi-actor/src/help/escape.rs @@ -0,0 +1,27 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Escape; + +impl Actor for Escape { + type Options = VoidOpt; + + const NAME: &'static str = "escape"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + if cx.help.keyword().is_none() { + return act!(help:toggle, cx, cx.help.layer); + } + + let help = &mut cx.help; + help.keyword = String::new(); + help.in_filter = None; + help.filter_apply(); + + succ!(render!()); + } +} diff --git a/yazi-actor/src/help/filter.rs b/yazi-actor/src/help/filter.rs new file mode 100644 index 00000000..120ddfa6 --- /dev/null +++ b/yazi-actor/src/help/filter.rs @@ -0,0 +1,22 @@ +use anyhow::Result; +use yazi_macro::{render, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Filter; + +impl Actor for Filter { + type Options = VoidOpt; + + const NAME: &'static str = "filter"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + let help = &mut cx.help; + + help.in_filter = Some(Default::default()); + help.filter_apply(); + succ!(render!()); + } +} diff --git a/yazi-actor/src/help/mod.rs b/yazi-actor/src/help/mod.rs new file mode 100644 index 00000000..37276cc1 --- /dev/null +++ b/yazi-actor/src/help/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(arrow escape filter toggle); diff --git a/yazi-actor/src/help/toggle.rs b/yazi-actor/src/help/toggle.rs new file mode 100644 index 00000000..c67d9666 --- /dev/null +++ b/yazi-actor/src/help/toggle.rs @@ -0,0 +1,29 @@ +use anyhow::Result; +use yazi_macro::{render, succ}; +use yazi_parser::help::ToggleOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Toggle; + +impl Actor for Toggle { + type Options = ToggleOpt; + + const NAME: &'static str = "toggle"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let help = &mut cx.help; + + help.visible = !help.visible; + help.layer = opt.layer; + + help.keyword = String::new(); + help.in_filter = None; + help.filter_apply(); + + help.offset = 0; + help.cursor = 0; + succ!(render!()); + } +} diff --git a/yazi-actor/src/input/close.rs b/yazi-actor/src/input/close.rs new file mode 100644 index 00000000..c11ea87f --- /dev/null +++ b/yazi-actor/src/input/close.rs @@ -0,0 +1,28 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::input::CloseOpt; +use yazi_shared::{errors::InputError, event::Data}; + +use crate::{Actor, Ctx}; + +pub struct Close; + +impl Actor for Close { + type Options = CloseOpt; + + const NAME: &'static str = "close"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let input = &mut cx.input; + input.visible = false; + input.ticket.next(); + + if let Some(tx) = input.tx.take() { + let value = input.snap().value.clone(); + _ = tx.send(if opt.submit { Ok(value) } else { Err(InputError::Canceled(value)) }); + } + + act!(cmp:close, cx)?; + succ!(render!()); + } +} diff --git a/yazi-actor/src/input/escape.rs b/yazi-actor/src/input/escape.rs new file mode 100644 index 00000000..ce62f47e --- /dev/null +++ b/yazi-actor/src/input/escape.rs @@ -0,0 +1,30 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; +use yazi_widgets::input::InputOp; + +use crate::{Actor, Ctx}; + +pub struct Escape; + +impl Actor for Escape { + type Options = VoidOpt; + + const NAME: &'static str = "escape"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + use yazi_widgets::input::InputMode as M; + let input = &mut cx.input; + + let mode = input.snap().mode; + match mode { + M::Normal if input.snap_mut().op == InputOp::None => act!(input:close, cx), + M::Insert => act!(cmp:close, cx), + M::Normal | M::Replace => Ok(().into()), + }?; + + act!(escape, cx.input)?; + succ!(render!()); + } +} diff --git a/yazi-core/src/input/commands/mod.rs b/yazi-actor/src/input/mod.rs similarity index 100% rename from yazi-core/src/input/commands/mod.rs rename to yazi-actor/src/input/mod.rs diff --git a/yazi-actor/src/input/show.rs b/yazi-actor/src/input/show.rs new file mode 100644 index 00000000..0bd61562 --- /dev/null +++ b/yazi-actor/src/input/show.rs @@ -0,0 +1,55 @@ +use std::ops::DerefMut; + +use anyhow::Result; +use yazi_config::YAZI; +use yazi_macro::{act, render, succ}; +use yazi_parser::input::ShowOpt; +use yazi_shared::{errors::InputError, event::Data}; +use yazi_widgets::input::InputCallback; + +use crate::{Actor, Ctx}; + +pub struct Show; + +impl Actor for Show { + type Options = ShowOpt; + + const NAME: &'static str = "show"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + act!(input:close, cx)?; + + let input = &mut cx.input; + input.visible = true; + input.title = opt.cfg.title; + input.position = opt.cfg.position; + + // Typing + input.tx = Some(opt.tx.clone()); + let ticket = input.ticket.clone(); + + // Reset input + let cb: InputCallback = Box::new(move |before, after| { + if opt.cfg.realtime { + opt.tx.send(Err(InputError::Typed(format!("{before}{after}")))).ok(); + } else if opt.cfg.completion { + opt.tx.send(Err(InputError::Completed(before.to_owned(), ticket.current()))).ok(); + } + }); + *input.deref_mut() = yazi_widgets::input::Input::new( + opt.cfg.value, + opt.cfg.position.offset.width.saturating_sub(YAZI.input.border()) as usize, + opt.cfg.obscure, + cb, + ); + + // Set cursor after reset + // TODO: remove this + if let Some(cursor) = opt.cfg.cursor { + input.snap_mut().cursor = cursor; + act!(r#move, input)?; + } + + succ!(render!()); + } +} diff --git a/yazi-actor/src/lib.rs b/yazi-actor/src/lib.rs new file mode 100644 index 00000000..40feb38e --- /dev/null +++ b/yazi-actor/src/lib.rs @@ -0,0 +1,5 @@ +extern crate self as yazi_actor; + +yazi_macro::mod_pub!(cmp confirm help input mgr pick spot tasks which); + +yazi_macro::mod_flat!(actor context); diff --git a/yazi-actor/src/mgr/arrow.rs b/yazi-actor/src/mgr/arrow.rs new file mode 100644 index 00000000..06fcd675 --- /dev/null +++ b/yazi-actor/src/mgr/arrow.rs @@ -0,0 +1,33 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::ArrowOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Arrow; + +impl Actor for Arrow { + type Options = ArrowOpt; + + const NAME: &'static str = "arrow"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let tab = cx.tab_mut(); + if !tab.current.arrow(opt.step) { + succ!(); + } + + // Visual selection + if let Some((start, items)) = tab.mode.visual_mut() { + let end = tab.current.cursor; + *items = (start.min(end)..=end.max(start)).collect(); + } + + act!(mgr:hover, cx, None)?; + act!(mgr:peek, cx)?; + act!(mgr:watch, cx)?; + + succ!(render!()); + } +} diff --git a/yazi-actor/src/mgr/back.rs b/yazi-actor/src/mgr/back.rs new file mode 100644 index 00000000..8163d386 --- /dev/null +++ b/yazi-actor/src/mgr/back.rs @@ -0,0 +1,21 @@ +use anyhow::Result; +use yazi_macro::{act, succ}; +use yazi_parser::{VoidOpt, tab::CdSource}; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Back; + +impl Actor for Back { + type Options = VoidOpt; + + const NAME: &'static str = "back"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + if let Some(u) = cx.tab_mut().backstack.shift_backward().cloned() { + return act!(mgr:cd, cx, (u, CdSource::Back)); + } + succ!(); + } +} diff --git a/yazi-core/src/mgr/commands/bulk_rename.rs b/yazi-actor/src/mgr/bulk_rename.rs similarity index 89% rename from yazi-core/src/mgr/commands/bulk_rename.rs rename to yazi-actor/src/mgr/bulk_rename.rs index 11048137..6cf2d673 100644 --- a/yazi-core/src/mgr/commands/bulk_rename.rs +++ b/yazi-actor/src/mgr/bulk_rename.rs @@ -7,25 +7,32 @@ use tokio::{fs::{self, OpenOptions}, io::AsyncWriteExt}; use yazi_config::YAZI; use yazi_dds::Pubsub; use yazi_fs::{File, FilesOp, max_common_root, maybe_exists, paths_to_same_file}; -use yazi_macro::err; +use yazi_macro::{err, succ}; +use yazi_parser::VoidOpt; use yazi_proxy::{AppProxy, HIDER, TasksProxy, WATCHER}; -use yazi_shared::{terminal_clear, url::Url}; +use yazi_shared::{event::Data, terminal_clear, url::Url}; use yazi_term::tty::TTY; -use crate::mgr::Mgr; +use crate::{Actor, Ctx}; -impl Mgr { - pub(super) fn bulk_rename(&self) { +pub struct BulkRename; + +impl Actor for BulkRename { + type Options = VoidOpt; + + const NAME: &'static str = "bulk_rename"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { let Some(opener) = YAZI.opener.block(YAZI.open.all("bulk-rename.txt", "text/plain")) else { - return AppProxy::notify_warn("Bulk rename", "No text opener found"); + succ!(AppProxy::notify_warn("Bulk rename", "No text opener found")); }; - let old: Vec<_> = self.selected_or_hovered().collect(); + let old: Vec<_> = cx.tab().selected_or_hovered().collect(); let root = max_common_root(&old); let old: Vec<_> = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect(); - let cwd = self.cwd().clone(); + let cwd = cx.cwd().clone(); tokio::spawn(async move { let tmp = YAZI.preview.tmpfile("bulk"); let s = old.iter().map(|o| o.as_os_str()).collect::>().join(OsStr::new("\n")); @@ -50,11 +57,14 @@ impl Mgr { let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().take(old.len()).map(PathBuf::from).collect(); - Self::bulk_rename_do(root, old, new).await + Self::r#do(root, old, new).await }); + succ!(); } +} - async fn bulk_rename_do(root: PathBuf, old: Vec, new: Vec) -> Result<()> { +impl BulkRename { + async fn r#do(root: PathBuf, old: Vec, new: Vec) -> Result<()> { terminal_clear(TTY.writer())?; if old.len() != new.len() { #[rustfmt::skip] @@ -176,7 +186,7 @@ mod tests { #[test] fn test_sort() { fn cmp(input: &[(&str, &str)], expected: &[(&str, &str)]) { - let sorted = Mgr::prioritized_paths( + let sorted = BulkRename::prioritized_paths( input.iter().map(|&(o, _)| o.into()).collect(), input.iter().map(|&(_, n)| n.into()).collect(), ); diff --git a/yazi-core/src/tab/commands/cd.rs b/yazi-actor/src/mgr/cd.rs similarity index 51% rename from yazi-core/src/tab/commands/cd.rs rename to yazi-actor/src/mgr/cd.rs index 8f8f659e..52f9cda1 100644 --- a/yazi-core/src/tab/commands/cd.rs +++ b/yazi-actor/src/mgr/cd.rs @@ -1,67 +1,72 @@ use std::{mem, time::Duration}; +use anyhow::Result; use tokio::pin; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use yazi_config::popup::InputCfg; use yazi_dds::Pubsub; use yazi_fs::{File, FilesOp, expand_path}; -use yazi_macro::{err, render}; +use yazi_macro::{act, err, render, succ}; use yazi_parser::tab::CdOpt; -use yazi_proxy::{CmpProxy, InputProxy, MgrProxy, TabProxy}; -use yazi_shared::{Debounce, errors::InputError, url::Url}; +use yazi_proxy::{CmpProxy, InputProxy, MgrProxy}; +use yazi_shared::{Debounce, errors::InputError, event::Data, url::Url}; -use crate::tab::Tab; +use crate::{Actor, Ctx}; -impl Tab { - #[yazi_codegen::command] - pub fn cd(&mut self, opt: CdOpt) { - if !self.try_escape_visual() { - return; - } +pub struct Cd; +impl Actor for Cd { + type Options = CdOpt; + + const NAME: &'static str = "cd"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + act!(mgr:escape_visual, cx)?; if opt.interactive { - return self.cd_interactive(); + return Self::cd_interactive(); } - if opt.target == *self.cwd() { - return; + let tab = cx.tab_mut(); + if opt.target == *tab.cwd() { + succ!(); } // Take parent to history - if let Some(rep) = self.parent.take() { - self.history.insert(rep.url.to_owned(), rep); + if let Some(rep) = tab.parent.take() { + tab.history.insert(rep.url.to_owned(), rep); } // Backstack if opt.source.big_jump() { - if self.current.url.is_regular() { - self.backstack.push(&self.current.url); + if tab.current.url.is_regular() { + tab.backstack.push(&tab.current.url); } if opt.target.is_regular() { - self.backstack.push(&opt.target); + tab.backstack.push(&opt.target); } } // Current - let rep = self.history.remove_or(&opt.target); - let rep = mem::replace(&mut self.current, rep); + let rep = tab.history.remove_or(&opt.target); + let rep = mem::replace(&mut tab.current, rep); if rep.url.is_regular() { - self.history.insert(rep.url.to_owned(), rep); + tab.history.insert(rep.url.to_owned(), rep); } // Parent if let Some(parent) = opt.target.parent_url() { - self.parent = Some(self.history.remove_or(&parent)); + tab.parent = Some(tab.history.remove_or(&parent)); } - err!(Pubsub::pub_after_cd(self.id, self.cwd())); - self.hover(None); - - MgrProxy::refresh(); - render!(); + err!(Pubsub::pub_after_cd(tab.id, tab.cwd())); + act!(mgr:hover, cx)?; + act!(mgr:refresh, cx)?; + succ!(render!()); } +} - fn cd_interactive(&mut self) { +impl Cd { + fn cd_interactive() -> Result { let input = InputProxy::show(InputCfg::cd()); tokio::spawn(async move { @@ -75,13 +80,13 @@ impl Tab { let Ok(file) = File::new(url.clone()).await else { return }; if file.is_dir() { - return TabProxy::cd(&url); + return MgrProxy::cd(&url); } if let Some(p) = url.parent_url() { FilesOp::Upserting(p, [(url.urn_owned(), file)].into()).emit(); } - TabProxy::reveal(&url); + MgrProxy::reveal(&url); } Err(InputError::Completed(before, ticket)) => { CmpProxy::trigger(&before, ticket); @@ -90,5 +95,6 @@ impl Tab { } } }); + succ!(); } } diff --git a/yazi-actor/src/mgr/close.rs b/yazi-actor/src/mgr/close.rs new file mode 100644 index 00000000..a5fc4807 --- /dev/null +++ b/yazi-actor/src/mgr/close.rs @@ -0,0 +1,22 @@ +use anyhow::Result; +use yazi_macro::act; +use yazi_parser::mgr::CloseOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Close; + +impl Actor for Close { + type Options = CloseOpt; + + const NAME: &'static str = "close"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + if cx.tabs().len() > 1 { + act!(mgr:tab_close, cx, cx.tabs().cursor) + } else { + act!(mgr:quit, cx, opt.0) + } + } +} diff --git a/yazi-core/src/tab/commands/copy.rs b/yazi-actor/src/mgr/copy.rs similarity index 61% rename from yazi-core/src/tab/commands/copy.rs rename to yazi-actor/src/mgr/copy.rs index cd0b9621..cee378f3 100644 --- a/yazi-core/src/tab/commands/copy.rs +++ b/yazi-actor/src/mgr/copy.rs @@ -1,22 +1,28 @@ use std::{ffi::OsString, path::Path}; +use anyhow::{Result, bail}; +use yazi_macro::{act, succ}; use yazi_parser::tab::CopyOpt; +use yazi_shared::event::Data; use yazi_widgets::CLIPBOARD; -use crate::tab::Tab; +use crate::{Actor, Ctx}; -impl Tab { - #[yazi_codegen::command] - pub fn copy(&mut self, opt: CopyOpt) { - if !self.try_escape_visual() { - return; - } +pub struct Copy; + +impl Actor for Copy { + type Options = CopyOpt; + + const NAME: &'static str = "copy"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + act!(mgr:escape_visual, cx)?; let mut s = OsString::new(); let mut it = if opt.hovered { - Box::new(self.hovered().map(|h| &h.url).into_iter()) + Box::new(cx.hovered().map(|h| &h.url).into_iter()) } else { - self.selected_or_hovered() + cx.tab().selected_or_hovered() } .peekable(); @@ -26,7 +32,7 @@ impl Tab { "dirname" => opt.separator.transform(u.parent().unwrap_or(Path::new(""))), "filename" => opt.separator.transform(u.name()), "name_without_ext" => opt.separator.transform(u.file_stem().unwrap_or_default()), - _ => return, + _ => bail!("Unknown copy type: {}", opt.r#type), }); if it.peek().is_some() { s.push("\n"); @@ -35,9 +41,10 @@ impl Tab { // Copy the CWD path regardless even if the directory is empty if s.is_empty() && opt.r#type == "dirname" { - s.push(self.cwd()); + s.push(cx.cwd()); } futures::executor::block_on(CLIPBOARD.set(s)); + succ!(); } } diff --git a/yazi-core/src/mgr/commands/create.rs b/yazi-actor/src/mgr/create.rs similarity index 68% rename from yazi-core/src/mgr/commands/create.rs rename to yazi-actor/src/mgr/create.rs index d216670c..59480ddb 100644 --- a/yazi-core/src/mgr/commands/create.rs +++ b/yazi-actor/src/mgr/create.rs @@ -2,16 +2,22 @@ use anyhow::Result; use tokio::fs; use yazi_config::popup::{ConfirmCfg, InputCfg}; use yazi_fs::{File, FilesOp, maybe_exists, ok_or_not_found, realname}; +use yazi_macro::succ; use yazi_parser::mgr::CreateOpt; -use yazi_proxy::{ConfirmProxy, InputProxy, TabProxy, WATCHER}; -use yazi_shared::url::{Url, UrnBuf}; +use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy, WATCHER}; +use yazi_shared::{event::Data, url::{Url, UrnBuf}}; -use crate::mgr::Mgr; +use crate::{Actor, Ctx}; -impl Mgr { - #[yazi_codegen::command] - pub fn create(&self, opt: CreateOpt) { - let cwd = self.cwd().to_owned(); +pub struct Create; + +impl Actor for Create { + type Options = CreateOpt; + + const NAME: &'static str = "create"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let cwd = cx.cwd().to_owned(); let mut input = InputProxy::show(InputCfg::create(opt.dir)); tokio::spawn(async move { @@ -28,11 +34,14 @@ impl Mgr { return; } - _ = Self::create_do(new, opt.dir || name.ends_with('/') || name.ends_with('\\')).await; + _ = Self::r#do(new, opt.dir || name.ends_with('/') || name.ends_with('\\')).await; }); + succ!(); } +} - async fn create_do(new: Url, dir: bool) -> Result<()> { +impl Create { + async fn r#do(new: Url, dir: bool) -> Result<()> { let Some(parent) = new.parent_url() else { return Ok(()) }; let _permit = WATCHER.acquire().await.unwrap(); @@ -50,7 +59,7 @@ impl Mgr { if let Ok(f) = File::new(new.clone()).await { FilesOp::Upserting(parent, [(f.urn_owned(), f)].into()).emit(); - TabProxy::reveal(&new) + MgrProxy::reveal(&new) } Ok(()) } diff --git a/yazi-actor/src/mgr/enter.rs b/yazi-actor/src/mgr/enter.rs new file mode 100644 index 00000000..81642f04 --- /dev/null +++ b/yazi-actor/src/mgr/enter.rs @@ -0,0 +1,20 @@ +use anyhow::Result; +use yazi_macro::{act, succ}; +use yazi_parser::{VoidOpt, tab::CdSource}; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Enter; + +impl Actor for Enter { + type Options = VoidOpt; + + const NAME: &'static str = "enter"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + let Some(h) = cx.hovered().filter(|h| h.is_dir()) else { succ!() }; + + act!(mgr:cd, cx, (h.url.to_regular(), CdSource::Enter)) + } +} diff --git a/yazi-actor/src/mgr/escape.rs b/yazi-actor/src/mgr/escape.rs new file mode 100644 index 00000000..4f9761ad --- /dev/null +++ b/yazi-actor/src/mgr/escape.rs @@ -0,0 +1,146 @@ +use anyhow::{Result, bail}; +use yazi_macro::{act, render, render_and, succ}; +use yazi_parser::{VoidOpt, tab::EscapeOpt}; +use yazi_proxy::AppProxy; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Escape; + +impl Actor for Escape { + type Options = EscapeOpt; + + const NAME: &'static str = "escape"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + if opt.is_empty() { + _ = act!(mgr:escape_find, cx)? != false + || act!(mgr:escape_visual, cx)? != false + || act!(mgr:escape_filter, cx)? != false + || act!(mgr:escape_select, cx)? != false + || act!(mgr:escape_search, cx)? != false; + succ!(); + } + + if opt.contains(EscapeOpt::FIND) { + act!(mgr:escape_find, cx)?; + } + if opt.contains(EscapeOpt::VISUAL) { + act!(mgr:escape_visual, cx)?; + } + if opt.contains(EscapeOpt::FILTER) { + act!(mgr:escape_filter, cx)?; + } + if opt.contains(EscapeOpt::SELECT) { + act!(mgr:escape_select, cx)?; + } + if opt.contains(EscapeOpt::SEARCH) { + act!(mgr:escape_search, cx)?; + } + succ!(); + } +} + +// --- Find +pub struct EscapeFind; + +impl Actor for EscapeFind { + type Options = VoidOpt; + + const NAME: &'static str = "escape_find"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + succ!(render_and!(cx.tab_mut().finder.take().is_some())) + } +} + +// --- Visual +pub struct EscapeVisual; + +impl Actor for EscapeVisual { + type Options = VoidOpt; + + const NAME: &'static str = "escape_visual"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + let tab = cx.tab_mut(); + + let select = tab.mode.is_select(); + let Some((_, indices)) = tab.mode.take_visual() else { succ!(false) }; + + render!(); + let urls: Vec<_> = + indices.into_iter().filter_map(|i| tab.current.files.get(i)).map(|f| &f.url).collect(); + + if !select { + tab.selected.remove_many(&urls); + } else if tab.selected.add_many(&urls) != urls.len() { + AppProxy::notify_warn( + "Escape visual mode", + "Some files cannot be selected, due to path nesting conflict.", + ); + bail!("Some files cannot be selected, due to path nesting conflict."); + } + + succ!(true) + } +} + +// --- Filter +pub struct EscapeFilter; + +impl Actor for EscapeFilter { + type Options = VoidOpt; + + const NAME: &'static str = "escape_filter"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + if cx.current_mut().files.filter().is_none() { + succ!(false); + } + + act!(mgr:filter_do, cx)?; + render!(); + succ!(true); + } +} + +// --- Select +pub struct EscapeSelect; + +impl Actor for EscapeSelect { + type Options = VoidOpt; + + const NAME: &'static str = "escape_select"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + let tab = cx.tab_mut(); + if tab.selected.is_empty() { + succ!(false); + } + + tab.selected.clear(); + if tab.hovered().is_some_and(|h| h.is_dir()) { + act!(mgr:peek, cx, true)?; + } + + render!(); + succ!(true); + } +} + +// --- Search +pub struct EscapeSearch; + +impl Actor for EscapeSearch { + type Options = VoidOpt; + + const NAME: &'static str = "escape_search"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + let b = cx.cwd().is_search(); + act!(mgr:search_stop, cx)?; + succ!(render_and!(b)); + } +} diff --git a/yazi-core/src/tab/commands/filter.rs b/yazi-actor/src/mgr/filter.rs similarity index 70% rename from yazi-core/src/tab/commands/filter.rs rename to yazi-actor/src/mgr/filter.rs index c1f73741..d3d0192c 100644 --- a/yazi-core/src/tab/commands/filter.rs +++ b/yazi-actor/src/mgr/filter.rs @@ -1,19 +1,25 @@ use std::time::Duration; +use anyhow::Result; use tokio::pin; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use yazi_config::popup::InputCfg; use yazi_fs::FilterCase; -use yazi_macro::emit; +use yazi_macro::{emit, succ}; use yazi_parser::tab::FilterOpt; use yazi_proxy::InputProxy; -use yazi_shared::{Debounce, errors::InputError, event::Cmd}; +use yazi_shared::{Debounce, errors::InputError, event::{Cmd, Data}}; -use crate::tab::Tab; +use crate::{Actor, Ctx}; -impl Tab { - #[yazi_codegen::command] - pub fn filter(&mut self, opt: FilterOpt) { +pub struct Filter; + +impl Actor for Filter { + type Options = FilterOpt; + + const NAME: &'static str = "filter"; + + fn act(_: &mut Ctx, opt: Self::Options) -> Result { let input = InputProxy::show(InputCfg::filter()); tokio::spawn(async move { @@ -32,5 +38,6 @@ impl Tab { )); } }); + succ!(); } } diff --git a/yazi-actor/src/mgr/filter_do.rs b/yazi-actor/src/mgr/filter_do.rs new file mode 100644 index 00000000..780ac91d --- /dev/null +++ b/yazi-actor/src/mgr/filter_do.rs @@ -0,0 +1,36 @@ +use anyhow::Result; +use yazi_fs::Filter; +use yazi_macro::{act, render, succ}; +use yazi_parser::tab::FilterOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct FilterDo; + +impl Actor for FilterDo { + type Options = FilterOpt; + + const NAME: &'static str = "filter_do"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let filter = if opt.query.is_empty() { None } else { Some(Filter::new(&opt.query, opt.case)?) }; + + let hovered = cx.hovered().map(|f| f.urn_owned()); + if cx.current_mut().files.set_filter(filter) { + cx.current_mut().repos(hovered.as_ref()); + } + + if cx.hovered().map(|f| f.urn()) != hovered.as_ref().map(|u| u.as_urn()) { + act!(mgr:hover, cx)?; + act!(mgr:peek, cx)?; + act!(mgr:watch, cx)?; + } + + if opt.done { + act!(mgr:update_paged, cx)?; + } + + succ!(render!()); + } +} diff --git a/yazi-core/src/tab/commands/find.rs b/yazi-actor/src/mgr/find.rs similarity index 69% rename from yazi-core/src/tab/commands/find.rs rename to yazi-actor/src/mgr/find.rs index d1478ad8..d14566a1 100644 --- a/yazi-core/src/tab/commands/find.rs +++ b/yazi-actor/src/mgr/find.rs @@ -1,19 +1,25 @@ use std::time::Duration; +use anyhow::Result; use tokio::pin; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use yazi_config::popup::InputCfg; use yazi_fs::FilterCase; -use yazi_macro::emit; +use yazi_macro::{emit, succ}; use yazi_parser::tab::FindOpt; use yazi_proxy::InputProxy; -use yazi_shared::{Debounce, errors::InputError, event::Cmd}; +use yazi_shared::{Debounce, errors::InputError, event::{Cmd, Data}}; -use crate::tab::Tab; +use crate::{Actor, Ctx}; -impl Tab { - #[yazi_codegen::command] - pub fn find(&mut self, opt: FindOpt) { +pub struct Find; + +impl Actor for Find { + type Options = FindOpt; + + const NAME: &'static str = "find"; + + fn act(_: &mut Ctx, opt: Self::Options) -> Result { let input = InputProxy::show(InputCfg::find(opt.prev)); tokio::spawn(async move { @@ -29,5 +35,6 @@ impl Tab { )); } }); + succ!(); } } diff --git a/yazi-actor/src/mgr/find_arrow.rs b/yazi-actor/src/mgr/find_arrow.rs new file mode 100644 index 00000000..67522f55 --- /dev/null +++ b/yazi-actor/src/mgr/find_arrow.rs @@ -0,0 +1,31 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::tab::FindArrowOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct FindArrow; + +impl Actor for FindArrow { + type Options = FindArrowOpt; + + const NAME: &'static str = "find_arrow"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let tab = cx.tab_mut(); + let Some(finder) = &mut tab.finder else { succ!() }; + + render!(finder.catchup(&tab.current)); + let offset = if opt.prev { + finder.prev(&tab.current.files, tab.current.cursor, false) + } else { + finder.next(&tab.current.files, tab.current.cursor, false) + }; + + if let Some(offset) = offset { + act!(mgr:arrow, cx, offset)?; + } + succ!(); + } +} diff --git a/yazi-actor/src/mgr/find_do.rs b/yazi-actor/src/mgr/find_do.rs new file mode 100644 index 00000000..c50e9d48 --- /dev/null +++ b/yazi-actor/src/mgr/find_do.rs @@ -0,0 +1,39 @@ +use anyhow::Result; +use yazi_core::tab::Finder; +use yazi_macro::{act, render, succ}; +use yazi_parser::tab::FindDoOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct FindDo; + +impl Actor for FindDo { + type Options = FindDoOpt; + + const NAME: &'static str = "find_do"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + if opt.query.is_empty() { + return act!(mgr:escape_find, cx); + } + + let finder = Finder::new(&opt.query, opt.case)?; + if matches!(&cx.tab().finder, Some(f) if f.filter == finder.filter) { + succ!(); + } + + let step = if opt.prev { + finder.prev(&cx.current().files, cx.current().cursor, true) + } else { + finder.next(&cx.current().files, cx.current().cursor, true) + }; + + if let Some(step) = step { + act!(mgr:arrow, cx, step)?; + } + + cx.tab_mut().finder = Some(finder); + succ!(render!()); + } +} diff --git a/yazi-actor/src/mgr/follow.rs b/yazi-actor/src/mgr/follow.rs new file mode 100644 index 00000000..7ac6963e --- /dev/null +++ b/yazi-actor/src/mgr/follow.rs @@ -0,0 +1,28 @@ +use anyhow::Result; +use yazi_fs::clean_url; +use yazi_macro::{act, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Follow; + +impl Actor for Follow { + type Options = VoidOpt; + + const NAME: &'static str = "follow"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + let Some(file) = cx.hovered() else { succ!() }; + let Some(link_to) = &file.link_to else { succ!() }; + + if link_to.is_absolute() { + act!(mgr:reveal, cx, link_to.to_owned()) + } else if let Some(p) = file.url.parent_url() { + act!(mgr:reveal, cx, clean_url(&p.join(link_to))) + } else { + succ!() + } + } +} diff --git a/yazi-actor/src/mgr/forward.rs b/yazi-actor/src/mgr/forward.rs new file mode 100644 index 00000000..14f2a1ea --- /dev/null +++ b/yazi-actor/src/mgr/forward.rs @@ -0,0 +1,21 @@ +use anyhow::Result; +use yazi_macro::{act, succ}; +use yazi_parser::{VoidOpt, tab::CdSource}; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Forward; + +impl Actor for Forward { + type Options = VoidOpt; + + const NAME: &'static str = "forward"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + match cx.tab_mut().backstack.shift_forward().cloned() { + Some(u) => act!(mgr:cd, cx, (u, CdSource::Forward)), + None => succ!(), + } + } +} diff --git a/yazi-actor/src/mgr/hardlink.rs b/yazi-actor/src/mgr/hardlink.rs new file mode 100644 index 00000000..751f4ea1 --- /dev/null +++ b/yazi-actor/src/mgr/hardlink.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use yazi_macro::succ; +use yazi_parser::mgr::HardlinkOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Hardlink; + +impl Actor for Hardlink { + type Options = HardlinkOpt; + + const NAME: &'static str = "hardlink"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let mgr = &mut cx.core.mgr; + let tab = &mgr.tabs[cx.tab]; + + if !mgr.yanked.cut { + cx.core.tasks.file_hardlink(&mgr.yanked, tab.cwd(), opt.force, opt.follow); + } + + succ!(); + } +} diff --git a/yazi-actor/src/mgr/hidden.rs b/yazi-actor/src/mgr/hidden.rs new file mode 100644 index 00000000..1755c4ee --- /dev/null +++ b/yazi-actor/src/mgr/hidden.rs @@ -0,0 +1,33 @@ +use anyhow::Result; +use yazi_macro::{act, succ}; +use yazi_parser::mgr::HiddenOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Hidden; + +impl Actor for Hidden { + type Options = HiddenOpt; + + const NAME: &'static str = "hidden"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let tab = cx.tab_mut(); + tab.pref.show_hidden = opt.state.unwrap_or(!tab.pref.show_hidden); + + let hovered = tab.hovered().map(|f| f.url_owned()); + tab.apply_files_attrs(); + + if hovered.as_ref() != tab.hovered().map(|f| &f.url) { + act!(mgr:hover, cx, hovered)?; + act!(mgr:peek, cx)?; + act!(mgr:watch, cx)?; + } else if tab.hovered().is_some_and(|f| f.is_dir()) { + act!(mgr:peek, cx, true)?; + } + + act!(mgr:update_paged, cx)?; + succ!(); + } +} diff --git a/yazi-actor/src/mgr/hover.rs b/yazi-actor/src/mgr/hover.rs new file mode 100644 index 00000000..c49b8a0e --- /dev/null +++ b/yazi-actor/src/mgr/hover.rs @@ -0,0 +1,52 @@ +use anyhow::Result; +use yazi_dds::Pubsub; +use yazi_macro::{act, err, render, succ}; +use yazi_parser::tab::{HoverDoOpt, HoverOpt}; +use yazi_shared::{event::Data, url::Urn}; + +use crate::{Actor, Ctx}; + +pub struct Hover; + +impl Actor for Hover { + type Options = HoverOpt; + + const NAME: &'static str = "hover"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + if let Some(u) = opt.url { + act!(mgr:hover_do, cx, u)?; + } else { + cx.current_mut().arrow(0); + } + + // Publish through DDS + let tab = cx.tab(); + err!(Pubsub::pub_after_hover(tab.id, tab.hovered().map(|h| &h.url))); + succ!(); + } +} + +// --- Do +pub struct HoverDo; + +impl Actor for HoverDo { + type Options = HoverDoOpt; + + const NAME: &'static str = "hover_do"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + // Hover on the file + if let Ok(p) = opt.url.strip_prefix(cx.cwd()) { + render!(cx.current_mut().hover(Urn::new(p))); + } + + // Turn on tracing + if cx.hovered().is_some_and(|h| h.url == opt.url) { + // `hover(Some)` occurs after user actions, such as create, rename, reveal, etc. + // At this point, it's intuitive to track the location of the file regardless. + cx.current_mut().trace = Some(opt.url.urn_owned()); + } + succ!(); + } +} diff --git a/yazi-actor/src/mgr/leave.rs b/yazi-actor/src/mgr/leave.rs new file mode 100644 index 00000000..dabb7bbd --- /dev/null +++ b/yazi-actor/src/mgr/leave.rs @@ -0,0 +1,24 @@ +use anyhow::Result; +use yazi_macro::{act, succ}; +use yazi_parser::{VoidOpt, tab::CdSource}; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Leave; + +impl Actor for Leave { + type Options = VoidOpt; + + const NAME: &'static str = "leave"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + let url = cx + .hovered() + .and_then(|h| h.url.parent_url()) + .filter(|u| u != cx.cwd()) + .or_else(|| cx.cwd().parent_url()); + + if let Some(u) = url { act!(mgr:cd, cx, (u.into_regular(), CdSource::Leave)) } else { succ!() } + } +} diff --git a/yazi-actor/src/mgr/linemode.rs b/yazi-actor/src/mgr/linemode.rs new file mode 100644 index 00000000..33843790 --- /dev/null +++ b/yazi-actor/src/mgr/linemode.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use yazi_macro::{render, succ}; +use yazi_parser::mgr::LinemodeOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Linemode; + +impl Actor for Linemode { + type Options = LinemodeOpt; + + const NAME: &'static str = "linemode"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let tab = cx.tab_mut(); + + if opt.new != tab.pref.linemode { + tab.pref.linemode = opt.new.into_owned(); + render!(); + } + + succ!(); + } +} diff --git a/yazi-actor/src/mgr/link.rs b/yazi-actor/src/mgr/link.rs new file mode 100644 index 00000000..4bd770da --- /dev/null +++ b/yazi-actor/src/mgr/link.rs @@ -0,0 +1,25 @@ +use anyhow::Result; +use yazi_macro::succ; +use yazi_parser::mgr::LinkOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Link; + +impl Actor for Link { + type Options = LinkOpt; + + const NAME: &'static str = "link"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let mgr = &mut cx.core.mgr; + let tab = &mgr.tabs[cx.tab]; + + if !mgr.yanked.cut { + cx.core.tasks.file_link(&mgr.yanked, tab.cwd(), opt.relative, opt.force); + } + + succ!(); + } +} diff --git a/yazi-core/src/mgr/commands/mod.rs b/yazi-actor/src/mgr/mod.rs similarity index 55% rename from yazi-core/src/mgr/commands/mod.rs rename to yazi-actor/src/mgr/mod.rs index cf2735f8..1499db7e 100644 --- a/yazi-core/src/mgr/commands/mod.rs +++ b/yazi-actor/src/mgr/mod.rs @@ -1,8 +1,25 @@ yazi_macro::mod_flat!( + arrow + back bulk_rename + cd close + copy create + enter + escape + filter + filter_do + find + find_arrow + find_do + follow + forward hardlink + hidden + hover + leave + linemode link open paste @@ -11,19 +28,28 @@ yazi_macro::mod_flat!( refresh remove rename + reveal + search seek + shell + sort spot suspend tab_close tab_create tab_swap tab_switch + toggle + toggle_all unyank update_files update_mimes update_paged + update_peeked + update_spotted update_tasks update_yanked + visual_mode watch yank ); diff --git a/yazi-core/src/mgr/commands/open.rs b/yazi-actor/src/mgr/open.rs similarity index 58% rename from yazi-core/src/mgr/commands/open.rs rename to yazi-actor/src/mgr/open.rs index 6906b8c9..61faab2e 100644 --- a/yazi-core/src/mgr/commands/open.rs +++ b/yazi-actor/src/mgr/open.rs @@ -1,29 +1,34 @@ use std::{borrow::Cow, iter}; +use anyhow::Result; use tracing::error; use yazi_config::{YAZI, popup::PickCfg}; +use yazi_core::tab::Folder; use yazi_fs::File; +use yazi_macro::{act, succ}; use yazi_parser::mgr::OpenOpt; use yazi_plugin::isolate; use yazi_proxy::{MgrProxy, PickProxy, TasksProxy, options::OpenDoOpt}; -use yazi_shared::{MIME_DIR, event::CmdCow, url::Url}; +use yazi_shared::{MIME_DIR, event::{CmdCow, Data}, url::Url}; -use crate::{mgr::Mgr, tab::Folder, tasks::Tasks}; +use crate::{Actor, Ctx, mgr::Quit}; -impl Mgr { - #[yazi_codegen::command] - pub fn open(&mut self, opt: OpenOpt, tasks: &Tasks) { - if !self.active_mut().try_escape_visual() { - return; - } - let Some(hovered) = self.hovered().map(|h| h.url_owned()) else { - return; - }; +pub struct Open; + +impl Actor for Open { + type Options = OpenOpt; + + const NAME: &'static str = "open"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + act!(mgr:escape_visual, cx)?; + + let Some(hovered) = cx.hovered().map(|h| h.url_owned()) else { succ!() }; let mut selected = - if opt.hovered { Box::new(iter::once(&hovered)) } else { self.selected_or_hovered() }; - if Self::quit_with_selected(opt, &mut selected) { - return; + if opt.hovered { Box::new(iter::once(&hovered)) } else { cx.tab().selected_or_hovered() }; + if Quit::quit_with_selected(opt, &mut selected) { + succ!(); } let mut todo = vec![]; @@ -31,9 +36,9 @@ impl Mgr { .cloned() .enumerate() .map(|(i, u)| { - if self.mimetype.contains(&u) { + if cx.mgr.mimetype.contains(&u) { (u, "") - } else if self.guess_folder(&u) { + } else if Self::guess_folder(cx, &u) { (u, MIME_DIR) } else { todo.push(i); @@ -42,10 +47,9 @@ impl Mgr { }) .collect(); - let cwd = self.cwd().clone(); + let cwd = cx.cwd().clone(); if todo.is_empty() { - return self - .open_do(OpenDoOpt { cwd, hovered, targets, interactive: opt.interactive }, tasks); + return act!(mgr:open_do, cx, OpenDoOpt { cwd, hovered, targets, interactive: opt.interactive }); } tokio::spawn(async move { @@ -64,25 +68,34 @@ impl Mgr { MgrProxy::open_do(OpenDoOpt { cwd, hovered, targets, interactive: opt.interactive }); }); + succ!(); } +} - #[yazi_codegen::command] - pub fn open_do(&mut self, opt: OpenDoOpt, tasks: &Tasks) { +// --- Do +pub struct OpenDo; + +impl Actor for OpenDo { + type Options = OpenDoOpt; + + const NAME: &'static str = "open_do"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { let mut targets = opt.targets; targets.iter_mut().filter(|(_, m)| m.is_empty()).for_each(|(u, m)| { - *m = self.mimetype.by_url(u).unwrap_or_default(); + *m = cx.mgr.mimetype.by_url(u).unwrap_or_default(); }); targets.retain(|(_, m)| !m.is_empty()); if targets.is_empty() { - return; + succ!(); } else if !opt.interactive { - return tasks.process_from_files(opt.cwd, opt.hovered, targets); + succ!(cx.tasks.process_from_files(opt.cwd, opt.hovered, targets)); } let openers: Vec<_> = YAZI.opener.all(YAZI.open.common(&targets).into_iter()); if openers.is_empty() { - return; + succ!(); } let pick = PickProxy::show(PickCfg::open(openers.iter().map(|o| o.desc()).collect())); @@ -92,9 +105,12 @@ impl Mgr { TasksProxy::open_with(Cow::Borrowed(openers[choice]), opt.cwd, urls); } }); + succ!(); } +} - fn guess_folder(&self, url: &Url) -> bool { +impl Open { + fn guess_folder(cx: &Ctx, url: &Url) -> bool { let Some(p) = url.parent_url() else { return true; }; @@ -105,9 +121,9 @@ impl Mgr { }) }; - find(Some(self.current())) - || find(self.parent()) - || find(self.hovered_folder()) - || find(self.active().history.get(&p)) + find(Some(cx.current())) + || find(cx.parent()) + || find(cx.hovered_folder()) + || find(cx.tab().history.get(&p)) } } diff --git a/yazi-actor/src/mgr/paste.rs b/yazi-actor/src/mgr/paste.rs new file mode 100644 index 00000000..880ee6d5 --- /dev/null +++ b/yazi-actor/src/mgr/paste.rs @@ -0,0 +1,29 @@ +use anyhow::Result; +use yazi_macro::{act, succ}; +use yazi_parser::mgr::PasteOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Paste; + +impl Actor for Paste { + type Options = PasteOpt; + + const NAME: &'static str = "paste"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let mgr = &mut cx.core.mgr; + let tab = &mgr.tabs[cx.tab]; + let (src, dest) = (mgr.yanked.iter().collect::>(), tab.cwd()); + + if mgr.yanked.cut { + cx.core.tasks.file_cut(&src, dest, opt.force); + + mgr.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(&src)); + act!(mgr:unyank, cx) + } else { + succ!(cx.core.tasks.file_copy(&src, dest, opt.force, opt.follow)); + } + } +} diff --git a/yazi-actor/src/mgr/peek.rs b/yazi-actor/src/mgr/peek.rs new file mode 100644 index 00000000..4bc78e4f --- /dev/null +++ b/yazi-actor/src/mgr/peek.rs @@ -0,0 +1,54 @@ +use anyhow::Result; +use yazi_macro::succ; +use yazi_parser::mgr::PeekOpt; +use yazi_proxy::HIDER; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Peek; + +impl Actor for Peek { + type Options = PeekOpt; + + const NAME: &'static str = "peek"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let Some(hovered) = cx.hovered().cloned() else { + succ!(cx.tab_mut().preview.reset()); + }; + if HIDER.try_acquire().is_err() { + succ!(cx.tab_mut().preview.reset_image()); + } + + let mime = cx.mgr.mimetype.by_file_owned(&hovered).unwrap_or_default(); + let folder = cx.tab().hovered_folder().map(|f| (f.offset, f.cha)); + + if !cx.tab().preview.same_url(&hovered.url) { + cx.tab_mut().preview.skip = folder.map(|f| f.0).unwrap_or_default(); + } + if !cx.tab().preview.same_file(&hovered, &mime) { + cx.tab_mut().preview.reset(); + } + + if matches!(opt.only_if, Some(u) if u != hovered.url) { + succ!(); + } + + if let Some(skip) = opt.skip { + let preview = &mut cx.tab_mut().preview; + if opt.upper_bound { + preview.skip = preview.skip.min(skip); + } else { + preview.skip = skip; + } + } + + if hovered.is_dir() { + cx.tab_mut().preview.go_folder(hovered, folder.map(|(_, cha)| cha), opt.force); + } else { + cx.tab_mut().preview.go(hovered, mime, opt.force); + } + succ!(); + } +} diff --git a/yazi-core/src/mgr/commands/quit.rs b/yazi-actor/src/mgr/quit.rs similarity index 77% rename from yazi-core/src/mgr/commands/quit.rs rename to yazi-actor/src/mgr/quit.rs index fc664142..41da9e09 100644 --- a/yazi-core/src/mgr/commands/quit.rs +++ b/yazi-actor/src/mgr/quit.rs @@ -1,29 +1,34 @@ use std::{ffi::OsString, time::Duration}; +use anyhow::Result; use tokio::{select, time}; use yazi_boot::ARGS; use yazi_config::popup::ConfirmCfg; -use yazi_macro::emit; +use yazi_macro::{emit, succ}; use yazi_parser::mgr::{OpenOpt, QuitOpt}; use yazi_proxy::ConfirmProxy; -use yazi_shared::{event::EventQuit, url::Url}; +use yazi_shared::{event::{Data, EventQuit}, url::Url}; -use crate::{mgr::Mgr, tasks::Tasks}; +use crate::{Actor, Ctx}; -impl Mgr { - #[yazi_codegen::command] - pub fn quit(&self, opt: QuitOpt, tasks: &Tasks) { +pub struct Quit; + +impl Actor for Quit { + type Options = QuitOpt; + + const NAME: &'static str = "quit"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { let event = opt.into(); - let ongoing = tasks.ongoing().clone(); + let ongoing = cx.tasks().ongoing().clone(); let (left, left_names) = { let ongoing = ongoing.lock(); (ongoing.len(), ongoing.values().take(11).map(|t| t.name.clone()).collect()) }; if left == 0 { - emit!(Quit(event)); - return; + succ!(emit!(Quit(event))); } tokio::spawn(async move { @@ -52,8 +57,11 @@ impl Mgr { emit!(Quit(event)); } }); + succ!(); } +} +impl Quit { pub(super) fn quit_with_selected<'a, I>(opt: OpenOpt, selected: I) -> bool where I: Iterator, diff --git a/yazi-actor/src/mgr/refresh.rs b/yazi-actor/src/mgr/refresh.rs new file mode 100644 index 00000000..e48edd02 --- /dev/null +++ b/yazi-actor/src/mgr/refresh.rs @@ -0,0 +1,39 @@ +use anyhow::Result; +use crossterm::{execute, terminal::SetTitle}; +use yazi_config::YAZI; +use yazi_fs::CWD; +use yazi_macro::{act, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; +use yazi_term::tty::TTY; + +use crate::{Actor, Ctx}; + +pub struct Refresh; + +impl Actor for Refresh { + type Options = VoidOpt; + + const NAME: &'static str = "refresh"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + if let (_, Some(s)) = (CWD.set(cx.cwd()), YAZI.mgr.title()) { + execute!(TTY.writer(), SetTitle(s)).ok(); + } + + cx.tab_mut().apply_files_attrs(); + + if let Some(p) = cx.parent() { + cx.mgr.watcher.trigger_dirs(&[cx.current(), p]); + } else { + cx.mgr.watcher.trigger_dirs(&[cx.current()]); + } + + act!(mgr:peek, cx, false)?; + act!(mgr:watch, cx)?; + act!(mgr:update_paged, cx)?; + + cx.tasks().prework_sorted(&cx.current().files); + succ!(); + } +} diff --git a/yazi-actor/src/mgr/remove.rs b/yazi-actor/src/mgr/remove.rs new file mode 100644 index 00000000..2dca462f --- /dev/null +++ b/yazi-actor/src/mgr/remove.rs @@ -0,0 +1,70 @@ +use anyhow::Result; +use yazi_config::popup::ConfirmCfg; +use yazi_macro::{act, succ}; +use yazi_parser::mgr::RemoveOpt; +use yazi_proxy::{ConfirmProxy, MgrProxy}; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Remove; + +impl Actor for Remove { + type Options = RemoveOpt; + + const NAME: &'static str = "remove"; + + fn act(cx: &mut Ctx, mut opt: Self::Options) -> Result { + act!(mgr:escape_visual, cx)?; + + opt.targets = if opt.hovered { + cx.hovered().map_or(vec![], |h| vec![h.url.clone()]) + } else { + cx.tab().selected_or_hovered().cloned().collect() + }; + + if opt.targets.is_empty() { + succ!(); + } else if opt.force { + return act!(mgr:remove_do, cx, opt); + } + + let confirm = ConfirmProxy::show(if opt.permanently { + ConfirmCfg::delete(&opt.targets) + } else { + ConfirmCfg::trash(&opt.targets) + }); + + tokio::spawn(async move { + if confirm.await { + MgrProxy::remove_do(opt.targets, opt.permanently); + } + }); + succ!(); + } +} + +// --- Do +pub struct RemoveDo; + +impl Actor for RemoveDo { + type Options = RemoveOpt; + + const NAME: &'static str = "remove_do"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let mgr = &mut cx.mgr; + + mgr.tabs.iter_mut().for_each(|t| { + t.selected.remove_many(&opt.targets); + }); + + for u in &opt.targets { + mgr.yanked.remove(u); + } + + mgr.yanked.catchup_revision(false); + cx.tasks.file_remove(opt.targets, opt.permanently); + succ!(); + } +} diff --git a/yazi-core/src/mgr/commands/rename.rs b/yazi-actor/src/mgr/rename.rs similarity index 74% rename from yazi-core/src/mgr/commands/rename.rs rename to yazi-actor/src/mgr/rename.rs index a753b1a2..5bc5d283 100644 --- a/yazi-core/src/mgr/commands/rename.rs +++ b/yazi-actor/src/mgr/rename.rs @@ -3,23 +3,29 @@ use tokio::fs; use yazi_config::popup::{ConfirmCfg, InputCfg}; use yazi_dds::Pubsub; use yazi_fs::{File, FilesOp, maybe_exists, ok_or_not_found, paths_to_same_file, realname}; -use yazi_macro::err; +use yazi_macro::{act, err, succ}; use yazi_parser::mgr::RenameOpt; -use yazi_proxy::{ConfirmProxy, InputProxy, TabProxy, WATCHER}; -use yazi_shared::{Id, url::{Url, UrnBuf}}; +use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy, WATCHER}; +use yazi_shared::{Id, event::Data, url::{Url, UrnBuf}}; -use crate::mgr::Mgr; +use crate::{Actor, Ctx}; -impl Mgr { - #[yazi_codegen::command] - pub fn rename(&mut self, opt: RenameOpt) { - if !self.active_mut().try_escape_visual() { - return; - } else if !opt.hovered && !self.active().selected.is_empty() { - return self.bulk_rename(); +pub struct Rename; + +impl Actor for Rename { + type Options = RenameOpt; + + const NAME: &'static str = "rename"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + act!(mgr:escape_visual, cx)?; + + if !opt.hovered && !cx.tab_mut().selected.is_empty() { + return act!(mgr:bulk_rename, cx); } - let Some(hovered) = self.hovered() else { return }; + let Some(hovered) = cx.hovered() else { succ!() }; + let name = Self::empty_url_part(&hovered.url, &opt.empty); let cursor = match opt.cursor.as_ref() { "start" => Some(0), @@ -33,8 +39,7 @@ impl Mgr { _ => None, }; - let old = hovered.url_owned(); - let tab = self.tabs.active().id; + let (tab, old) = (cx.tab().id, hovered.url_owned()); let mut input = InputProxy::show(InputCfg::rename().with_value(name).with_cursor(cursor)); tokio::spawn(async move { @@ -45,14 +50,17 @@ impl Mgr { let new = Url::from(old.parent().unwrap().join(name)); if opt.force || !maybe_exists(&new).await || paths_to_same_file(&old, &new).await { - Self::rename_do(tab, old, new).await.ok(); + Self::r#do(tab, old, new).await.ok(); } else if ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await { - Self::rename_do(tab, old, new).await.ok(); + Self::r#do(tab, old, new).await.ok(); } }); + succ!(); } +} - async fn rename_do(tab: Id, old: Url, new: Url) -> Result<()> { +impl Rename { + async fn r#do(tab: Id, old: Url, new: Url) -> Result<()> { let Some((p_old, n_old)) = old.pair() else { return Ok(()) }; let Some((p_new, n_new)) = new.pair() else { return Ok(()) }; let _permit = WATCHER.acquire().await.unwrap(); @@ -73,7 +81,7 @@ impl Mgr { FilesOp::Upserting(p_new, [(n_new, file)].into()).emit(); } - TabProxy::reveal(&new); + MgrProxy::reveal(&new); err!(Pubsub::pub_after_rename(tab, &old, &new)); Ok(()) } diff --git a/yazi-actor/src/mgr/reveal.rs b/yazi-actor/src/mgr/reveal.rs new file mode 100644 index 00000000..247b6497 --- /dev/null +++ b/yazi-actor/src/mgr/reveal.rs @@ -0,0 +1,40 @@ +use anyhow::Result; +use yazi_fs::{File, FilesOp}; +use yazi_macro::{act, succ}; +use yazi_parser::tab::RevealOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Reveal; + +impl Actor for Reveal { + type Options = RevealOpt; + + const NAME: &'static str = "reveal"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let Some((parent, child)) = opt.target.pair() else { succ!() }; + + // Cd to the parent directory + act!(mgr:cd, cx, (parent.clone(), opt.source))?; + + // Try to hover on the child file + let tab = cx.tab_mut(); + tab.current.hover(child.as_urn()); + + // If the child is not hovered, which means it doesn't exist, + // create a dummy file + if !opt.no_dummy && tab.hovered().is_none_or(|f| &child != f.urn()) { + let op = FilesOp::Creating(parent, vec![File::from_dummy(opt.target.clone(), None)]); + tab.current.update_pub(tab.id, op); + } + + // Now, we can safely hover on the target + act!(mgr:hover, cx, Some(opt.target))?; + + act!(mgr:peek, cx)?; + act!(mgr:watch, cx)?; + succ!(); + } +} diff --git a/yazi-core/src/tab/commands/search.rs b/yazi-actor/src/mgr/search.rs similarity index 50% rename from yazi-core/src/tab/commands/search.rs rename to yazi-actor/src/mgr/search.rs index bfeb3c0b..d1d33252 100644 --- a/yazi-core/src/tab/commands/search.rs +++ b/yazi-actor/src/mgr/search.rs @@ -1,48 +1,60 @@ use std::{borrow::Cow, mem, time::Duration}; +use anyhow::Result; use tokio::pin; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; -use tracing::error; use yazi_config::popup::InputCfg; use yazi_fs::{FilesOp, cha::Cha}; +use yazi_macro::{act, succ}; use yazi_plugin::external; -use yazi_proxy::{AppProxy, InputProxy, MgrProxy, TabProxy, options::{SearchOpt, SearchOptVia}}; +use yazi_proxy::{InputProxy, MgrProxy, options::{SearchOpt, SearchOptVia}}; +use yazi_shared::event::Data; -use crate::tab::Tab; +use crate::{Actor, Ctx}; -impl Tab { - pub fn search(&mut self, opt: impl TryInto) { - let Ok(mut opt): Result = opt.try_into() else { - return AppProxy::notify_error("Invalid `search` option", "Failed to parse search option"); - }; +pub struct Search; - if let Some(handle) = self.search.take() { +impl Actor for Search { + type Options = SearchOpt; + + const NAME: &'static str = "search"; + + fn act(cx: &mut Ctx, mut opt: Self::Options) -> Result { + if let Some(handle) = cx.tab_mut().search.take() { handle.abort(); } let mut input = InputProxy::show(InputCfg::search(opt.via.into_str()).with_value(&*opt.subject)); + tokio::spawn(async move { if let Some(Ok(subject)) = input.recv().await { opt.subject = Cow::Owned(subject); - TabProxy::search_do(opt); + MgrProxy::search_do(opt); } }); + succ!(); } +} - pub fn search_do(&mut self, opt: impl TryInto) { - let Ok(opt): Result = opt.try_into() else { - return error!("Failed to parse search option for `search_do`"); - }; +// --- Do +pub struct SearchDo; - if let Some(handle) = self.search.take() { +impl Actor for SearchDo { + type Options = SearchOpt; + + const NAME: &'static str = "search_do"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let tab = cx.tab_mut(); + if let Some(handle) = tab.search.take() { handle.abort(); } - let cwd = self.cwd().to_search(opt.subject.as_ref()); - let hidden = self.pref.show_hidden; + let cwd = tab.cwd().to_search(opt.subject.as_ref()); + let hidden = tab.pref.show_hidden; - self.search = Some(tokio::spawn(async move { + tab.search = Some(tokio::spawn(async move { let rx = match opt.via { SearchOptVia::Rg => external::rg(external::RgOpt { cwd: cwd.clone(), @@ -67,7 +79,7 @@ impl Tab { let rx = UnboundedReceiverStream::new(rx).chunks_timeout(5000, Duration::from_millis(500)); pin!(rx); - let ((), ticket) = (TabProxy::cd(&cwd), FilesOp::prepare(&cwd)); + let ((), ticket) = (MgrProxy::cd(&cwd), FilesOp::prepare(&cwd)); while let Some(chunk) = rx.next().await { FilesOp::Part(cwd.clone(), chunk, ticket).emit(); } @@ -75,16 +87,30 @@ impl Tab { Ok(()) })); - } - pub(super) fn search_stop(&mut self) { - if let Some(handle) = self.search.take() { - handle.abort(); - } - if self.cwd().is_search() { - let rep = self.history.remove_or(&self.cwd().to_regular()); - drop(mem::replace(&mut self.current, rep)); - MgrProxy::refresh(); - } + succ!(); + } +} + +// --- Stop +pub struct SearchStop; + +impl Actor for SearchStop { + type Options = (); + + const NAME: &'static str = "search_stop"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + let tab = cx.tab_mut(); + if let Some(handle) = tab.search.take() { + handle.abort(); + } + + if tab.cwd().is_search() { + let rep = tab.history.remove_or(&tab.cwd().to_regular()); + drop(mem::replace(&mut tab.current, rep)); + act!(mgr:refresh, cx)?; + } + succ!(); } } diff --git a/yazi-actor/src/mgr/seek.rs b/yazi-actor/src/mgr/seek.rs new file mode 100644 index 00000000..1195136d --- /dev/null +++ b/yazi-actor/src/mgr/seek.rs @@ -0,0 +1,33 @@ +use anyhow::Result; +use yazi_config::YAZI; +use yazi_macro::succ; +use yazi_parser::mgr::SeekOpt; +use yazi_plugin::isolate; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Seek; + +impl Actor for Seek { + type Options = SeekOpt; + + const NAME: &'static str = "seek"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let Some(hovered) = cx.hovered() else { + succ!(cx.tab_mut().preview.reset()); + }; + + let Some(mime) = cx.mgr.mimetype.by_file(hovered) else { + succ!(cx.tab_mut().preview.reset()); + }; + + let Some(previewer) = YAZI.plugin.previewer(&hovered.url, mime) else { + succ!(cx.tab_mut().preview.reset()); + }; + + isolate::seek_sync(&previewer.run, hovered.clone(), opt.units); + succ!(); + } +} diff --git a/yazi-core/src/tab/commands/shell.rs b/yazi-actor/src/mgr/shell.rs similarity index 57% rename from yazi-core/src/tab/commands/shell.rs rename to yazi-actor/src/mgr/shell.rs index 00343c78..2bae44da 100644 --- a/yazi-core/src/tab/commands/shell.rs +++ b/yazi-actor/src/mgr/shell.rs @@ -1,24 +1,26 @@ -use std::{borrow::Cow, fmt::Display}; +use std::borrow::Cow; +use anyhow::Result; use yazi_config::{opener::OpenerRule, popup::InputCfg}; +use yazi_macro::{act, succ}; use yazi_parser::tab::ShellOpt; -use yazi_proxy::{AppProxy, InputProxy, TasksProxy}; +use yazi_proxy::{InputProxy, TasksProxy}; +use yazi_shared::event::Data; -use crate::tab::Tab; +use crate::{Actor, Ctx}; -impl Tab { - pub fn shell(&mut self, opt: impl TryInto) { - if !self.try_escape_visual() { - return; - } +pub struct Shell; - let mut opt = match opt.try_into() { - Ok(o) => o as ShellOpt, - Err(e) => return AppProxy::notify_warn("`shell` command", e), - }; +impl Actor for Shell { + type Options = ShellOpt; - let cwd = opt.cwd.take().unwrap_or_else(|| self.cwd().clone()); - let selected = self.hovered_and_selected().cloned().collect(); + const NAME: &'static str = "shell"; + + fn act(cx: &mut Ctx, mut opt: Self::Options) -> Result { + act!(mgr:escape_visual, cx)?; + + let cwd = opt.cwd.take().unwrap_or_else(|| cx.cwd().clone()); + let selected = cx.tab().hovered_and_selected().cloned().collect(); let input = opt.interactive.then(|| { InputProxy::show(InputCfg::shell(opt.block).with_value(&*opt.run).with_cursor(opt.cursor)) @@ -48,5 +50,7 @@ impl Tab { selected, ); }); + + succ!(); } } diff --git a/yazi-actor/src/mgr/sort.rs b/yazi-actor/src/mgr/sort.rs new file mode 100644 index 00000000..4eb04253 --- /dev/null +++ b/yazi-actor/src/mgr/sort.rs @@ -0,0 +1,40 @@ +use std::str::FromStr; + +use anyhow::Result; +use yazi_fs::SortBy; +use yazi_macro::{act, succ}; +use yazi_shared::event::{CmdCow, Data}; + +use crate::{Actor, Ctx}; + +pub struct Sort; + +impl Actor for Sort { + type Options = CmdCow; + + const NAME: &'static str = "sort"; + + fn act(cx: &mut Ctx, c: Self::Options) -> Result { + let mut new = cx.tab().pref.clone(); + new.sort_by = c.first_str().and_then(|s| SortBy::from_str(s).ok()).unwrap_or(new.sort_by); + new.sort_reverse = c.maybe_bool("reverse").unwrap_or(new.sort_reverse); + new.sort_dir_first = c.maybe_bool("dir-first").unwrap_or(new.sort_dir_first); + new.sort_sensitive = c.maybe_bool("sensitive").unwrap_or(new.sort_sensitive); + new.sort_translit = c.maybe_bool("translit").unwrap_or(new.sort_translit); + + if new == cx.tab().pref { + succ!(); + } + + cx.tab_mut().pref = new; + cx.tab_mut().apply_files_attrs(); + act!(mgr:hover, cx)?; + + cx.tasks.prework_sorted(&cx.mgr.tabs[cx.tab].current.files); + act!(mgr:peek, cx)?; + act!(mgr:watch, cx)?; + act!(mgr:update_paged, cx)?; + + succ!(); + } +} diff --git a/yazi-actor/src/mgr/spot.rs b/yazi-actor/src/mgr/spot.rs new file mode 100644 index 00000000..70d8aa9f --- /dev/null +++ b/yazi-actor/src/mgr/spot.rs @@ -0,0 +1,32 @@ +use anyhow::Result; +use yazi_macro::succ; +use yazi_parser::mgr::SpotOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Spot; + +impl Actor for Spot { + type Options = SpotOpt; + + const NAME: &'static str = "spot"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let Some(hovered) = cx.hovered().cloned() else { succ!() }; + + let mime = cx.mgr.mimetype.by_file_owned(&hovered).unwrap_or_default(); + // if !self.active().spot.same_file(&hovered, &mime) { + // self.active_mut().spot.reset(); + // } + + if let Some(skip) = opt.skip { + cx.tab_mut().spot.skip = skip; + } else if !cx.tab().spot.same_url(&hovered.url) { + cx.tab_mut().spot.skip = 0; + } + + cx.tab_mut().spot.go(hovered, mime); + succ!(); + } +} diff --git a/yazi-actor/src/mgr/suspend.rs b/yazi-actor/src/mgr/suspend.rs new file mode 100644 index 00000000..35e72250 --- /dev/null +++ b/yazi-actor/src/mgr/suspend.rs @@ -0,0 +1,22 @@ +use anyhow::Result; +use yazi_macro::succ; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Suspend; + +impl Actor for Suspend { + type Options = VoidOpt; + + const NAME: &'static str = "suspend"; + + fn act(_: &mut Ctx, _: Self::Options) -> Result { + #[cfg(unix)] + unsafe { + libc::raise(libc::SIGTSTP); + } + succ!(); + } +} diff --git a/yazi-actor/src/mgr/tab_close.rs b/yazi-actor/src/mgr/tab_close.rs new file mode 100644 index 00000000..eee20fd0 --- /dev/null +++ b/yazi-actor/src/mgr/tab_close.rs @@ -0,0 +1,36 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::mgr::TabCloseOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct TabClose; + +impl Actor for TabClose { + type Options = TabCloseOpt; + + const NAME: &'static str = "tab_close"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let len = cx.tabs().len(); + if len < 2 || opt.idx >= len { + succ!(); + } + + let tabs = cx.tabs_mut(); + tabs.remove(opt.idx).shutdown(); + + if opt.idx > tabs.cursor { + tabs.set_idx(tabs.cursor); + } else { + tabs.set_idx(usize::min(tabs.cursor + 1, tabs.len() - 1)); + } + + let cx = &mut Ctx::active(cx.core); + act!(mgr:refresh, cx)?; + act!(mgr:peek, cx, true)?; + + succ!(render!()); + } +} diff --git a/yazi-actor/src/mgr/tab_create.rs b/yazi-actor/src/mgr/tab_create.rs new file mode 100644 index 00000000..8a1457e7 --- /dev/null +++ b/yazi-actor/src/mgr/tab_create.rs @@ -0,0 +1,55 @@ +use anyhow::Result; +use yazi_core::tab::Tab; +use yazi_macro::{act, render, succ}; +use yazi_parser::{mgr::TabCreateOpt, tab::CdSource}; +use yazi_proxy::AppProxy; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +const MAX_TABS: usize = 9; + +pub struct TabCreate; + +impl Actor for TabCreate { + type Options = TabCreateOpt; + + const NAME: &'static str = "tab_create"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + if cx.tabs().len() >= MAX_TABS { + succ!(AppProxy::notify_warn( + "Too many tabs", + "You can only open up to 9 tabs at the same time." + )); + } + + let mut tab = Tab::default(); + let (cd, url) = if let Some(wd) = opt.wd { + (true, wd) + } else if let Some(h) = cx.hovered() { + tab.pref = cx.tab().pref.clone(); + tab.apply_files_attrs(); + (false, h.url.to_regular()) + } else { + tab.pref = cx.tab().pref.clone(); + tab.apply_files_attrs(); + (true, cx.cwd().to_regular()) + }; + + let tabs = &mut cx.mgr.tabs; + tabs.items.insert(tabs.cursor + 1, tab); + tabs.set_idx(tabs.cursor + 1); + + let cx = &mut Ctx::active(cx.core); + if cd { + act!(mgr:cd, cx, (url, CdSource::Tab))?; + } else { + act!(mgr:reveal, cx, (url, CdSource::Tab))?; + } + + act!(mgr:refresh, cx)?; + act!(mgr:peek, cx, true)?; + succ!(render!()); + } +} diff --git a/yazi-actor/src/mgr/tab_swap.rs b/yazi-actor/src/mgr/tab_swap.rs new file mode 100644 index 00000000..664102c7 --- /dev/null +++ b/yazi-actor/src/mgr/tab_swap.rs @@ -0,0 +1,32 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::ArrowOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct TabSwap; + +impl Actor for TabSwap { + type Options = ArrowOpt; + + const NAME: &'static str = "tab_swap"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let tabs = cx.tabs_mut(); + + let new = opt.step.add(tabs.cursor, tabs.len(), 0); + if new == tabs.cursor { + succ!(); + } + + tabs.items.swap(tabs.cursor, new); + tabs.set_idx(new); + + let cx = &mut Ctx::active(cx.core); + act!(mgr:refresh, cx)?; + act!(mgr:peek, cx, true)?; + + succ!(render!()); + } +} diff --git a/yazi-actor/src/mgr/tab_switch.rs b/yazi-actor/src/mgr/tab_switch.rs new file mode 100644 index 00000000..cae33984 --- /dev/null +++ b/yazi-actor/src/mgr/tab_switch.rs @@ -0,0 +1,34 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::mgr::TabSwitchOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct TabSwitch; + +impl Actor for TabSwitch { + type Options = TabSwitchOpt; + + const NAME: &'static str = "tab_switch"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let tabs = cx.tabs_mut(); + let idx = if opt.relative { + opt.step.saturating_add_unsigned(tabs.cursor).rem_euclid(tabs.len() as _) as _ + } else { + opt.step as usize + }; + + if idx == tabs.cursor || idx >= tabs.len() { + succ!(); + } + + tabs.set_idx(idx); + let cx = &mut Ctx::active(cx.core); + + act!(mgr:refresh, cx)?; + act!(mgr:peek, cx, true)?; + succ!(render!()); + } +} diff --git a/yazi-actor/src/mgr/toggle.rs b/yazi-actor/src/mgr/toggle.rs new file mode 100644 index 00000000..ee66fa8c --- /dev/null +++ b/yazi-actor/src/mgr/toggle.rs @@ -0,0 +1,34 @@ +use anyhow::Result; +use yazi_macro::{render_and, succ}; +use yazi_parser::tab::ToggleOpt; +use yazi_proxy::AppProxy; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Toggle; + +impl Actor for Toggle { + type Options = ToggleOpt; + + const NAME: &'static str = "toggle"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let tab = cx.tab_mut(); + let Some(url) = opt.url.as_ref().or(tab.current.hovered().map(|h| &h.url)) else { succ!() }; + + let b = match opt.state { + Some(true) => render_and!(tab.selected.add(url)), + Some(false) => render_and!(tab.selected.remove(url)) | true, + None => render_and!(tab.selected.remove(url) || tab.selected.add(url)), + }; + + if !b { + AppProxy::notify_warn( + "Toggle", + "This file cannot be selected, due to path nesting conflict.", + ); + } + succ!(); + } +} diff --git a/yazi-actor/src/mgr/toggle_all.rs b/yazi-actor/src/mgr/toggle_all.rs new file mode 100644 index 00000000..20d42192 --- /dev/null +++ b/yazi-actor/src/mgr/toggle_all.rs @@ -0,0 +1,49 @@ +use anyhow::Result; +use yazi_macro::{render, succ}; +use yazi_parser::tab::ToggleAllOpt; +use yazi_proxy::AppProxy; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct ToggleAll; + +impl Actor for ToggleAll { + type Options = ToggleAllOpt; + + const NAME: &'static str = "toggle_all"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + use yazi_shared::Either::*; + let tab = cx.tab_mut(); + + let it = tab.current.files.iter().map(|f| &f.url); + let either = match opt.state { + Some(true) if opt.urls.is_empty() => Left((vec![], it.collect())), + Some(true) => Right((vec![], opt.urls)), + Some(false) if opt.urls.is_empty() => Left((it.collect(), vec![])), + Some(false) => Right((opt.urls, vec![])), + None if opt.urls.is_empty() => Left(it.partition(|&u| tab.selected.contains_key(u))), + None => Right(opt.urls.into_iter().partition(|u| tab.selected.contains_key(u))), + }; + + let warn = match either { + Left((removal, addition)) => { + render!(tab.selected.remove_many(&removal) > 0); + render!(tab.selected.add_many(&addition), > 0) != addition.len() + } + Right((removal, addition)) => { + render!(tab.selected.remove_many(&removal) > 0); + render!(tab.selected.add_many(&addition), > 0) != addition.len() + } + }; + + if warn { + AppProxy::notify_warn( + "Toggle all", + "Some files cannot be selected, due to path nesting conflict.", + ); + } + succ!(); + } +} diff --git a/yazi-actor/src/mgr/unyank.rs b/yazi-actor/src/mgr/unyank.rs new file mode 100644 index 00000000..1b6b47e9 --- /dev/null +++ b/yazi-actor/src/mgr/unyank.rs @@ -0,0 +1,26 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; + +use crate::Actor; + +pub struct Unyank; + +impl Actor for Unyank { + type Options = VoidOpt; + + const NAME: &'static str = "unyank"; + + fn act(cx: &mut crate::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(); + + render!(cx.mgr.yanked.catchup_revision(false)); + if repeek { + act!(mgr:peek, cx, true)?; + } + + succ!(); + } +} diff --git a/yazi-actor/src/mgr/update_files.rs b/yazi-actor/src/mgr/update_files.rs new file mode 100644 index 00000000..c85b08bd --- /dev/null +++ b/yazi-actor/src/mgr/update_files.rs @@ -0,0 +1,112 @@ +use anyhow::Result; +use yazi_core::{mgr::LINKED, tab::Folder}; +use yazi_fs::FilesOp; +use yazi_macro::{act, render, succ}; +use yazi_parser::mgr::UpdateFilesOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct UpdateFiles; + +impl Actor for UpdateFiles { + type Options = UpdateFilesOpt; + + const NAME: &'static str = "update_files"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let revision = cx.current().files.revision; + let linked: Vec<_> = LINKED.read().from_dir(opt.op.cwd()).map(|u| opt.op.rebase(u)).collect(); + for op in [opt.op].into_iter().chain(linked) { + cx.mgr.yanked.apply_op(&op); + Self::update_tab(cx, op).ok(); + } + + render!(cx.mgr.yanked.catchup_revision(false)); + cx.tab_mut().apply_files_attrs(); + + if revision != cx.current().files.revision { + act!(mgr:hover, cx)?; + act!(mgr:peek, cx, false)?; + act!(mgr:watch, cx)?; + act!(mgr:update_paged, cx)?; + } + succ!(); + } +} + +impl UpdateFiles { + fn update_tab(cx: &mut Ctx, op: FilesOp) -> Result { + let url = op.cwd(); + cx.tab_mut().selected.apply_op(&op); + + if url == cx.cwd() { + Self::update_current(cx, op) + } else if matches!(cx.parent(), Some(p) if *url == p.url) { + Self::update_parent(cx, op) + } else if matches!(cx.hovered(), Some(h) if *url == h.url) { + Self::update_hovered(cx, op) + } else { + Self::update_history(cx, op) + } + } + + fn update_parent(cx: &mut Ctx, op: FilesOp) -> Result { + let tab = cx.tab_mut(); + + let urn = tab.current.url.urn(); + let leave = matches!(op, FilesOp::Deleting(_, ref urns) if urns.contains(urn)); + + if let Some(f) = tab.parent.as_mut() { + render!(f.update_pub(tab.id, op)); + render!(f.hover(urn)); + } + + if leave { + act!(mgr:leave, cx)?; + } + succ!(); + } + + fn update_current(cx: &mut Ctx, op: FilesOp) -> Result { + let calc = !matches!(op, FilesOp::Size(..) | FilesOp::Deleting(..)); + + let id = cx.tab().id; + if !cx.current_mut().update_pub(id, op) { + succ!(); + } + + if calc { + cx.tasks.prework_sorted(&cx.current().files); + } + succ!(); + } + + fn update_hovered(cx: &mut Ctx, op: FilesOp) -> Result { + let (id, url) = (cx.tab().id, op.cwd()); + let folder = cx.tab_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(url)); + + if folder.update_pub(id, op) { + act!(mgr:peek, cx, true)?; + } + succ!(); + } + + fn update_history(cx: &mut Ctx, op: FilesOp) -> Result { + let tab = &mut cx.tab_mut(); + let leave = tab.parent.as_ref().and_then(|f| f.url.parent_url().map(|p| (p, f.url.urn()))).is_some_and( + |(p, n)| matches!(op, FilesOp::Deleting(ref parent, ref urns) if *parent == p && urns.contains(n)), + ); + + tab + .history + .entry(op.cwd().clone()) + .or_insert_with(|| Folder::from(op.cwd())) + .update_pub(tab.id, op); + + if leave { + act!(mgr:leave, cx)?; + } + succ!(); + } +} diff --git a/yazi-actor/src/mgr/update_mimes.rs b/yazi-actor/src/mgr/update_mimes.rs new file mode 100644 index 00000000..c000fe78 --- /dev/null +++ b/yazi-actor/src/mgr/update_mimes.rs @@ -0,0 +1,57 @@ +use std::collections::HashMap; + +use anyhow::Result; +use yazi_core::mgr::LINKED; +use yazi_macro::{act, render, succ}; +use yazi_parser::mgr::UpdateMimesOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct UpdateMimes; + +impl Actor for UpdateMimes { + type Options = UpdateMimesOpt; + + const NAME: &'static str = "update_mimes"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let linked = LINKED.read(); + let updates = opt + .updates + .into_iter() + .flat_map(|(key, value)| key.into_url().zip(value.into_string())) + .filter(|(url, mime)| cx.mgr.mimetype.by_url(url) != Some(mime)) + .fold(HashMap::new(), |mut map, (u, m)| { + for u in linked.from_file(&u) { + map.insert(u, m.to_string()); + } + map.insert(u, m.into_owned()); + map + }); + + drop(linked); + if updates.is_empty() { + succ!(); + } + + let affected: Vec<_> = cx + .current() + .paginate(cx.current().page) + .iter() + .filter(|&f| updates.contains_key(&f.url)) + .cloned() + .collect(); + + let repeek = cx.hovered().is_some_and(|f| updates.contains_key(&f.url)); + cx.mgr.mimetype.extend(updates); + + if repeek { + act!(mgr:peek, cx, false)?; + } + cx.tasks.fetch_paged(&affected, &cx.mgr.mimetype); + cx.tasks.preload_paged(&affected, &cx.mgr.mimetype); + + succ!(render!()); + } +} diff --git a/yazi-actor/src/mgr/update_paged.rs b/yazi-actor/src/mgr/update_paged.rs new file mode 100644 index 00000000..421cd0aa --- /dev/null +++ b/yazi-actor/src/mgr/update_paged.rs @@ -0,0 +1,27 @@ +use anyhow::Result; +use yazi_macro::succ; +use yazi_parser::mgr::UpdatePagedOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct UpdatePaged; + +impl Actor for UpdatePaged { + type Options = UpdatePagedOpt; + + const NAME: &'static str = "update_paged"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + if opt.only_if.is_some_and(|u| u != *cx.cwd()) { + succ!(); + } + + let targets = cx.current().paginate(opt.page.unwrap_or(cx.current().page)); + if !targets.is_empty() { + cx.tasks().fetch_paged(targets, &cx.mgr.mimetype); + cx.tasks().preload_paged(targets, &cx.mgr.mimetype); + } + succ!(); + } +} diff --git a/yazi-actor/src/mgr/update_peeked.rs b/yazi-actor/src/mgr/update_peeked.rs new file mode 100644 index 00000000..10d7a6d7 --- /dev/null +++ b/yazi-actor/src/mgr/update_peeked.rs @@ -0,0 +1,27 @@ +use anyhow::Result; +use yazi_macro::{render, succ}; +use yazi_parser::tab::UpdatePeekedOpt; +use yazi_shared::event::Data; + +use crate::Actor; + +pub struct UpdatePeeked; + +impl Actor for UpdatePeeked { + type Options = UpdatePeekedOpt; + + const NAME: &'static str = "update_peeked"; + + fn act(cx: &mut crate::Ctx, opt: Self::Options) -> Result { + let Some(hovered) = cx.hovered().map(|h| &h.url) else { + succ!(cx.tab_mut().preview.reset()); + }; + + if opt.lock.url == *hovered { + cx.tab_mut().preview.lock = Some(opt.lock); + render!(); + } + + succ!(); + } +} diff --git a/yazi-actor/src/mgr/update_spotted.rs b/yazi-actor/src/mgr/update_spotted.rs new file mode 100644 index 00000000..87589658 --- /dev/null +++ b/yazi-actor/src/mgr/update_spotted.rs @@ -0,0 +1,36 @@ +use anyhow::Result; +use yazi_macro::{render, succ}; +use yazi_parser::tab::UpdateSpottedOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct UpdateSpotted; + +impl Actor for UpdateSpotted { + type Options = UpdateSpottedOpt; + + const NAME: &'static str = "update_spotted"; + + fn act(cx: &mut Ctx, mut opt: Self::Options) -> Result { + let tab = cx.tab_mut(); + let Some(hovered) = tab.hovered().map(|h| &h.url) else { + succ!(tab.spot.reset()); + }; + + if opt.lock.url != *hovered { + succ!(); + } + + if tab.spot.lock.as_ref().is_none_or(|l| l.id != opt.lock.id) { + tab.spot.skip = opt.lock.selected().unwrap_or_default(); + } else if let Some(s) = opt.lock.selected() { + tab.spot.skip = s; + } else { + opt.lock.select(Some(tab.spot.skip)); + } + + tab.spot.lock = Some(opt.lock); + succ!(render!()); + } +} diff --git a/yazi-actor/src/mgr/update_tasks.rs b/yazi-actor/src/mgr/update_tasks.rs new file mode 100644 index 00000000..79a7b551 --- /dev/null +++ b/yazi-actor/src/mgr/update_tasks.rs @@ -0,0 +1,19 @@ +use anyhow::Result; +use yazi_macro::succ; +use yazi_parser::mgr::UpdateTasksOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct UpdateTasks; + +impl Actor for UpdateTasks { + type Options = UpdateTasksOpt; + + const NAME: &'static str = "update_tasks"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + cx.mgr.watcher.push_files(opt.urls); + succ!(); + } +} diff --git a/yazi-actor/src/mgr/update_yanked.rs b/yazi-actor/src/mgr/update_yanked.rs new file mode 100644 index 00000000..dd363063 --- /dev/null +++ b/yazi-actor/src/mgr/update_yanked.rs @@ -0,0 +1,24 @@ +use anyhow::Result; +use yazi_core::mgr::Yanked; +use yazi_macro::{render, succ}; +use yazi_parser::mgr::UpdateYankedOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct UpdateYanked; + +impl Actor for UpdateYanked { + type Options = UpdateYankedOpt; + + const NAME: &'static str = "update_yanked"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + if opt.urls.is_empty() && cx.mgr.yanked.is_empty() { + succ!(); + } + + cx.mgr.yanked = Yanked::new(opt.cut, opt.urls); + succ!(render!()); + } +} diff --git a/yazi-actor/src/mgr/visual_mode.rs b/yazi-actor/src/mgr/visual_mode.rs new file mode 100644 index 00000000..e2879013 --- /dev/null +++ b/yazi-actor/src/mgr/visual_mode.rs @@ -0,0 +1,30 @@ +use std::collections::BTreeSet; + +use anyhow::Result; +use yazi_core::tab::Mode; +use yazi_macro::{render, succ}; +use yazi_parser::tab::VisualModeOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct VisualMode; + +impl Actor for VisualMode { + type Options = VisualModeOpt; + + const NAME: &'static str = "visual_mode"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let tab = cx.tab_mut(); + + let idx = tab.current.cursor; + if opt.unset { + tab.mode = Mode::Unset(idx, BTreeSet::from([idx])); + } else { + tab.mode = Mode::Select(idx, BTreeSet::from([idx])); + }; + + succ!(render!()); + } +} diff --git a/yazi-actor/src/mgr/watch.rs b/yazi-actor/src/mgr/watch.rs new file mode 100644 index 00000000..bf9c67d4 --- /dev/null +++ b/yazi-actor/src/mgr/watch.rs @@ -0,0 +1,25 @@ +use std::iter; + +use anyhow::Result; +use yazi_macro::succ; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Watch; + +impl Actor for Watch { + type Options = VoidOpt; + + const NAME: &'static str = "watch"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + let it = iter::once(cx.core.mgr.tabs.active().cwd()) + .chain(cx.core.mgr.tabs.parent().map(|p| &p.url)) + .chain(cx.core.mgr.tabs.hovered().filter(|h| h.is_dir()).map(|h| &h.url)); + + cx.core.mgr.watcher.watch(it); + succ!(); + } +} diff --git a/yazi-actor/src/mgr/yank.rs b/yazi-actor/src/mgr/yank.rs new file mode 100644 index 00000000..256e5907 --- /dev/null +++ b/yazi-actor/src/mgr/yank.rs @@ -0,0 +1,24 @@ +use anyhow::Result; +use yazi_core::mgr::Yanked; +use yazi_macro::{act, render}; +use yazi_parser::mgr::YankOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Yank; + +impl Actor for Yank { + type Options = YankOpt; + + const NAME: &'static str = "yank"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + act!(mgr:escape_visual, cx)?; + + cx.mgr.yanked = Yanked::new(opt.cut, cx.tab().selected_or_hovered().cloned().collect()); + render!(cx.mgr.yanked.catchup_revision(true)); + + act!(mgr:escape_select, cx) + } +} diff --git a/yazi-actor/src/pick/arrow.rs b/yazi-actor/src/pick/arrow.rs new file mode 100644 index 00000000..8d807d7a --- /dev/null +++ b/yazi-actor/src/pick/arrow.rs @@ -0,0 +1,19 @@ +use anyhow::Result; +use yazi_macro::{render, succ}; +use yazi_parser::ArrowOpt; +use yazi_shared::event::Data; +use yazi_widgets::Scrollable; + +use crate::{Actor, Ctx}; + +pub struct Arrow; + +impl Actor for Arrow { + type Options = ArrowOpt; + + const NAME: &'static str = "arrow"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + succ!(render!(cx.pick.scroll(opt.step))); + } +} diff --git a/yazi-actor/src/pick/close.rs b/yazi-actor/src/pick/close.rs new file mode 100644 index 00000000..3c0e69c0 --- /dev/null +++ b/yazi-actor/src/pick/close.rs @@ -0,0 +1,26 @@ +use anyhow::{Result, anyhow}; +use yazi_macro::{render, succ}; +use yazi_parser::pick::CloseOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Close; + +impl Actor for Close { + type Options = CloseOpt; + + const NAME: &'static str = "close"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let pick = &mut cx.pick; + if let Some(cb) = pick.callback.take() { + _ = cb.send(if opt.submit { Ok(pick.cursor) } else { Err(anyhow!("canceled")) }); + } + + pick.cursor = 0; + pick.offset = 0; + pick.visible = false; + succ!(render!()); + } +} diff --git a/yazi-core/src/pick/commands/mod.rs b/yazi-actor/src/pick/mod.rs similarity index 100% rename from yazi-core/src/pick/commands/mod.rs rename to yazi-actor/src/pick/mod.rs diff --git a/yazi-actor/src/pick/show.rs b/yazi-actor/src/pick/show.rs new file mode 100644 index 00000000..31a90846 --- /dev/null +++ b/yazi-actor/src/pick/show.rs @@ -0,0 +1,27 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::pick::ShowOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Show; + +impl Actor for Show { + type Options = ShowOpt; + + const NAME: &'static str = "show"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + act!(pick:close, cx)?; + + let pick = &mut cx.pick; + pick.title = opt.cfg.title; + pick.items = opt.cfg.items; + pick.position = opt.cfg.position; + + pick.callback = Some(opt.tx); + pick.visible = true; + succ!(render!()); + } +} diff --git a/yazi-actor/src/spot/arrow.rs b/yazi-actor/src/spot/arrow.rs new file mode 100644 index 00000000..4aaed7bf --- /dev/null +++ b/yazi-actor/src/spot/arrow.rs @@ -0,0 +1,30 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::ArrowOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Arrow; + +impl Actor for Arrow { + type Options = ArrowOpt; + + const NAME: &'static str = "arrow"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let spot = &mut cx.tab_mut().spot; + let Some(lock) = &mut spot.lock else { succ!() }; + + let new = opt.step.add(spot.skip, lock.len().unwrap_or(u16::MAX as _), 0); + let Some(old) = lock.selected() else { + return act!(mgr:spot, cx, new); + }; + + lock.select(Some(new)); + let new = lock.selected().unwrap(); + + spot.skip = new; + succ!(render!(new != old)); + } +} diff --git a/yazi-actor/src/spot/close.rs b/yazi-actor/src/spot/close.rs new file mode 100644 index 00000000..e9a094de --- /dev/null +++ b/yazi-actor/src/spot/close.rs @@ -0,0 +1,18 @@ +use anyhow::Result; +use yazi_macro::succ; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Close; + +impl Actor for Close { + type Options = VoidOpt; + + const NAME: &'static str = "close"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + succ!(cx.tab_mut().spot.reset()); + } +} diff --git a/yazi-actor/src/spot/copy.rs b/yazi-actor/src/spot/copy.rs new file mode 100644 index 00000000..d5ed965e --- /dev/null +++ b/yazi-actor/src/spot/copy.rs @@ -0,0 +1,36 @@ +use anyhow::Result; +use yazi_macro::succ; +use yazi_parser::spot::CopyOpt; +use yazi_shared::event::Data; +use yazi_widgets::CLIPBOARD; + +use crate::{Actor, Ctx}; + +pub struct Copy; + +impl Actor for Copy { + type Options = CopyOpt; + + const NAME: &'static str = "copy"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let spot = &cx.tab().spot; + let Some(lock) = &spot.lock else { succ!() }; + let Some(table) = lock.table() else { succ!() }; + + let mut s = String::new(); + match opt.r#type.as_ref() { + "cell" => { + let Some(cell) = table.selected_cell() else { succ!() }; + s = cell.to_string(); + } + "line" => { + // TODO + } + _ => {} + } + + futures::executor::block_on(CLIPBOARD.set(s)); + succ!(); + } +} diff --git a/yazi-core/src/spot/commands/mod.rs b/yazi-actor/src/spot/mod.rs similarity index 100% rename from yazi-core/src/spot/commands/mod.rs rename to yazi-actor/src/spot/mod.rs diff --git a/yazi-actor/src/spot/swipe.rs b/yazi-actor/src/spot/swipe.rs new file mode 100644 index 00000000..754d7d3a --- /dev/null +++ b/yazi-actor/src/spot/swipe.rs @@ -0,0 +1,19 @@ +use anyhow::Result; +use yazi_macro::act; +use yazi_parser::ArrowOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Swipe; + +impl Actor for Swipe { + type Options = ArrowOpt; + + const NAME: &'static str = "swipe"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + act!(mgr:arrow, cx, opt)?; + act!(mgr:spot, cx) + } +} diff --git a/yazi-actor/src/tasks/arrow.rs b/yazi-actor/src/tasks/arrow.rs new file mode 100644 index 00000000..922ef794 --- /dev/null +++ b/yazi-actor/src/tasks/arrow.rs @@ -0,0 +1,24 @@ +use anyhow::Result; +use yazi_core::tasks::Tasks; +use yazi_macro::{render, succ}; +use yazi_parser::ArrowOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Arrow; + +impl Actor for Arrow { + type Options = ArrowOpt; + + const NAME: &'static str = "arrow"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + let tasks = &mut cx.tasks; + + let old = tasks.cursor; + tasks.cursor = opt.step.add(tasks.cursor, tasks.summaries.len(), Tasks::limit()); + + succ!(render!(tasks.cursor != old)); + } +} diff --git a/yazi-actor/src/tasks/cancel.rs b/yazi-actor/src/tasks/cancel.rs new file mode 100644 index 00000000..24a62fe7 --- /dev/null +++ b/yazi-actor/src/tasks/cancel.rs @@ -0,0 +1,27 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Cancel; + +impl Actor for Cancel { + type Options = VoidOpt; + + const NAME: &'static str = "cancel"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + let tasks = &mut cx.tasks; + + let id = tasks.ongoing().lock().get_id(tasks.cursor); + if id.map(|id| tasks.scheduler.cancel(id)) != Some(true) { + succ!(); + } + + tasks.summaries = tasks.paginate(); + act!(tasks:arrow, cx)?; + succ!(render!()); + } +} diff --git a/yazi-actor/src/tasks/close.rs b/yazi-actor/src/tasks/close.rs new file mode 100644 index 00000000..688c2e3b --- /dev/null +++ b/yazi-actor/src/tasks/close.rs @@ -0,0 +1,27 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Close; + +impl Actor for Close { + type Options = VoidOpt; + + const NAME: &'static str = "close"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + let tasks = &mut cx.tasks; + if !tasks.visible { + succ!(); + } + + tasks.visible = false; + tasks.summaries = Vec::new(); + + act!(tasks:arrow, cx)?; + succ!(render!()); + } +} diff --git a/yazi-core/src/tasks/commands/inspect.rs b/yazi-actor/src/tasks/inspect.rs similarity index 78% rename from yazi-core/src/tasks/commands/inspect.rs rename to yazi-actor/src/tasks/inspect.rs index c363be78..94c087ec 100644 --- a/yazi-core/src/tasks/commands/inspect.rs +++ b/yazi-actor/src/tasks/inspect.rs @@ -1,19 +1,28 @@ use std::io::Write; +use anyhow::Result; use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode}}; use scopeguard::defer; use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time}; +use yazi_macro::succ; +use yazi_parser::VoidOpt; use yazi_proxy::{AppProxy, HIDER}; -use yazi_shared::{event::CmdCow, terminal_clear}; +use yazi_shared::{event::Data, terminal_clear}; use yazi_term::tty::TTY; -use crate::tasks::Tasks; +use crate::{Actor, Ctx}; -impl Tasks { - pub fn inspect(&self, _: CmdCow) { - let ongoing = self.ongoing().clone(); - let Some(id) = ongoing.lock().get_id(self.cursor) else { - return; +pub struct Inspect; + +impl Actor for Inspect { + type Options = VoidOpt; + + const NAME: &'static str = "inspect"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + let ongoing = cx.tasks.ongoing().clone(); + let Some(id) = ongoing.lock().get_id(cx.tasks.cursor) else { + succ!(); }; tokio::spawn(async move { @@ -67,5 +76,6 @@ impl Tasks { answer = stdin.read_u8().await.unwrap_or(b'q'); } }); + succ!(); } } diff --git a/yazi-core/src/tasks/commands/mod.rs b/yazi-actor/src/tasks/mod.rs similarity index 100% rename from yazi-core/src/tasks/commands/mod.rs rename to yazi-actor/src/tasks/mod.rs diff --git a/yazi-actor/src/tasks/open_with.rs b/yazi-actor/src/tasks/open_with.rs new file mode 100644 index 00000000..e90170e1 --- /dev/null +++ b/yazi-actor/src/tasks/open_with.rs @@ -0,0 +1,22 @@ +use anyhow::Result; +use yazi_macro::succ; +use yazi_proxy::options::OpenWithOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct OpenWith; + +impl Actor for OpenWith { + type Options = OpenWithOpt; + + const NAME: &'static str = "open_with"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + succ!(cx.tasks.process_from_opener( + opt.cwd, + opt.opener, + opt.targets.into_iter().map(|u| u.into_path().into_os_string()).collect(), + )); + } +} diff --git a/yazi-actor/src/tasks/process_exec.rs b/yazi-actor/src/tasks/process_exec.rs new file mode 100644 index 00000000..ea5aab79 --- /dev/null +++ b/yazi-actor/src/tasks/process_exec.rs @@ -0,0 +1,18 @@ +use anyhow::Result; +use yazi_macro::succ; +use yazi_proxy::options::ProcessExecOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct ProcessExec; + +impl Actor for ProcessExec { + type Options = ProcessExecOpt; + + const NAME: &'static str = "process_exec"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + succ!(cx.tasks.scheduler.process_open(opt)); + } +} diff --git a/yazi-actor/src/tasks/show.rs b/yazi-actor/src/tasks/show.rs new file mode 100644 index 00000000..09e7250c --- /dev/null +++ b/yazi-actor/src/tasks/show.rs @@ -0,0 +1,27 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Show; + +impl Actor for Show { + type Options = VoidOpt; + + const NAME: &'static str = "show"; + + fn act(cx: &mut Ctx, _: Self::Options) -> Result { + let tasks = &mut cx.tasks; + if tasks.visible { + succ!(); + } + + tasks.visible = true; + tasks.summaries = tasks.paginate(); + + act!(tasks:arrow, cx)?; + succ!(render!()); + } +} diff --git a/yazi-actor/src/which/callback.rs b/yazi-actor/src/which/callback.rs new file mode 100644 index 00000000..3df4957e --- /dev/null +++ b/yazi-actor/src/which/callback.rs @@ -0,0 +1,19 @@ +use anyhow::Result; +use yazi_macro::succ; +use yazi_parser::which::CallbackOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Callback; + +impl Actor for Callback { + type Options = CallbackOpt; + + const NAME: &'static str = "callback"; + + fn act(_: &mut Ctx, opt: Self::Options) -> Result { + opt.tx.try_send(opt.idx)?; + succ!(); + } +} diff --git a/yazi-core/src/which/commands/mod.rs b/yazi-actor/src/which/mod.rs similarity index 100% rename from yazi-core/src/which/commands/mod.rs rename to yazi-actor/src/which/mod.rs diff --git a/yazi-actor/src/which/show.rs b/yazi-actor/src/which/show.rs new file mode 100644 index 00000000..cd39a0d4 --- /dev/null +++ b/yazi-actor/src/which/show.rs @@ -0,0 +1,28 @@ +use anyhow::Result; +use yazi_macro::{render, succ}; +use yazi_parser::which::ShowOpt; +use yazi_shared::event::Data; + +use crate::{Actor, Ctx}; + +pub struct Show; + +impl Actor for Show { + type Options = ShowOpt; + + const NAME: &'static str = "show"; + + fn act(cx: &mut Ctx, opt: Self::Options) -> Result { + if opt.cands.is_empty() { + succ!(); + } + + let which = &mut cx.which; + which.times = 0; + which.cands = opt.cands.into_iter().map(|c| c.into()).collect(); + + which.visible = true; + which.silent = opt.silent; + succ!(render!()); + } +} diff --git a/yazi-core/Cargo.toml b/yazi-core/Cargo.toml index 4e1d4a34..3aff4bbf 100644 --- a/yazi-core/Cargo.toml +++ b/yazi-core/Cargo.toml @@ -10,8 +10,6 @@ repository = "https://github.com/sxyazi/yazi" [dependencies] yazi-adapter = { path = "../yazi-adapter", version = "25.6.11" } -yazi-boot = { path = "../yazi-boot", version = "25.6.11" } -yazi-codegen = { path = "../yazi-codegen", version = "25.6.11" } yazi-config = { path = "../yazi-config", version = "25.6.11" } yazi-dds = { path = "../yazi-dds", version = "25.6.11" } yazi-fs = { path = "../yazi-fs", version = "25.6.11" } @@ -21,7 +19,6 @@ yazi-plugin = { path = "../yazi-plugin", version = "25.6.11" } yazi-proxy = { path = "../yazi-proxy", version = "25.6.11" } yazi-scheduler = { path = "../yazi-scheduler", version = "25.6.11" } yazi-shared = { path = "../yazi-shared", version = "25.6.11" } -yazi-term = { path = "../yazi-term", version = "25.6.11" } yazi-widgets = { path = "../yazi-widgets", version = "25.6.11" } # External dependencies @@ -32,8 +29,6 @@ indexmap = { workspace = true } notify = { version = "8.1.0", default-features = false, features = [ "macos_fsevent" ] } parking_lot = { workspace = true } ratatui = { workspace = true } -scopeguard = { workspace = true } -serde = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } tokio-util = { workspace = true } diff --git a/yazi-core/src/cmp/cmp.rs b/yazi-core/src/cmp/cmp.rs index 46b88b41..47b75a58 100644 --- a/yazi-core/src/cmp/cmp.rs +++ b/yazi-core/src/cmp/cmp.rs @@ -6,13 +6,13 @@ use yazi_widgets::Scrollable; #[derive(Default)] pub struct Cmp { - pub(super) caches: HashMap>, - pub(super) cands: Vec, - pub(super) offset: usize, - pub cursor: usize, + pub caches: HashMap>, + pub cands: Vec, + pub offset: usize, + pub cursor: usize, - pub(super) ticket: Id, - pub visible: bool, + pub ticket: Id, + pub visible: bool, } impl Cmp { @@ -30,3 +30,17 @@ impl Cmp { #[inline] pub fn rel_cursor(&self) -> usize { self.cursor - self.offset } } + +impl Scrollable for Cmp { + #[inline] + fn total(&self) -> usize { self.cands.len() } + + #[inline] + fn limit(&self) -> usize { self.cands.len().min(10) } + + #[inline] + fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor } + + #[inline] + fn offset_mut(&mut self) -> &mut usize { &mut self.offset } +} diff --git a/yazi-core/src/cmp/commands/arrow.rs b/yazi-core/src/cmp/commands/arrow.rs deleted file mode 100644 index eb81556c..00000000 --- a/yazi-core/src/cmp/commands/arrow.rs +++ /dev/null @@ -1,26 +0,0 @@ -use yazi_macro::render; -use yazi_parser::cmp::ArrowOpt; -use yazi_widgets::Scrollable; - -use crate::cmp::Cmp; - -impl Cmp { - #[yazi_codegen::command] - pub fn arrow(&mut self, opt: ArrowOpt) { - render!(self.scroll(opt.step)); - } -} - -impl Scrollable for Cmp { - #[inline] - fn total(&self) -> usize { self.cands.len() } - - #[inline] - fn limit(&self) -> usize { self.cands.len().min(10) } - - #[inline] - fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor } - - #[inline] - fn offset_mut(&mut self) -> &mut usize { &mut self.offset } -} diff --git a/yazi-core/src/cmp/commands/close.rs b/yazi-core/src/cmp/commands/close.rs deleted file mode 100644 index e6d06b32..00000000 --- a/yazi-core/src/cmp/commands/close.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::mem; - -use yazi_macro::render; -use yazi_parser::cmp::CloseOpt; -use yazi_proxy::InputProxy; - -use crate::cmp::Cmp; - -impl Cmp { - #[yazi_codegen::command] - pub fn close(&mut self, opt: CloseOpt) { - if let Some(s) = self.selected().filter(|_| opt.submit) { - InputProxy::complete(s, self.ticket); - } - - self.caches.clear(); - render!(mem::replace(&mut self.visible, false)); - } -} diff --git a/yazi-core/src/cmp/mod.rs b/yazi-core/src/cmp/mod.rs index 7a21d159..4fc18fd3 100644 --- a/yazi-core/src/cmp/mod.rs +++ b/yazi-core/src/cmp/mod.rs @@ -1,3 +1 @@ -yazi_macro::mod_pub!(commands); - yazi_macro::mod_flat!(cmp); diff --git a/yazi-core/src/confirm/commands/arrow.rs b/yazi-core/src/confirm/commands/arrow.rs deleted file mode 100644 index ef36a514..00000000 --- a/yazi-core/src/confirm/commands/arrow.rs +++ /dev/null @@ -1,17 +0,0 @@ -use yazi_macro::render; -use yazi_parser::confirm::ArrowOpt; - -use crate::{confirm::Confirm, mgr::Mgr}; - -impl Confirm { - #[yazi_codegen::command] - pub fn arrow(&mut self, opt: ArrowOpt, mgr: &Mgr) { - let area = mgr.area(self.position); - let len = self.list.line_count(area.width); - - let old = self.offset; - self.offset = opt.step.add(self.offset, len, area.height as _); - - render!(old != self.offset); - } -} diff --git a/yazi-core/src/confirm/commands/close.rs b/yazi-core/src/confirm/commands/close.rs deleted file mode 100644 index 42e1010f..00000000 --- a/yazi-core/src/confirm/commands/close.rs +++ /dev/null @@ -1,16 +0,0 @@ -use yazi_macro::render; -use yazi_parser::confirm::CloseOpt; - -use crate::confirm::Confirm; - -impl Confirm { - #[yazi_codegen::command] - pub fn close(&mut self, opt: CloseOpt) { - if let Some(cb) = self.callback.take() { - _ = cb.send(opt.submit); - } - - self.visible = false; - render!(); - } -} diff --git a/yazi-core/src/confirm/commands/show.rs b/yazi-core/src/confirm/commands/show.rs deleted file mode 100644 index 7f80a2a8..00000000 --- a/yazi-core/src/confirm/commands/show.rs +++ /dev/null @@ -1,24 +0,0 @@ -use yazi_macro::render; -use yazi_parser::confirm::ShowOpt; - -use crate::confirm::Confirm; - -impl Confirm { - pub fn show(&mut self, opt: impl TryInto) { - let Ok(opt): Result = opt.try_into() else { - return; - }; - - self.close(false); - self.title = opt.cfg.title; - self.body = opt.cfg.body; - self.list = opt.cfg.list; - - self.position = opt.cfg.position; - self.offset = 0; - - self.callback = Some(opt.tx); - self.visible = true; - render!(); - } -} diff --git a/yazi-core/src/confirm/confirm.rs b/yazi-core/src/confirm/confirm.rs index b6496c33..0fb62fe2 100644 --- a/yazi-core/src/confirm/confirm.rs +++ b/yazi-core/src/confirm/confirm.rs @@ -11,6 +11,6 @@ pub struct Confirm { pub position: Position, pub offset: usize, - pub(super) callback: Option>, - pub visible: bool, + pub callback: Option>, + pub visible: bool, } diff --git a/yazi-core/src/confirm/mod.rs b/yazi-core/src/confirm/mod.rs index 70f87db6..04908124 100644 --- a/yazi-core/src/confirm/mod.rs +++ b/yazi-core/src/confirm/mod.rs @@ -1,3 +1 @@ -yazi_macro::mod_pub!(commands); - yazi_macro::mod_flat!(confirm); diff --git a/yazi-core/src/app.rs b/yazi-core/src/core.rs similarity index 96% rename from yazi-core/src/app.rs rename to yazi-core/src/core.rs index 5a03df6e..e4531807 100644 --- a/yazi-core/src/app.rs +++ b/yazi-core/src/core.rs @@ -78,8 +78,8 @@ impl Core { pub fn active_mut(&mut self) -> &mut Tab { self.mgr.active_mut() } #[inline] - pub fn current(&self) -> &Folder { self.mgr.current() } + pub fn current_mut(&mut self) -> &mut Folder { self.mgr.current_mut() } #[inline] - pub fn current_mut(&mut self) -> &mut Folder { self.mgr.current_mut() } + pub fn parent_mut(&mut self) -> Option<&mut Folder> { self.mgr.parent_mut() } } diff --git a/yazi-core/src/help/commands/arrow.rs b/yazi-core/src/help/commands/arrow.rs deleted file mode 100644 index 84eaea31..00000000 --- a/yazi-core/src/help/commands/arrow.rs +++ /dev/null @@ -1,27 +0,0 @@ -use yazi_adapter::Dimension; -use yazi_macro::render; -use yazi_parser::help::ArrowOpt; -use yazi_widgets::Scrollable; - -use crate::help::{HELP_MARGIN, Help}; - -impl Help { - #[yazi_codegen::command] - pub fn arrow(&mut self, opt: ArrowOpt) { - render!(self.scroll(opt.step)); - } -} - -impl Scrollable for Help { - #[inline] - fn total(&self) -> usize { self.bindings.len() } - - #[inline] - fn limit(&self) -> usize { Dimension::available().rows.saturating_sub(HELP_MARGIN) as usize } - - #[inline] - fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor } - - #[inline] - fn offset_mut(&mut self) -> &mut usize { &mut self.offset } -} diff --git a/yazi-core/src/help/commands/escape.rs b/yazi-core/src/help/commands/escape.rs deleted file mode 100644 index 801d8f8b..00000000 --- a/yazi-core/src/help/commands/escape.rs +++ /dev/null @@ -1,17 +0,0 @@ -use yazi_macro::render; -use yazi_shared::event::CmdCow; - -use crate::help::Help; - -impl Help { - pub fn escape(&mut self, _: CmdCow) { - if self.keyword().is_none() { - return self.toggle(self.layer); - } - - self.keyword = String::new(); - self.in_filter = None; - self.filter_apply(); - render!(); - } -} diff --git a/yazi-core/src/help/commands/filter.rs b/yazi-core/src/help/commands/filter.rs deleted file mode 100644 index 0bfb54af..00000000 --- a/yazi-core/src/help/commands/filter.rs +++ /dev/null @@ -1,12 +0,0 @@ -use yazi_macro::render; -use yazi_shared::event::CmdCow; - -use crate::help::Help; - -impl Help { - pub fn filter(&mut self, _: CmdCow) { - self.in_filter = Some(Default::default()); - self.filter_apply(); - render!(); - } -} diff --git a/yazi-core/src/help/commands/mod.rs b/yazi-core/src/help/commands/mod.rs deleted file mode 100644 index 11f3a072..00000000 --- a/yazi-core/src/help/commands/mod.rs +++ /dev/null @@ -1 +0,0 @@ -yazi_macro::mod_flat!(arrow escape filter); diff --git a/yazi-core/src/help/help.rs b/yazi-core/src/help/help.rs index e5990cc8..3c3a6876 100644 --- a/yazi-core/src/help/help.rs +++ b/yazi-core/src/help/help.rs @@ -1,11 +1,14 @@ +use anyhow::Result; use crossterm::{cursor::SetCursorStyle, event::KeyCode}; use unicode_width::UnicodeWidthStr; use yazi_adapter::Dimension; use yazi_config::{KEYMAP, YAZI, keymap::{Chord, Key}}; -use yazi_macro::{render, render_and}; +use yazi_macro::{act, render, render_and}; use yazi_shared::Layer; use yazi_widgets::Scrollable; +use crate::help::HELP_MARGIN; + #[derive(Default)] pub struct Help { pub visible: bool, @@ -13,32 +16,16 @@ pub struct Help { pub(super) bindings: Vec<&'static Chord>, // Filter - pub(super) keyword: String, - pub(super) in_filter: Option, + pub keyword: String, + pub in_filter: Option, - pub(super) offset: usize, - pub(super) cursor: usize, + pub offset: usize, + pub cursor: usize, } impl Help { - pub fn toggle(&mut self, layer: Layer) { - self.visible = !self.visible; - self.layer = layer; - - self.keyword = String::new(); - self.in_filter = None; - self.filter_apply(); - - self.offset = 0; - self.cursor = 0; - render!(); - } - - pub fn r#type(&mut self, key: &Key) -> bool { - let Some(input) = &mut self.in_filter else { - return false; - }; - + pub fn r#type(&mut self, key: &Key) -> Result { + let Some(input) = &mut self.in_filter else { return Ok(false) }; match key { Key { code: KeyCode::Esc, shift: false, ctrl: false, alt: false, super_: false } => { self.in_filter = None; @@ -46,21 +33,21 @@ impl Help { } Key { code: KeyCode::Enter, shift: false, ctrl: false, alt: false, super_: false } => { self.in_filter = None; - return render_and!(true); // Don't do the `filter_apply` below, since we already have the filtered results. + return Ok(render_and!(true)); // Don't do the `filter_apply` below, since we already have the filtered results. } Key { code: KeyCode::Backspace, shift: false, ctrl: false, alt: false, super_: false } => { - input.backspace(false); + act!(backspace, input)?; } _ => { - input.r#type(key); + input.r#type(key)?; } } self.filter_apply(); - true + Ok(true) } - pub(super) fn filter_apply(&mut self) { + pub fn filter_apply(&mut self) { let kw = self.in_filter.as_ref().map_or("", |i| i.value()); if kw.is_empty() { @@ -71,7 +58,7 @@ impl Help { self.bindings = KEYMAP.get(self.layer).iter().filter(|&c| c.contains(kw)).collect(); } - self.arrow(0); + render!(self.scroll(0)); } } @@ -118,3 +105,17 @@ impl Help { } } } + +impl Scrollable for Help { + #[inline] + fn total(&self) -> usize { self.bindings.len() } + + #[inline] + fn limit(&self) -> usize { Dimension::available().rows.saturating_sub(HELP_MARGIN) as usize } + + #[inline] + fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor } + + #[inline] + fn offset_mut(&mut self) -> &mut usize { &mut self.offset } +} diff --git a/yazi-core/src/help/mod.rs b/yazi-core/src/help/mod.rs index 342463d3..386cd482 100644 --- a/yazi-core/src/help/mod.rs +++ b/yazi-core/src/help/mod.rs @@ -1,5 +1,3 @@ -yazi_macro::mod_pub!(commands); - yazi_macro::mod_flat!(help); -pub const HELP_MARGIN: u16 = 1; +const HELP_MARGIN: u16 = 1; diff --git a/yazi-core/src/input/commands/close.rs b/yazi-core/src/input/commands/close.rs deleted file mode 100644 index 9be627b0..00000000 --- a/yazi-core/src/input/commands/close.rs +++ /dev/null @@ -1,22 +0,0 @@ -use yazi_macro::render; -use yazi_parser::input::CloseOpt; -use yazi_proxy::CmpProxy; -use yazi_shared::errors::InputError; - -use crate::input::Input; - -impl Input { - #[yazi_codegen::command] - pub fn close(&mut self, opt: CloseOpt) { - self.visible = false; - self.ticket.next(); - - if let Some(tx) = self.tx.take() { - let value = self.snap().value.clone(); - _ = tx.send(if opt.submit { Ok(value) } else { Err(InputError::Canceled(value)) }); - } - - CmpProxy::close(); - render!(); - } -} diff --git a/yazi-core/src/input/commands/escape.rs b/yazi-core/src/input/commands/escape.rs deleted file mode 100644 index 6ba4a548..00000000 --- a/yazi-core/src/input/commands/escape.rs +++ /dev/null @@ -1,23 +0,0 @@ -use yazi_macro::render; -use yazi_parser::input::EscapeOpt; -use yazi_proxy::CmpProxy; -use yazi_widgets::input::InputOp; - -use crate::input::Input; - -impl Input { - #[yazi_codegen::command] - pub fn escape(&mut self, _: EscapeOpt) { - use yazi_widgets::input::InputMode as M; - - let mode = self.snap().mode; - match mode { - M::Normal if self.snap_mut().op == InputOp::None => self.close(false), - M::Insert => CmpProxy::close(), - M::Normal | M::Replace => {} - } - - self.inner.escape(()); - render!(); - } -} diff --git a/yazi-core/src/input/commands/show.rs b/yazi-core/src/input/commands/show.rs deleted file mode 100644 index d925ef36..00000000 --- a/yazi-core/src/input/commands/show.rs +++ /dev/null @@ -1,46 +0,0 @@ -use yazi_config::YAZI; -use yazi_macro::render; -use yazi_parser::input::ShowOpt; -use yazi_shared::errors::InputError; -use yazi_widgets::input::InputCallback; - -use crate::input::Input; - -impl Input { - pub fn show(&mut self, opt: impl TryInto) { - let Ok(opt): Result = opt.try_into() else { return }; - - self.close(false); - self.visible = true; - self.title = opt.cfg.title; - self.position = opt.cfg.position; - - // Typing - self.tx = Some(opt.tx.clone()); - let ticket = self.ticket.clone(); - - // Reset input - let cb: InputCallback = Box::new(move |before, after| { - if opt.cfg.realtime { - opt.tx.send(Err(InputError::Typed(format!("{before}{after}")))).ok(); - } else if opt.cfg.completion { - opt.tx.send(Err(InputError::Completed(before.to_owned(), ticket.current()))).ok(); - } - }); - self.inner = yazi_widgets::input::Input::new( - opt.cfg.value, - opt.cfg.position.offset.width.saturating_sub(YAZI.input.border()) as usize, - opt.cfg.obscure, - cb, - ); - - // Set cursor after reset - // TODO: remove this - if let Some(cursor) = opt.cfg.cursor { - self.snap_mut().cursor = cursor; - self.r#move(0); - } - - render!(); - } -} diff --git a/yazi-core/src/input/input.rs b/yazi-core/src/input/input.rs index b68b0e0d..b592a6e2 100644 --- a/yazi-core/src/input/input.rs +++ b/yazi-core/src/input/input.rs @@ -13,8 +13,8 @@ pub struct Input { pub position: Position, // Typing - pub(super) tx: Option>>, - pub(super) ticket: Rc, + pub tx: Option>>, + pub ticket: Rc, } impl Deref for Input { diff --git a/yazi-core/src/input/mod.rs b/yazi-core/src/input/mod.rs index e77b2fe8..9ad1aafa 100644 --- a/yazi-core/src/input/mod.rs +++ b/yazi-core/src/input/mod.rs @@ -1,3 +1 @@ -yazi_macro::mod_pub!(commands); - yazi_macro::mod_flat!(input); diff --git a/yazi-core/src/lib.rs b/yazi-core/src/lib.rs index 033592b1..6badf67e 100644 --- a/yazi-core/src/lib.rs +++ b/yazi-core/src/lib.rs @@ -8,7 +8,7 @@ yazi_macro::mod_pub!(cmp confirm help input mgr notify pick spot tab tasks which); -yazi_macro::mod_flat!(app); +yazi_macro::mod_flat!(core); pub fn init() { mgr::WATCHED.with(<_>::default); diff --git a/yazi-core/src/mgr/commands/close.rs b/yazi-core/src/mgr/commands/close.rs deleted file mode 100644 index 45802450..00000000 --- a/yazi-core/src/mgr/commands/close.rs +++ /dev/null @@ -1,13 +0,0 @@ -use yazi_parser::mgr::CloseOpt; - -use crate::{mgr::Mgr, tasks::Tasks}; - -impl Mgr { - #[yazi_codegen::command] - pub fn close(&mut self, opt: CloseOpt, tasks: &Tasks) { - if self.tabs.len() > 1 { - return self.tabs.close(self.tabs.cursor); - } - self.quit(opt.0, tasks); - } -} diff --git a/yazi-core/src/mgr/commands/hardlink.rs b/yazi-core/src/mgr/commands/hardlink.rs deleted file mode 100644 index 16a9c302..00000000 --- a/yazi-core/src/mgr/commands/hardlink.rs +++ /dev/null @@ -1,14 +0,0 @@ -use yazi_parser::mgr::HardlinkOpt; - -use crate::{mgr::Mgr, tasks::Tasks}; - -impl Mgr { - #[yazi_codegen::command] - pub fn hardlink(&mut self, opt: HardlinkOpt, tasks: &Tasks) { - if self.yanked.cut { - return; - } - - tasks.file_hardlink(&self.yanked, self.cwd(), opt.force, opt.follow); - } -} diff --git a/yazi-core/src/mgr/commands/link.rs b/yazi-core/src/mgr/commands/link.rs deleted file mode 100644 index f4339d9c..00000000 --- a/yazi-core/src/mgr/commands/link.rs +++ /dev/null @@ -1,14 +0,0 @@ -use yazi_parser::mgr::LinkOpt; - -use crate::{mgr::Mgr, tasks::Tasks}; - -impl Mgr { - #[yazi_codegen::command] - pub fn link(&mut self, opt: LinkOpt, tasks: &Tasks) { - if self.yanked.cut { - return; - } - - tasks.file_link(&self.yanked, self.cwd(), opt.relative, opt.force); - } -} diff --git a/yazi-core/src/mgr/commands/paste.rs b/yazi-core/src/mgr/commands/paste.rs deleted file mode 100644 index 6a94101f..00000000 --- a/yazi-core/src/mgr/commands/paste.rs +++ /dev/null @@ -1,19 +0,0 @@ -use yazi_parser::mgr::PasteOpt; - -use crate::{mgr::Mgr, tasks::Tasks}; - -impl Mgr { - #[yazi_codegen::command] - pub fn paste(&mut self, opt: PasteOpt, tasks: &Tasks) { - let (src, dest) = (self.yanked.iter().collect::>(), self.cwd()); - - if self.yanked.cut { - tasks.file_cut(&src, dest, opt.force); - - self.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(&src)); - self.unyank(()); - } else { - tasks.file_copy(&src, dest, opt.force, opt.follow); - } - } -} diff --git a/yazi-core/src/mgr/commands/peek.rs b/yazi-core/src/mgr/commands/peek.rs deleted file mode 100644 index 3c33d447..00000000 --- a/yazi-core/src/mgr/commands/peek.rs +++ /dev/null @@ -1,45 +0,0 @@ -use yazi_parser::mgr::PeekOpt; -use yazi_proxy::HIDER; - -use crate::mgr::Mgr; - -impl Mgr { - #[yazi_codegen::command] - pub fn peek(&mut self, opt: PeekOpt) { - let Some(hovered) = self.hovered().cloned() else { - return self.active_mut().preview.reset(); - }; - if HIDER.try_acquire().is_err() { - return self.active_mut().preview.reset_image(); - } - - let mime = self.mimetype.by_file_owned(&hovered).unwrap_or_default(); - let folder = self.active().hovered_folder().map(|f| (f.offset, f.cha)); - - if !self.active().preview.same_url(&hovered.url) { - self.active_mut().preview.skip = folder.map(|f| f.0).unwrap_or_default(); - } - if !self.active().preview.same_file(&hovered, &mime) { - self.active_mut().preview.reset(); - } - - if matches!(opt.only_if, Some(u) if u != hovered.url) { - return; - } - - if let Some(skip) = opt.skip { - let preview = &mut self.active_mut().preview; - if opt.upper_bound { - preview.skip = preview.skip.min(skip); - } else { - preview.skip = skip; - } - } - - if hovered.is_dir() { - self.active_mut().preview.go_folder(hovered, folder.map(|(_, cha)| cha), opt.force); - } else { - self.active_mut().preview.go(hovered, mime, opt.force); - } - } -} diff --git a/yazi-core/src/mgr/commands/refresh.rs b/yazi-core/src/mgr/commands/refresh.rs deleted file mode 100644 index 8b32983d..00000000 --- a/yazi-core/src/mgr/commands/refresh.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crossterm::{execute, terminal::SetTitle}; -use yazi_config::YAZI; -use yazi_fs::CWD; -use yazi_shared::event::CmdCow; -use yazi_term::tty::TTY; - -use crate::{mgr::Mgr, tasks::Tasks}; - -impl Mgr { - pub fn refresh(&mut self, _: CmdCow, tasks: &Tasks) { - if let (_, Some(s)) = (CWD.set(self.cwd()), YAZI.mgr.title()) { - execute!(TTY.writer(), SetTitle(s)).ok(); - } - - self.active_mut().apply_files_attrs(); - - if let Some(p) = self.parent() { - self.watcher.trigger_dirs(&[self.current(), p]); - } else { - self.watcher.trigger_dirs(&[self.current()]); - } - - self.peek(false); - self.watch(()); - self.update_paged((), tasks); - - tasks.prework_sorted(&self.current().files); - } -} diff --git a/yazi-core/src/mgr/commands/remove.rs b/yazi-core/src/mgr/commands/remove.rs deleted file mode 100644 index 922ad344..00000000 --- a/yazi-core/src/mgr/commands/remove.rs +++ /dev/null @@ -1,52 +0,0 @@ -use yazi_config::popup::ConfirmCfg; -use yazi_parser::mgr::RemoveOpt; -use yazi_proxy::{ConfirmProxy, MgrProxy}; - -use crate::{mgr::Mgr, tasks::Tasks}; - -impl Mgr { - #[yazi_codegen::command] - pub fn remove(&mut self, mut opt: RemoveOpt, tasks: &Tasks) { - if !self.active_mut().try_escape_visual() { - return; - } - - opt.targets = if opt.hovered { - self.hovered().map_or(vec![], |h| vec![h.url.clone()]) - } else { - self.selected_or_hovered().cloned().collect() - }; - - if opt.targets.is_empty() { - return; - } else if opt.force { - return self.remove_do(opt, tasks); - } - - let confirm = ConfirmProxy::show(if opt.permanently { - ConfirmCfg::delete(&opt.targets) - } else { - ConfirmCfg::trash(&opt.targets) - }); - - tokio::spawn(async move { - if confirm.await { - MgrProxy::remove_do(opt.targets, opt.permanently); - } - }); - } - - #[yazi_codegen::command] - pub fn remove_do(&mut self, opt: RemoveOpt, tasks: &Tasks) { - self.tabs.iter_mut().for_each(|t| { - t.selected.remove_many(&opt.targets); - }); - - for u in &opt.targets { - self.yanked.remove(u); - } - - self.yanked.catchup_revision(false); - tasks.file_remove(opt.targets, opt.permanently); - } -} diff --git a/yazi-core/src/mgr/commands/seek.rs b/yazi-core/src/mgr/commands/seek.rs deleted file mode 100644 index fc637996..00000000 --- a/yazi-core/src/mgr/commands/seek.rs +++ /dev/null @@ -1,24 +0,0 @@ -use yazi_config::YAZI; -use yazi_parser::mgr::SeekOpt; -use yazi_plugin::isolate; - -use crate::mgr::Mgr; - -impl Mgr { - #[yazi_codegen::command] - pub fn seek(&mut self, opt: SeekOpt) { - let Some(hovered) = self.hovered() else { - return self.active_mut().preview.reset(); - }; - - let Some(mime) = self.mimetype.by_file(hovered) else { - return self.active_mut().preview.reset(); - }; - - let Some(previewer) = YAZI.plugin.previewer(&hovered.url, mime) else { - return self.active_mut().preview.reset(); - }; - - isolate::seek_sync(&previewer.run, hovered.clone(), opt.units); - } -} diff --git a/yazi-core/src/mgr/commands/spot.rs b/yazi-core/src/mgr/commands/spot.rs deleted file mode 100644 index 67cca11a..00000000 --- a/yazi-core/src/mgr/commands/spot.rs +++ /dev/null @@ -1,25 +0,0 @@ -use yazi_parser::mgr::SpotOpt; - -use crate::mgr::Mgr; - -impl Mgr { - #[yazi_codegen::command] - pub fn spot(&mut self, opt: SpotOpt) { - let Some(hovered) = self.hovered().cloned() else { - return; - }; - - let mime = self.mimetype.by_file_owned(&hovered).unwrap_or_default(); - // if !self.active().spot.same_file(&hovered, &mime) { - // self.active_mut().spot.reset(); - // } - - if let Some(skip) = opt.skip { - self.active_mut().spot.skip = skip; - } else if !self.active().spot.same_url(&hovered.url) { - self.active_mut().spot.skip = 0; - } - - self.active_mut().spot.go(hovered, mime); - } -} diff --git a/yazi-core/src/mgr/commands/suspend.rs b/yazi-core/src/mgr/commands/suspend.rs deleted file mode 100644 index c623686b..00000000 --- a/yazi-core/src/mgr/commands/suspend.rs +++ /dev/null @@ -1,12 +0,0 @@ -use yazi_shared::event::CmdCow; - -use crate::mgr::Mgr; - -impl Mgr { - pub fn suspend(&mut self, _: CmdCow) { - #[cfg(unix)] - unsafe { - libc::raise(libc::SIGTSTP); - } - } -} diff --git a/yazi-core/src/mgr/commands/tab_close.rs b/yazi-core/src/mgr/commands/tab_close.rs deleted file mode 100644 index c9d644e1..00000000 --- a/yazi-core/src/mgr/commands/tab_close.rs +++ /dev/null @@ -1,23 +0,0 @@ -use yazi_macro::render; -use yazi_parser::mgr::TabCloseOpt; - -use crate::mgr::Tabs; - -impl Tabs { - #[yazi_codegen::command] - pub fn close(&mut self, opt: TabCloseOpt) { - let len = self.items.len(); - if len < 2 || opt.idx >= len { - return; - } - - self.items.remove(opt.idx).shutdown(); - if opt.idx > self.cursor { - self.set_idx(self.cursor); - } else { - self.set_idx(usize::min(self.cursor + 1, self.items.len() - 1)); - } - - render!(); - } -} diff --git a/yazi-core/src/mgr/commands/tab_create.rs b/yazi-core/src/mgr/commands/tab_create.rs deleted file mode 100644 index 432388d9..00000000 --- a/yazi-core/src/mgr/commands/tab_create.rs +++ /dev/null @@ -1,34 +0,0 @@ -use yazi_macro::render; -use yazi_parser::{mgr::TabCreateOpt, tab::CdSource}; -use yazi_proxy::AppProxy; - -use crate::{mgr::Tabs, tab::Tab}; - -const MAX_TABS: usize = 9; - -impl Tabs { - #[yazi_codegen::command] - pub fn create(&mut self, opt: TabCreateOpt) { - if self.items.len() >= MAX_TABS { - AppProxy::notify_warn("Too many tabs", "You can only open up to 9 tabs at the same time."); - return; - } - - let mut tab = Tab::default(); - if let Some(wd) = opt.wd { - tab.cd((wd, CdSource::Tab)); - } else if let Some(h) = self.active().hovered() { - tab.pref = self.active().pref.clone(); - tab.apply_files_attrs(); - tab.reveal((h.url.to_regular(), CdSource::Tab)); - } else { - tab.pref = self.active().pref.clone(); - tab.apply_files_attrs(); - tab.cd((self.active().cwd().to_regular(), CdSource::Tab)); - } - - self.items.insert(self.cursor + 1, tab); - self.set_idx(self.cursor + 1); - render!(); - } -} diff --git a/yazi-core/src/mgr/commands/tab_swap.rs b/yazi-core/src/mgr/commands/tab_swap.rs deleted file mode 100644 index 003fa34a..00000000 --- a/yazi-core/src/mgr/commands/tab_swap.rs +++ /dev/null @@ -1,18 +0,0 @@ -use yazi_macro::render; -use yazi_parser::mgr::TabSwapOpt; - -use crate::mgr::Tabs; - -impl Tabs { - #[yazi_codegen::command] - pub fn swap(&mut self, opt: TabSwapOpt) { - let idx = opt.step.saturating_add_unsigned(self.cursor).rem_euclid(self.items.len() as _) as _; - if idx == self.cursor { - return; - } - - self.items.swap(self.cursor, idx); - self.set_idx(idx); - render!(); - } -} diff --git a/yazi-core/src/mgr/commands/tab_switch.rs b/yazi-core/src/mgr/commands/tab_switch.rs deleted file mode 100644 index 1508f454..00000000 --- a/yazi-core/src/mgr/commands/tab_switch.rs +++ /dev/null @@ -1,22 +0,0 @@ -use yazi_macro::render; -use yazi_parser::mgr::TabSwitchOpt; - -use crate::mgr::Tabs; - -impl Tabs { - #[yazi_codegen::command] - pub fn switch(&mut self, opt: TabSwitchOpt) { - let idx = if opt.relative { - opt.step.saturating_add_unsigned(self.cursor).rem_euclid(self.items.len() as _) as _ - } else { - opt.step as usize - }; - - if idx == self.cursor || idx >= self.items.len() { - return; - } - - self.set_idx(idx); - render!(); - } -} diff --git a/yazi-core/src/mgr/commands/unyank.rs b/yazi-core/src/mgr/commands/unyank.rs deleted file mode 100644 index e63efdd5..00000000 --- a/yazi-core/src/mgr/commands/unyank.rs +++ /dev/null @@ -1,17 +0,0 @@ -use yazi_macro::render; -use yazi_parser::mgr::UnyankOpt; - -use crate::mgr::Mgr; - -impl Mgr { - #[yazi_codegen::command] - pub fn unyank(&mut self, _: UnyankOpt) { - let repeek = self.hovered().is_some_and(|f| f.is_dir() && self.yanked.contains_in(&f.url)); - self.yanked.clear(); - - render!(self.yanked.catchup_revision(false)); - if repeek { - self.peek(true); - } - } -} diff --git a/yazi-core/src/mgr/commands/update_files.rs b/yazi-core/src/mgr/commands/update_files.rs deleted file mode 100644 index 3a3dec1c..00000000 --- a/yazi-core/src/mgr/commands/update_files.rs +++ /dev/null @@ -1,100 +0,0 @@ -use yazi_fs::FilesOp; -use yazi_macro::render; -use yazi_parser::mgr::UpdateFilesOpt; - -use crate::{mgr::{LINKED, Mgr}, tab::Folder, tasks::Tasks}; - -impl Mgr { - pub fn update_files(&mut self, opt: impl TryInto, tasks: &Tasks) { - let Ok(opt) = opt.try_into() else { - return; - }; - - let revision = self.current().files.revision; - let linked: Vec<_> = LINKED.read().from_dir(opt.op.cwd()).map(|u| opt.op.rebase(u)).collect(); - for op in [opt.op].into_iter().chain(linked) { - self.yanked.apply_op(&op); - self.update_tab(op, tasks); - } - - render!(self.yanked.catchup_revision(false)); - self.active_mut().apply_files_attrs(); - - if revision != self.current().files.revision { - self.active_mut().hover(None); - self.peek(false); - self.watch(()); - self.update_paged((), tasks); - } - } - - fn update_tab(&mut self, op: FilesOp, tasks: &Tasks) { - let url = op.cwd(); - self.active_mut().selected.apply_op(&op); - - if url == self.cwd() { - self.update_current(op, tasks); - } else if matches!(self.parent(), Some(p) if *url == p.url) { - self.update_parent(op); - } else if matches!(self.hovered(), Some(h) if *url == h.url) { - self.update_hovered(op); - } else { - self.update_history(op); - } - } - - fn update_parent(&mut self, op: FilesOp) { - let tab = self.active_mut(); - - let urn = tab.current.url.urn(); - let leave = matches!(op, FilesOp::Deleting(_, ref urns) if urns.contains(urn)); - - if let Some(f) = tab.parent.as_mut() { - render!(f.update_pub(tab.id, op)); - render!(f.hover(urn)); - } - - if leave { - tab.leave(()); - } - } - - fn update_current(&mut self, op: FilesOp, tasks: &Tasks) { - let calc = !matches!(op, FilesOp::Size(..) | FilesOp::Deleting(..)); - - let id = self.active().id; - if !self.current_mut().update_pub(id, op) { - return; - } - - if calc { - tasks.prework_sorted(&self.current().files); - } - } - - fn update_hovered(&mut self, op: FilesOp) { - let (id, url) = (self.active().id, op.cwd()); - let folder = self.active_mut().history.entry(url.clone()).or_insert_with(|| Folder::from(url)); - - if folder.update_pub(id, op) { - self.peek(true); - } - } - - fn update_history(&mut self, op: FilesOp) { - let tab = &mut self.active_mut(); - let leave = tab.parent.as_ref().and_then(|f| f.url.parent_url().map(|p| (p, f.url.urn()))).is_some_and( - |(p, n)| matches!(op, FilesOp::Deleting(ref parent, ref urns) if *parent == p && urns.contains(n)), - ); - - tab - .history - .entry(op.cwd().clone()) - .or_insert_with(|| Folder::from(op.cwd())) - .update_pub(tab.id, op); - - if leave { - tab.leave(()); - } - } -} diff --git a/yazi-core/src/mgr/commands/update_mimes.rs b/yazi-core/src/mgr/commands/update_mimes.rs deleted file mode 100644 index 59e09524..00000000 --- a/yazi-core/src/mgr/commands/update_mimes.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::collections::HashMap; - -use tracing::error; -use yazi_macro::render; -use yazi_parser::mgr::UpdateMimesOpt; - -use crate::{mgr::{LINKED, Mgr}, tasks::Tasks}; - -impl Mgr { - pub fn update_mimes(&mut self, opt: impl TryInto, tasks: &Tasks) { - let Ok(opt): Result = opt.try_into() else { - return error!("invalid arguments for update_mimes"); - }; - - let linked = LINKED.read(); - let updates = opt - .updates - .into_iter() - .flat_map(|(key, value)| key.into_url().zip(value.into_string())) - .filter(|(url, mime)| self.mimetype.by_url(url) != Some(mime)) - .fold(HashMap::new(), |mut map, (u, m)| { - for u in linked.from_file(&u) { - map.insert(u, m.to_string()); - } - map.insert(u, m.into_owned()); - map - }); - - drop(linked); - if updates.is_empty() { - return; - } - - let affected: Vec<_> = self - .current() - .paginate(self.current().page) - .iter() - .filter(|&f| updates.contains_key(&f.url)) - .cloned() - .collect(); - - let repeek = self.hovered().is_some_and(|f| updates.contains_key(&f.url)); - self.mimetype.extend(updates); - - if repeek { - self.peek(false); - } - tasks.fetch_paged(&affected, &self.mimetype); - tasks.preload_paged(&affected, &self.mimetype); - - render!(); - } -} diff --git a/yazi-core/src/mgr/commands/update_paged.rs b/yazi-core/src/mgr/commands/update_paged.rs deleted file mode 100644 index 4cf25b32..00000000 --- a/yazi-core/src/mgr/commands/update_paged.rs +++ /dev/null @@ -1,21 +0,0 @@ -use yazi_parser::mgr::UpdatePagedOpt; - -use crate::{mgr::Mgr, tasks::Tasks}; - -impl Mgr { - pub fn update_paged(&mut self, opt: impl TryInto, tasks: &Tasks) { - let Ok(opt): Result = opt.try_into() else { - return; - }; - - if opt.only_if.is_some_and(|u| u != *self.cwd()) { - return; - } - - let targets = self.current().paginate(opt.page.unwrap_or(self.current().page)); - if !targets.is_empty() { - tasks.fetch_paged(targets, &self.mimetype); - tasks.preload_paged(targets, &self.mimetype); - } - } -} diff --git a/yazi-core/src/mgr/commands/update_tasks.rs b/yazi-core/src/mgr/commands/update_tasks.rs deleted file mode 100644 index e70e6962..00000000 --- a/yazi-core/src/mgr/commands/update_tasks.rs +++ /dev/null @@ -1,13 +0,0 @@ -use yazi_parser::mgr::UpdateTasksOpt; - -use crate::mgr::Mgr; - -impl Mgr { - pub fn update_tasks(&mut self, opt: impl TryInto) { - let Ok(opt) = opt.try_into() else { - return; - }; - - self.watcher.push_files(opt.urls); - } -} diff --git a/yazi-core/src/mgr/commands/update_yanked.rs b/yazi-core/src/mgr/commands/update_yanked.rs deleted file mode 100644 index 4cd8f5c2..00000000 --- a/yazi-core/src/mgr/commands/update_yanked.rs +++ /dev/null @@ -1,17 +0,0 @@ -use yazi_macro::render; -use yazi_parser::mgr::UpdateYankedOpt; - -use crate::mgr::{Mgr, Yanked}; - -impl Mgr { - pub fn update_yanked(&mut self, opt: impl TryInto) { - let Ok(opt) = opt.try_into() else { return }; - - if opt.urls.is_empty() && self.yanked.is_empty() { - return; - } - - self.yanked = Yanked::new(opt.cut, opt.urls); - render!(); - } -} diff --git a/yazi-core/src/mgr/commands/watch.rs b/yazi-core/src/mgr/commands/watch.rs deleted file mode 100644 index 154318e4..00000000 --- a/yazi-core/src/mgr/commands/watch.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::iter; - -use yazi_parser::mgr::WatchOpt; - -use crate::mgr::Mgr; - -impl Mgr { - #[yazi_codegen::command] - pub fn watch(&mut self, _: WatchOpt) { - let it = iter::once(self.tabs.active().cwd()) - .chain(self.tabs.parent().map(|p| &p.url)) - .chain(self.tabs.hovered().filter(|h| h.is_dir()).map(|h| &h.url)); - - self.watcher.watch(it); - } -} diff --git a/yazi-core/src/mgr/commands/yank.rs b/yazi-core/src/mgr/commands/yank.rs deleted file mode 100644 index 24d779b3..00000000 --- a/yazi-core/src/mgr/commands/yank.rs +++ /dev/null @@ -1,18 +0,0 @@ -use yazi_macro::render; -use yazi_parser::mgr::YankOpt; - -use crate::mgr::{Mgr, Yanked}; - -impl Mgr { - #[yazi_codegen::command] - pub fn yank(&mut self, opt: YankOpt) { - if !self.active_mut().try_escape_visual() { - return; - } - - self.yanked = Yanked::new(opt.cut, self.selected_or_hovered().cloned().collect()); - render!(self.yanked.catchup_revision(true)); - - self.active_mut().escape_select(); - } -} diff --git a/yazi-core/src/mgr/mgr.rs b/yazi-core/src/mgr/mgr.rs index 6a7a06cd..4fd97e06 100644 --- a/yazi-core/src/mgr/mgr.rs +++ b/yazi-core/src/mgr/mgr.rs @@ -1,7 +1,6 @@ use ratatui::layout::Rect; use yazi_adapter::Dimension; use yazi_config::popup::{Origin, Position}; -use yazi_fs::File; use yazi_shared::url::Url; use super::{Mimetype, Tabs, Watcher, Yanked}; @@ -11,14 +10,14 @@ pub struct Mgr { pub tabs: Tabs, pub yanked: Yanked, - pub(super) watcher: Watcher, - pub mimetype: Mimetype, + pub watcher: Watcher, + pub mimetype: Mimetype, } impl Mgr { pub fn make() -> Self { Self { - tabs: Tabs::make(), + tabs: Default::default(), yanked: Default::default(), watcher: Watcher::serve(), @@ -47,31 +46,9 @@ impl Mgr { #[inline] pub fn active_mut(&mut self) -> &mut Tab { self.tabs.active_mut() } - #[inline] - pub fn current(&self) -> &Folder { &self.active().current } - #[inline] pub fn current_mut(&mut self) -> &mut Folder { &mut self.active_mut().current } - #[inline] - pub fn parent(&self) -> Option<&Folder> { self.active().parent.as_ref() } - #[inline] pub fn parent_mut(&mut self) -> Option<&mut Folder> { self.active_mut().parent.as_mut() } - - #[inline] - pub fn hovered(&self) -> Option<&File> { self.active().hovered() } - - #[inline] - pub fn hovered_folder(&self) -> Option<&Folder> { self.active().hovered_folder() } - - #[inline] - pub fn selected_or_hovered(&self) -> Box + '_> { - self.tabs.active().selected_or_hovered() - } - - #[inline] - pub fn hovered_and_selected(&self) -> Box + '_> { - self.tabs.active().hovered_and_selected() - } } diff --git a/yazi-core/src/mgr/mod.rs b/yazi-core/src/mgr/mod.rs index 87fbda62..26bc8a47 100644 --- a/yazi-core/src/mgr/mod.rs +++ b/yazi-core/src/mgr/mod.rs @@ -1,3 +1 @@ -yazi_macro::mod_pub!(commands); - yazi_macro::mod_flat!(linked mgr mimetype tabs watcher yanked); diff --git a/yazi-core/src/mgr/tabs.rs b/yazi-core/src/mgr/tabs.rs index f26294b9..df83fcc8 100644 --- a/yazi-core/src/mgr/tabs.rs +++ b/yazi-core/src/mgr/tabs.rs @@ -1,52 +1,35 @@ use std::ops::{Deref, DerefMut}; -use yazi_boot::BOOT; use yazi_dds::Pubsub; use yazi_fs::File; use yazi_macro::err; -use yazi_parser::tab::CdSource; -use yazi_proxy::MgrProxy; -use yazi_shared::{Id, url::Url}; use crate::tab::{Folder, Tab}; pub struct Tabs { - pub cursor: usize, - pub(super) items: Vec, + pub cursor: usize, + pub items: Vec, +} + +impl Default for Tabs { + fn default() -> Self { Self { cursor: 0, items: vec![Default::default()] } } } impl Tabs { - pub fn make() -> Self { - let mut tabs = - Self { cursor: 0, items: (0..BOOT.cwds.len()).map(|_| Tab::default()).collect() }; - - for (i, tab) in tabs.iter_mut().enumerate() { - let file = &BOOT.files[i]; - if file.is_empty() { - tab.cd((Url::from(&BOOT.cwds[i]), CdSource::Tab)); - } else { - tab.reveal((Url::from(BOOT.cwds[i].join(file)), CdSource::Tab)); - } - } - tabs - } - - pub(super) fn set_idx(&mut self, idx: usize) { + pub fn set_idx(&mut self, idx: usize) { // Reset the preview of the last active tab if let Some(active) = self.items.get_mut(self.cursor) { active.preview.reset_image(); } self.cursor = idx; - MgrProxy::refresh(); - MgrProxy::peek(true); err!(Pubsub::pub_after_tab(self.active().id)); } } impl Tabs { #[inline] - pub fn active(&self) -> &Tab { &self.items[self.cursor] } + pub fn active(&self) -> &Tab { &self[self.cursor] } #[inline] pub(super) fn active_mut(&mut self) -> &mut Tab { &mut self.items[self.cursor] } @@ -59,9 +42,6 @@ impl Tabs { #[inline] pub fn hovered(&self) -> Option<&File> { self.current().hovered() } - - #[inline] - pub fn find_mut(&mut self, id: Id) -> Option<&mut Tab> { self.iter_mut().find(|t| t.id == id) } } impl Deref for Tabs { diff --git a/yazi-core/src/mgr/watcher.rs b/yazi-core/src/mgr/watcher.rs index 69d405e5..a151647f 100644 --- a/yazi-core/src/mgr/watcher.rs +++ b/yazi-core/src/mgr/watcher.rs @@ -51,11 +51,11 @@ impl Watcher { Self { in_tx, out_tx } } - pub(super) fn watch<'a>(&mut self, it: impl Iterator) { + pub fn watch<'a>(&mut self, it: impl Iterator) { self.in_tx.send(it.filter(|u| u.is_regular()).cloned().collect()).ok(); } - pub(super) fn push_files(&self, urls: Vec) { + pub fn push_files(&self, urls: Vec) { Self::push_files_impl(&self.out_tx, urls.into_iter()); } @@ -75,7 +75,7 @@ impl Watcher { } // TODO: performance improvement - pub(super) fn trigger_dirs(&self, folders: &[&Folder]) { + pub fn trigger_dirs(&self, folders: &[&Folder]) { let todo: Vec<_> = folders.iter().filter(|&f| f.url.is_regular()).map(|&f| (f.url.to_owned(), f.cha)).collect(); if todo.is_empty() { diff --git a/yazi-core/src/notify/commands/push.rs b/yazi-core/src/notify/commands/push.rs index 052f1934..ca409e0f 100644 --- a/yazi-core/src/notify/commands/push.rs +++ b/yazi-core/src/notify/commands/push.rs @@ -1,14 +1,14 @@ use std::time::{Duration, Instant}; -use yazi_proxy::AppProxy; +use yazi_proxy::{AppProxy, options::NotifyOpt}; use crate::notify::{Message, Notify}; impl Notify { - pub fn push(&mut self, msg: impl Into) { - let mut msg = msg.into() as Message; - + 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) { diff --git a/yazi-core/src/notify/commands/tick.rs b/yazi-core/src/notify/commands/tick.rs index b289d94e..0a513105 100644 --- a/yazi-core/src/notify/commands/tick.rs +++ b/yazi-core/src/notify/commands/tick.rs @@ -7,11 +7,8 @@ use yazi_proxy::AppProxy; use crate::notify::Notify; impl Notify { - pub fn tick(&mut self, opt: impl TryInto, area: Rect) { + pub fn tick(&mut self, opt: TickOpt, area: Rect) { self.tick_handle.take().map(|h| h.abort()); - let Ok(opt) = opt.try_into() else { - return; - }; let limit = self.limit(area); if limit == 0 { diff --git a/yazi-core/src/pick/commands/arrow.rs b/yazi-core/src/pick/commands/arrow.rs deleted file mode 100644 index 979d4c16..00000000 --- a/yazi-core/src/pick/commands/arrow.rs +++ /dev/null @@ -1,29 +0,0 @@ -use yazi_config::YAZI; -use yazi_macro::render; -use yazi_parser::pick::ArrowOpt; -use yazi_widgets::Scrollable; - -use crate::pick::Pick; - -impl Pick { - #[yazi_codegen::command] - pub fn arrow(&mut self, opt: ArrowOpt) { - render!(self.scroll(opt.step)); - } -} - -impl Scrollable for Pick { - #[inline] - fn total(&self) -> usize { self.items.len() } - - #[inline] - fn limit(&self) -> usize { - self.position.offset.height.saturating_sub(YAZI.pick.border()) as usize - } - - #[inline] - fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor } - - #[inline] - fn offset_mut(&mut self) -> &mut usize { &mut self.offset } -} diff --git a/yazi-core/src/pick/commands/close.rs b/yazi-core/src/pick/commands/close.rs deleted file mode 100644 index bbd29ffd..00000000 --- a/yazi-core/src/pick/commands/close.rs +++ /dev/null @@ -1,19 +0,0 @@ -use anyhow::anyhow; -use yazi_macro::render; -use yazi_parser::pick::CloseOpt; - -use crate::pick::Pick; - -impl Pick { - #[yazi_codegen::command] - pub fn close(&mut self, opt: CloseOpt) { - if let Some(cb) = self.callback.take() { - _ = cb.send(if opt.submit { Ok(self.cursor) } else { Err(anyhow!("canceled")) }); - } - - self.cursor = 0; - self.offset = 0; - self.visible = false; - render!(); - } -} diff --git a/yazi-core/src/pick/commands/show.rs b/yazi-core/src/pick/commands/show.rs deleted file mode 100644 index c1edc25c..00000000 --- a/yazi-core/src/pick/commands/show.rs +++ /dev/null @@ -1,21 +0,0 @@ -use yazi_macro::render; -use yazi_parser::pick::ShowOpt; - -use crate::pick::Pick; - -impl Pick { - pub fn show(&mut self, opt: impl TryInto) { - let Ok(opt) = opt.try_into() else { - return; - }; - - self.close(false); - self.title = opt.cfg.title; - self.items = opt.cfg.items; - self.position = opt.cfg.position; - - self.callback = Some(opt.tx); - self.visible = true; - render!(); - } -} diff --git a/yazi-core/src/pick/mod.rs b/yazi-core/src/pick/mod.rs index c9f4add5..7275cd93 100644 --- a/yazi-core/src/pick/mod.rs +++ b/yazi-core/src/pick/mod.rs @@ -1,3 +1 @@ -yazi_macro::mod_pub!(commands); - yazi_macro::mod_flat!(pick); diff --git a/yazi-core/src/pick/pick.rs b/yazi-core/src/pick/pick.rs index 8309c27d..969ca526 100644 --- a/yazi-core/src/pick/pick.rs +++ b/yazi-core/src/pick/pick.rs @@ -1,17 +1,17 @@ use anyhow::Result; use tokio::sync::oneshot::Sender; -use yazi_config::popup::Position; +use yazi_config::{YAZI, popup::Position}; use yazi_widgets::Scrollable; #[derive(Default)] pub struct Pick { - pub(super) title: String, - pub(super) items: Vec, - pub position: Position, + pub title: String, + pub items: Vec, + pub position: Position, - pub(super) offset: usize, - pub cursor: usize, - pub(super) callback: Option>>, + pub offset: usize, + pub cursor: usize, + pub callback: Option>>, pub visible: bool, } @@ -25,3 +25,19 @@ impl Pick { self.items.iter().map(AsRef::as_ref).enumerate().skip(self.offset).take(self.limit()) } } + +impl Scrollable for Pick { + #[inline] + fn total(&self) -> usize { self.items.len() } + + #[inline] + fn limit(&self) -> usize { + self.position.offset.height.saturating_sub(YAZI.pick.border()) as usize + } + + #[inline] + fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor } + + #[inline] + fn offset_mut(&mut self) -> &mut usize { &mut self.offset } +} diff --git a/yazi-core/src/spot/commands/arrow.rs b/yazi-core/src/spot/commands/arrow.rs deleted file mode 100644 index e1eef324..00000000 --- a/yazi-core/src/spot/commands/arrow.rs +++ /dev/null @@ -1,23 +0,0 @@ -use yazi_macro::render; -use yazi_parser::spot::ArrowOpt; -use yazi_proxy::MgrProxy; - -use crate::spot::Spot; - -impl Spot { - #[yazi_codegen::command] - pub fn arrow(&mut self, opt: ArrowOpt) { - let Some(lock) = &mut self.lock else { return }; - - let new = opt.step.add(self.skip, lock.len().unwrap_or(u16::MAX as _), 0); - let Some(old) = lock.selected() else { - return MgrProxy::spot(Some(new)); - }; - - lock.select(Some(new)); - let new = lock.selected().unwrap(); - - self.skip = new; - render!(new != old); - } -} diff --git a/yazi-core/src/spot/commands/close.rs b/yazi-core/src/spot/commands/close.rs deleted file mode 100644 index 01212740..00000000 --- a/yazi-core/src/spot/commands/close.rs +++ /dev/null @@ -1,8 +0,0 @@ -use yazi_parser::spot::CloseOpt; - -use crate::spot::Spot; - -impl Spot { - #[yazi_codegen::command] - pub fn close(&mut self, _: CloseOpt) { self.reset(); } -} diff --git a/yazi-core/src/spot/commands/copy.rs b/yazi-core/src/spot/commands/copy.rs deleted file mode 100644 index 7dd5ffed..00000000 --- a/yazi-core/src/spot/commands/copy.rs +++ /dev/null @@ -1,26 +0,0 @@ -use yazi_parser::spot::CopyOpt; -use yazi_widgets::CLIPBOARD; - -use crate::spot::Spot; - -impl Spot { - #[yazi_codegen::command] - pub fn copy(&mut self, opt: CopyOpt) { - let Some(lock) = &self.lock else { return }; - let Some(table) = lock.table() else { return }; - - let mut s = String::new(); - match opt.r#type.as_ref() { - "cell" => { - let Some(cell) = table.selected_cell() else { return }; - s = cell.to_string(); - } - "line" => { - // TODO - } - _ => {} - } - - futures::executor::block_on(CLIPBOARD.set(s)); - } -} diff --git a/yazi-core/src/spot/commands/swipe.rs b/yazi-core/src/spot/commands/swipe.rs deleted file mode 100644 index 02f9e2b6..00000000 --- a/yazi-core/src/spot/commands/swipe.rs +++ /dev/null @@ -1,12 +0,0 @@ -use yazi_parser::spot::SwipeOpt; -use yazi_proxy::{MgrProxy, TabProxy}; - -use crate::spot::Spot; - -impl Spot { - #[yazi_codegen::command] - pub fn swipe(&mut self, opt: SwipeOpt) { - TabProxy::arrow(opt.step); - MgrProxy::spot(None); - } -} diff --git a/yazi-core/src/spot/mod.rs b/yazi-core/src/spot/mod.rs index 593f2f5d..9681606b 100644 --- a/yazi-core/src/spot/mod.rs +++ b/yazi-core/src/spot/mod.rs @@ -1,3 +1 @@ -yazi_macro::mod_pub!(commands); - yazi_macro::mod_flat!(spot); diff --git a/yazi-core/src/spot/spot.rs b/yazi-core/src/spot/spot.rs index 360536b3..d65c6a5f 100644 --- a/yazi-core/src/spot/spot.rs +++ b/yazi-core/src/spot/spot.rs @@ -23,7 +23,7 @@ impl Spot { } let Some(spotter) = YAZI.plugin.spotter(&file.url, &mime) else { - return self.close(()); + return self.reset(); }; self.abort(); diff --git a/yazi-core/src/tab/commands/arrow.rs b/yazi-core/src/tab/commands/arrow.rs deleted file mode 100644 index e385e275..00000000 --- a/yazi-core/src/tab/commands/arrow.rs +++ /dev/null @@ -1,26 +0,0 @@ -use yazi_macro::render; -use yazi_parser::tab::ArrowOpt; -use yazi_proxy::MgrProxy; - -use crate::tab::Tab; - -impl Tab { - #[yazi_codegen::command] - pub fn arrow(&mut self, opt: ArrowOpt) { - if !self.current.arrow(opt.step) { - return; - } - - // Visual selection - if let Some((start, items)) = self.mode.visual_mut() { - let end = self.current.cursor; - *items = (start.min(end)..=end.max(start)).collect(); - } - - self.hover(None); - MgrProxy::peek(false); - MgrProxy::watch(); - - render!(); - } -} diff --git a/yazi-core/src/tab/commands/back.rs b/yazi-core/src/tab/commands/back.rs deleted file mode 100644 index a5129398..00000000 --- a/yazi-core/src/tab/commands/back.rs +++ /dev/null @@ -1,12 +0,0 @@ -use yazi_parser::tab::CdSource; -use yazi_shared::event::CmdCow; - -use crate::tab::Tab; - -impl Tab { - pub fn back(&mut self, _: CmdCow) { - if let Some(u) = self.backstack.shift_backward().cloned() { - self.cd((u, CdSource::Back)); - } - } -} diff --git a/yazi-core/src/tab/commands/enter.rs b/yazi-core/src/tab/commands/enter.rs deleted file mode 100644 index 860ad5be..00000000 --- a/yazi-core/src/tab/commands/enter.rs +++ /dev/null @@ -1,14 +0,0 @@ -use yazi_parser::tab::CdSource; -use yazi_shared::event::CmdCow; - -use crate::tab::Tab; - -impl Tab { - pub fn enter(&mut self, _: CmdCow) { - self - .hovered() - .filter(|h| h.is_dir()) - .map(|h| h.url.to_regular()) - .map(|u| self.cd((u, CdSource::Enter))); - } -} diff --git a/yazi-core/src/tab/commands/escape.rs b/yazi-core/src/tab/commands/escape.rs deleted file mode 100644 index 6d70f942..00000000 --- a/yazi-core/src/tab/commands/escape.rs +++ /dev/null @@ -1,97 +0,0 @@ -use yazi_macro::{render, render_and}; -use yazi_parser::tab::{EscapeOpt, FilterOpt}; -use yazi_proxy::{AppProxy, MgrProxy}; - -use crate::tab::Tab; - -impl Tab { - #[yazi_codegen::command] - pub fn escape(&mut self, opt: EscapeOpt) { - if opt.is_empty() { - _ = self.escape_find() - || self.escape_visual() - || self.escape_filter() - || self.escape_select() - || self.escape_search(); - return; - } - - if opt.contains(EscapeOpt::FIND) { - self.escape_find(); - } - if opt.contains(EscapeOpt::VISUAL) { - self.escape_visual(); - } - if opt.contains(EscapeOpt::FILTER) { - self.escape_filter(); - } - if opt.contains(EscapeOpt::SELECT) { - self.escape_select(); - } - if opt.contains(EscapeOpt::SEARCH) { - self.escape_search(); - } - } - - pub fn escape_find(&mut self) -> bool { render_and!(self.finder.take().is_some()) } - - pub fn escape_visual(&mut self) -> bool { - if !self.mode.is_visual() { - return false; - } - - self.try_escape_visual(); - true - } - - pub fn escape_filter(&mut self) -> bool { - if self.current.files.filter().is_none() { - return false; - } - - self.filter_do(FilterOpt::default()); - render_and!(true) - } - - pub fn escape_select(&mut self) -> bool { - if self.selected.is_empty() { - return false; - } - - self.selected.clear(); - if self.hovered().is_some_and(|h| h.is_dir()) { - MgrProxy::peek(true); - } - render_and!(true) - } - - pub fn escape_search(&mut self) -> bool { - let b = self.cwd().is_search(); - self.search_stop(); - - render_and!(b) - } - - pub fn try_escape_visual(&mut self) -> bool { - let select = self.mode.is_select(); - let Some((_, indices)) = self.mode.take_visual() else { - return true; - }; - - render!(); - let urls: Vec<_> = - indices.into_iter().filter_map(|i| self.current.files.get(i)).map(|f| &f.url).collect(); - - if !select { - self.selected.remove_many(&urls); - } else if self.selected.add_many(&urls) != urls.len() { - AppProxy::notify_warn( - "Escape visual mode", - "Some files cannot be selected, due to path nesting conflict.", - ); - return false; - } - - true - } -} diff --git a/yazi-core/src/tab/commands/filter_do.rs b/yazi-core/src/tab/commands/filter_do.rs deleted file mode 100644 index 3582de8d..00000000 --- a/yazi-core/src/tab/commands/filter_do.rs +++ /dev/null @@ -1,37 +0,0 @@ -use yazi_fs::Filter; -use yazi_macro::render; -use yazi_parser::tab::FilterOpt; -use yazi_proxy::MgrProxy; - -use crate::tab::Tab; - -impl Tab { - #[yazi_codegen::command] - pub fn filter_do(&mut self, opt: FilterOpt) { - let filter = if opt.query.is_empty() { - None - } else if let Ok(f) = Filter::new(&opt.query, opt.case) { - Some(f) - } else { - return; - }; - - if opt.done { - MgrProxy::update_paged(); // Update for paged files in next loop - } - - let hovered = self.hovered().map(|f| f.urn_owned()); - if !self.current.files.set_filter(filter) { - return; - } - - self.current.repos(hovered.as_ref()); - if self.hovered().map(|f| f.urn()) != hovered.as_ref().map(|u| u.as_urn()) { - self.hover(None); - MgrProxy::peek(false); - MgrProxy::watch(); - } - - render!(); - } -} diff --git a/yazi-core/src/tab/commands/find_arrow.rs b/yazi-core/src/tab/commands/find_arrow.rs deleted file mode 100644 index 1bee33b3..00000000 --- a/yazi-core/src/tab/commands/find_arrow.rs +++ /dev/null @@ -1,20 +0,0 @@ -use yazi_macro::render; -use yazi_parser::tab::FindArrowOpt; - -use crate::tab::Tab; - -impl Tab { - #[yazi_codegen::command] - pub fn find_arrow(&mut self, opt: FindArrowOpt) { - let Some(finder) = &mut self.finder else { - return; - }; - - render!(finder.catchup(&self.current)); - if opt.prev { - finder.prev(&self.current.files, self.current.cursor, false).map(|s| self.arrow(s)); - } else { - finder.next(&self.current.files, self.current.cursor, false).map(|s| self.arrow(s)); - } - } -} diff --git a/yazi-core/src/tab/commands/find_do.rs b/yazi-core/src/tab/commands/find_do.rs deleted file mode 100644 index db160f9c..00000000 --- a/yazi-core/src/tab/commands/find_do.rs +++ /dev/null @@ -1,37 +0,0 @@ -use yazi_macro::render; -use yazi_parser::tab::FindOpt; - -use crate::tab::{Finder, Tab}; - -impl Tab { - #[yazi_codegen::command] - pub fn find_do(&mut self, opt: FindOpt) { - let Some(query) = opt.query else { - return; - }; - if query.is_empty() { - self.escape_find(); - return; - } - - let Ok(finder) = Finder::new(&query, opt.case) else { - return; - }; - if matches!(&self.finder, Some(f) if f.filter == finder.filter) { - return; - } - - let step = if opt.prev { - finder.prev(&self.current.files, self.current.cursor, true) - } else { - finder.next(&self.current.files, self.current.cursor, true) - }; - - if let Some(step) = step { - self.arrow(step); - } - - self.finder = Some(finder); - render!(); - } -} diff --git a/yazi-core/src/tab/commands/follow.rs b/yazi-core/src/tab/commands/follow.rs deleted file mode 100644 index 2f33bc16..00000000 --- a/yazi-core/src/tab/commands/follow.rs +++ /dev/null @@ -1,18 +0,0 @@ -use yazi_fs::clean_url; -use yazi_parser::tab::FollowOpt; - -use crate::tab::Tab; - -impl Tab { - #[yazi_codegen::command] - pub fn follow(&mut self, _: FollowOpt) { - let Some(file) = self.hovered() else { return }; - let Some(link_to) = &file.link_to else { return }; - - if link_to.is_absolute() { - self.reveal(link_to.to_owned()); - } else if let Some(p) = file.url.parent_url() { - self.reveal(clean_url(&p.join(link_to))); - } - } -} diff --git a/yazi-core/src/tab/commands/forward.rs b/yazi-core/src/tab/commands/forward.rs deleted file mode 100644 index c8de0525..00000000 --- a/yazi-core/src/tab/commands/forward.rs +++ /dev/null @@ -1,12 +0,0 @@ -use yazi_parser::tab::CdSource; -use yazi_shared::event::CmdCow; - -use crate::tab::Tab; - -impl Tab { - pub fn forward(&mut self, _: CmdCow) { - if let Some(u) = self.backstack.shift_forward().cloned() { - self.cd((u, CdSource::Forward)); - } - } -} diff --git a/yazi-core/src/tab/commands/hidden.rs b/yazi-core/src/tab/commands/hidden.rs deleted file mode 100644 index 4af36605..00000000 --- a/yazi-core/src/tab/commands/hidden.rs +++ /dev/null @@ -1,26 +0,0 @@ -use yazi_proxy::MgrProxy; -use yazi_shared::event::CmdCow; - -use crate::tab::Tab; - -impl Tab { - pub fn hidden(&mut self, mut c: CmdCow) { - self.pref.show_hidden = match c.take_first_str().as_deref() { - Some("show") => true, - Some("hide") => false, - _ => !self.pref.show_hidden, - }; - - let hovered = self.hovered().map(|f| f.url_owned()); - self.apply_files_attrs(); - - if hovered.as_ref() != self.hovered().map(|f| &f.url) { - self.hover(hovered); - MgrProxy::peek(false); - MgrProxy::watch(); - } else if self.hovered().is_some_and(|f| f.is_dir()) { - MgrProxy::peek(true); - } - MgrProxy::update_paged(); - } -} diff --git a/yazi-core/src/tab/commands/hover.rs b/yazi-core/src/tab/commands/hover.rs deleted file mode 100644 index 179df338..00000000 --- a/yazi-core/src/tab/commands/hover.rs +++ /dev/null @@ -1,34 +0,0 @@ -use yazi_dds::Pubsub; -use yazi_macro::{err, render}; -use yazi_parser::tab::HoverOpt; -use yazi_shared::url::{Url, Urn}; - -use crate::tab::Tab; - -impl Tab { - #[yazi_codegen::command] - pub fn hover(&mut self, opt: HoverOpt) { - if let Some(u) = opt.url { - self.hover_do(u); - } else { - self.current.arrow(0); - } - - // Publish through DDS - err!(Pubsub::pub_after_hover(self.id, self.hovered().map(|h| &h.url))); - } - - fn hover_do(&mut self, url: Url) { - // Hover on the file - if let Ok(p) = url.strip_prefix(self.cwd()) { - render!(self.current.hover(Urn::new(p))); - } - - // Turn on tracing - if self.hovered().is_some_and(|h| h.url == url) { - // `hover(Some)` occurs after user actions, such as create, rename, reveal, etc. - // At this point, it's intuitive to track the location of the file regardless. - self.current.trace = Some(url.urn_owned()); - } - } -} diff --git a/yazi-core/src/tab/commands/leave.rs b/yazi-core/src/tab/commands/leave.rs deleted file mode 100644 index 44fbd654..00000000 --- a/yazi-core/src/tab/commands/leave.rs +++ /dev/null @@ -1,16 +0,0 @@ -use yazi_parser::tab::{CdSource, LeaveOpt}; - -use crate::tab::Tab; - -impl Tab { - #[yazi_codegen::command] - pub fn leave(&mut self, _: LeaveOpt) { - self - .current - .hovered() - .and_then(|h| h.url.parent_url()) - .filter(|u| u != self.cwd()) - .or_else(|| self.cwd().parent_url()) - .map(|u| self.cd((u.into_regular(), CdSource::Leave))); - } -} diff --git a/yazi-core/src/tab/commands/linemode.rs b/yazi-core/src/tab/commands/linemode.rs deleted file mode 100644 index 767da8f2..00000000 --- a/yazi-core/src/tab/commands/linemode.rs +++ /dev/null @@ -1,18 +0,0 @@ -use yazi_macro::render; -use yazi_shared::event::CmdCow; - -use crate::tab::Tab; - -impl Tab { - pub fn linemode(&mut self, mut c: CmdCow) { - let Some(new) = c.take_first_str() else { return }; - if new == self.pref.linemode { - return; - } else if new.is_empty() || new.len() > 20 { - return; - } - - self.pref.linemode = new.into_owned(); - render!(); - } -} diff --git a/yazi-core/src/tab/commands/mod.rs b/yazi-core/src/tab/commands/mod.rs deleted file mode 100644 index e5f9291c..00000000 --- a/yazi-core/src/tab/commands/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -yazi_macro::mod_flat!( - arrow - back - cd - copy - enter - escape - filter - filter_do - find - find_arrow - find_do - follow - forward - hidden - hover - leave - linemode - reveal - search - shell - sort - toggle - toggle_all - update_peeked - update_spotted - visual_mode -); diff --git a/yazi-core/src/tab/commands/reveal.rs b/yazi-core/src/tab/commands/reveal.rs deleted file mode 100644 index 31930271..00000000 --- a/yazi-core/src/tab/commands/reveal.rs +++ /dev/null @@ -1,26 +0,0 @@ -use yazi_fs::{File, FilesOp}; -use yazi_parser::tab::RevealOpt; -use yazi_proxy::MgrProxy; - -use crate::tab::Tab; - -impl Tab { - #[yazi_codegen::command] - pub fn reveal(&mut self, opt: RevealOpt) { - let Some((parent, child)) = opt.target.pair() else { - return; - }; - - self.cd((parent.clone(), opt.source)); - self.current.hover(child.as_urn()); - - if !opt.no_dummy && self.hovered().is_none_or(|f| &child != f.urn()) { - let op = FilesOp::Creating(parent, vec![File::from_dummy(opt.target.clone(), None)]); - self.current.update_pub(self.id, op); - } - - self.hover(Some(opt.target)); - MgrProxy::peek(false); - MgrProxy::watch(); - } -} diff --git a/yazi-core/src/tab/commands/sort.rs b/yazi-core/src/tab/commands/sort.rs deleted file mode 100644 index 17ddfbd8..00000000 --- a/yazi-core/src/tab/commands/sort.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::str::FromStr; - -use yazi_fs::SortBy; -use yazi_proxy::MgrProxy; -use yazi_shared::event::CmdCow; - -use crate::{tab::Tab, tasks::Tasks}; - -impl Tab { - pub fn sort(&mut self, c: CmdCow, tasks: &Tasks) { - let mut new = self.pref.clone(); - new.sort_by = c.first_str().and_then(|s| SortBy::from_str(s).ok()).unwrap_or(new.sort_by); - new.sort_reverse = c.maybe_bool("reverse").unwrap_or(new.sort_reverse); - new.sort_dir_first = c.maybe_bool("dir-first").unwrap_or(new.sort_dir_first); - new.sort_sensitive = c.maybe_bool("sensitive").unwrap_or(new.sort_sensitive); - new.sort_translit = c.maybe_bool("translit").unwrap_or(new.sort_translit); - - if new == self.pref { - return; - } - - self.pref = new; - self.apply_files_attrs(); - self.hover(None); - tasks.prework_sorted(&self.current.files); - - MgrProxy::peek(false); - MgrProxy::watch(); - MgrProxy::update_paged(); - } -} diff --git a/yazi-core/src/tab/commands/toggle.rs b/yazi-core/src/tab/commands/toggle.rs deleted file mode 100644 index 54cae9b2..00000000 --- a/yazi-core/src/tab/commands/toggle.rs +++ /dev/null @@ -1,27 +0,0 @@ -use yazi_macro::render_and; -use yazi_parser::tab::ToggleOpt; -use yazi_proxy::AppProxy; - -use crate::tab::Tab; - -impl Tab { - #[yazi_codegen::command] - pub fn toggle(&mut self, opt: ToggleOpt) { - let Some(url) = opt.url.as_ref().or(self.current.hovered().map(|h| &h.url)) else { - return; - }; - - let b = match opt.state { - Some(true) => render_and!(self.selected.add(url)), - Some(false) => render_and!(self.selected.remove(url)) | true, - None => render_and!(self.selected.remove(url) || self.selected.add(url)), - }; - - if !b { - AppProxy::notify_warn( - "Toggle", - "This file cannot be selected, due to path nesting conflict.", - ); - } - } -} diff --git a/yazi-core/src/tab/commands/toggle_all.rs b/yazi-core/src/tab/commands/toggle_all.rs deleted file mode 100644 index 3e87061e..00000000 --- a/yazi-core/src/tab/commands/toggle_all.rs +++ /dev/null @@ -1,40 +0,0 @@ -use yazi_macro::render; -use yazi_parser::tab::ToggleAllOpt; -use yazi_proxy::AppProxy; - -use crate::tab::Tab; - -impl Tab { - #[yazi_codegen::command] - pub fn toggle_all(&mut self, opt: ToggleAllOpt) { - use yazi_shared::Either::*; - - let it = self.current.files.iter().map(|f| &f.url); - let either = match opt.state { - Some(true) if opt.urls.is_empty() => Left((vec![], it.collect())), - Some(true) => Right((vec![], opt.urls)), - Some(false) if opt.urls.is_empty() => Left((it.collect(), vec![])), - Some(false) => Right((opt.urls, vec![])), - None if opt.urls.is_empty() => Left(it.partition(|&u| self.selected.contains_key(u))), - None => Right(opt.urls.into_iter().partition(|u| self.selected.contains_key(u))), - }; - - let warn = match either { - Left((removal, addition)) => { - render!(self.selected.remove_many(&removal) > 0); - render!(self.selected.add_many(&addition), > 0) != addition.len() - } - Right((removal, addition)) => { - render!(self.selected.remove_many(&removal) > 0); - render!(self.selected.add_many(&addition), > 0) != addition.len() - } - }; - - if warn { - AppProxy::notify_warn( - "Toggle all", - "Some files cannot be selected, due to path nesting conflict.", - ); - } - } -} diff --git a/yazi-core/src/tab/commands/update_peeked.rs b/yazi-core/src/tab/commands/update_peeked.rs deleted file mode 100644 index eb66f770..00000000 --- a/yazi-core/src/tab/commands/update_peeked.rs +++ /dev/null @@ -1,23 +0,0 @@ -use yazi_macro::render; -use yazi_parser::tab::UpdatePeekedOpt; - -use crate::tab::Tab; - -impl Tab { - pub fn update_peeked(&mut self, opt: impl TryInto) { - let Some(hovered) = self.hovered().map(|h| &h.url) else { - return self.preview.reset(); - }; - - let Ok(opt): Result = opt.try_into() else { - return; - }; - - if opt.lock.url != *hovered { - return; - } - - self.preview.lock = Some(opt.lock); - render!(); - } -} diff --git a/yazi-core/src/tab/commands/update_spotted.rs b/yazi-core/src/tab/commands/update_spotted.rs deleted file mode 100644 index f96fadb7..00000000 --- a/yazi-core/src/tab/commands/update_spotted.rs +++ /dev/null @@ -1,31 +0,0 @@ -use yazi_macro::render; -use yazi_parser::tab::UpdateSpottedOpt; - -use crate::tab::Tab; - -impl Tab { - pub fn update_spotted(&mut self, opt: impl TryInto) { - let Some(hovered) = self.hovered().map(|h| &h.url) else { - return self.spot.reset(); - }; - - let Ok(mut opt): Result = opt.try_into() else { - return; - }; - - if opt.lock.url != *hovered { - return; - } - - if self.spot.lock.as_ref().is_none_or(|l| l.id != opt.lock.id) { - self.spot.skip = opt.lock.selected().unwrap_or_default(); - } else if let Some(s) = opt.lock.selected() { - self.spot.skip = s; - } else { - opt.lock.select(Some(self.spot.skip)); - } - - self.spot.lock = Some(opt.lock); - render!(); - } -} diff --git a/yazi-core/src/tab/commands/visual_mode.rs b/yazi-core/src/tab/commands/visual_mode.rs deleted file mode 100644 index f85db655..00000000 --- a/yazi-core/src/tab/commands/visual_mode.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::collections::BTreeSet; - -use yazi_macro::render; -use yazi_parser::tab::VisualModeOpt; - -use crate::tab::{Mode, Tab}; - -impl Tab { - #[yazi_codegen::command] - pub fn visual_mode(&mut self, opt: VisualModeOpt) { - let idx = self.current.cursor; - if opt.unset { - self.mode = Mode::Unset(idx, BTreeSet::from([idx])); - } else { - self.mode = Mode::Select(idx, BTreeSet::from([idx])); - }; - render!(); - } -} diff --git a/yazi-core/src/tab/finder.rs b/yazi-core/src/tab/finder.rs index 534a6a85..71be5df4 100644 --- a/yazi-core/src/tab/finder.rs +++ b/yazi-core/src/tab/finder.rs @@ -19,7 +19,7 @@ struct FinderLock { } impl Finder { - pub(super) fn new(s: &str, case: FilterCase) -> Result { + pub fn new(s: &str, case: FilterCase) -> Result { Ok(Self { filter: Filter::new(s, case)?, matched: Default::default(), @@ -27,7 +27,7 @@ impl Finder { }) } - pub(super) fn prev(&self, files: &Files, cursor: usize, include: bool) -> Option { + pub fn prev(&self, files: &Files, cursor: usize, include: bool) -> Option { for i in !include as usize..files.len() { let idx = (cursor + files.len() - i) % files.len(); if self.filter.matches(files[idx].name()) { @@ -37,7 +37,7 @@ impl Finder { None } - pub(super) fn next(&self, files: &Files, cursor: usize, include: bool) -> Option { + pub fn next(&self, files: &Files, cursor: usize, include: bool) -> Option { for i in !include as usize..files.len() { let idx = (cursor + i) % files.len(); if self.filter.matches(files[idx].name()) { @@ -47,7 +47,7 @@ impl Finder { None } - pub(super) fn catchup(&mut self, folder: &Folder) -> bool { + pub fn catchup(&mut self, folder: &Folder) -> bool { if self.lock == *folder { return false; } diff --git a/yazi-core/src/tab/folder.rs b/yazi-core/src/tab/folder.rs index c1e28091..7b0343c0 100644 --- a/yazi-core/src/tab/folder.rs +++ b/yazi-core/src/tab/folder.rs @@ -4,9 +4,10 @@ use yazi_config::{LAYOUT, YAZI}; use yazi_dds::Pubsub; use yazi_fs::{File, Files, FilesOp, FolderStage, cha::Cha}; use yazi_macro::err; +use yazi_parser::Step; use yazi_proxy::MgrProxy; use yazi_shared::{Id, url::{Url, Urn, UrnBuf}}; -use yazi_widgets::{Scrollable, Step}; +use yazi_widgets::Scrollable; pub struct Folder { pub url: Url, diff --git a/yazi-core/src/tab/mod.rs b/yazi-core/src/tab/mod.rs index e925851a..c4a014ae 100644 --- a/yazi-core/src/tab/mod.rs +++ b/yazi-core/src/tab/mod.rs @@ -1,3 +1 @@ -yazi_macro::mod_pub!(commands); - yazi_macro::mod_flat!(backstack finder folder history mode preference preview selected tab); diff --git a/yazi-core/src/tasks/commands/arrow.rs b/yazi-core/src/tasks/commands/arrow.rs deleted file mode 100644 index 7b451061..00000000 --- a/yazi-core/src/tasks/commands/arrow.rs +++ /dev/null @@ -1,14 +0,0 @@ -use yazi_macro::render; -use yazi_parser::tasks::ArrowOpt; - -use crate::tasks::Tasks; - -impl Tasks { - #[yazi_codegen::command] - pub fn arrow(&mut self, opt: ArrowOpt) { - let old = self.cursor; - self.cursor = opt.step.add(self.cursor, self.summaries.len(), Self::limit()); - - render!(self.cursor != old); - } -} diff --git a/yazi-core/src/tasks/commands/cancel.rs b/yazi-core/src/tasks/commands/cancel.rs deleted file mode 100644 index a8cd9be3..00000000 --- a/yazi-core/src/tasks/commands/cancel.rs +++ /dev/null @@ -1,17 +0,0 @@ -use yazi_macro::render; -use yazi_shared::event::CmdCow; - -use crate::tasks::Tasks; - -impl Tasks { - pub fn cancel(&mut self, _: CmdCow) { - let id = self.ongoing().lock().get_id(self.cursor); - if id.map(|id| self.scheduler.cancel(id)) != Some(true) { - return; - } - - self.summaries = self.paginate(); - self.arrow(0); - render!(); - } -} diff --git a/yazi-core/src/tasks/commands/close.rs b/yazi-core/src/tasks/commands/close.rs deleted file mode 100644 index 3dcb708d..00000000 --- a/yazi-core/src/tasks/commands/close.rs +++ /dev/null @@ -1,19 +0,0 @@ -use yazi_macro::render; -use yazi_parser::tasks::CloseOpt; - -use crate::tasks::Tasks; - -impl Tasks { - #[yazi_codegen::command] - pub fn close(&mut self, _: CloseOpt) { - if !self.visible { - return; - } - - self.visible = false; - self.summaries = Vec::new(); - - self.arrow(0); - render!(); - } -} diff --git a/yazi-core/src/tasks/commands/open_with.rs b/yazi-core/src/tasks/commands/open_with.rs deleted file mode 100644 index 3bee010f..00000000 --- a/yazi-core/src/tasks/commands/open_with.rs +++ /dev/null @@ -1,15 +0,0 @@ -use yazi_proxy::options::OpenWithOpt; - -use crate::tasks::Tasks; - -impl Tasks { - pub fn open_with(&mut self, opt: impl TryInto) { - if let Ok(opt) = opt.try_into() { - self.process_from_opener( - opt.cwd, - opt.opener, - opt.targets.into_iter().map(|u| u.into_path().into_os_string()).collect(), - ); - } - } -} diff --git a/yazi-core/src/tasks/commands/process_exec.rs b/yazi-core/src/tasks/commands/process_exec.rs deleted file mode 100644 index 0777d42e..00000000 --- a/yazi-core/src/tasks/commands/process_exec.rs +++ /dev/null @@ -1,11 +0,0 @@ -use yazi_proxy::options::ProcessExecOpt; - -use crate::tasks::Tasks; - -impl Tasks { - pub fn process_exec(&mut self, opt: impl TryInto) { - if let Ok(opt) = opt.try_into() { - self.scheduler.process_open(opt); - } - } -} diff --git a/yazi-core/src/tasks/commands/show.rs b/yazi-core/src/tasks/commands/show.rs deleted file mode 100644 index f8c6959e..00000000 --- a/yazi-core/src/tasks/commands/show.rs +++ /dev/null @@ -1,19 +0,0 @@ -use yazi_macro::render; -use yazi_parser::tasks::ShowOpt; - -use crate::tasks::Tasks; - -impl Tasks { - #[yazi_codegen::command] - pub fn show(&mut self, _: ShowOpt) { - if self.visible { - return; - } - - self.visible = true; - self.summaries = self.paginate(); - - self.arrow(0); - render!(); - } -} diff --git a/yazi-core/src/tasks/mod.rs b/yazi-core/src/tasks/mod.rs index 77dff620..5b056421 100644 --- a/yazi-core/src/tasks/mod.rs +++ b/yazi-core/src/tasks/mod.rs @@ -1,6 +1,4 @@ -yazi_macro::mod_pub!(commands); - -yazi_macro::mod_flat!(file plugin preload process progress tasks); +yazi_macro::mod_flat!(file plugin preload process tasks); pub const TASKS_BORDER: u16 = 2; pub const TASKS_PADDING: u16 = 2; diff --git a/yazi-core/src/tasks/progress.rs b/yazi-core/src/tasks/progress.rs deleted file mode 100644 index faf8a1e9..00000000 --- a/yazi-core/src/tasks/progress.rs +++ /dev/null @@ -1,31 +0,0 @@ -use serde::Serialize; -use yazi_scheduler::Ongoing; - -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] -pub struct TasksProgress { - pub total: u32, - pub succ: u32, - pub fail: u32, - - pub found: u64, - pub processed: u64, -} - -impl From<&Ongoing> for TasksProgress { - fn from(ongoing: &Ongoing) -> Self { - let mut progress = Self::default(); - if ongoing.is_empty() { - return progress; - } - - for task in ongoing.values() { - progress.total += task.total; - progress.succ += task.succ; - progress.fail += task.fail; - - progress.found += task.found; - progress.processed += task.processed; - } - progress - } -} diff --git a/yazi-core/src/tasks/tasks.rs b/yazi-core/src/tasks/tasks.rs index 11a906d2..ff1b9023 100644 --- a/yazi-core/src/tasks/tasks.rs +++ b/yazi-core/src/tasks/tasks.rs @@ -4,14 +4,15 @@ use parking_lot::Mutex; use tokio::{task::JoinHandle, time::sleep}; use yazi_adapter::Dimension; use yazi_macro::emit; +use yazi_parser::app::TasksProgress; use yazi_scheduler::{Ongoing, Scheduler, TaskSummary}; use yazi_shared::event::Cmd; -use super::{TASKS_BORDER, TASKS_PADDING, TASKS_PERCENT, TasksProgress}; +use super::{TASKS_BORDER, TASKS_PADDING, TASKS_PERCENT}; pub struct Tasks { - pub(super) scheduler: Arc, - handle: JoinHandle<()>, + pub scheduler: Arc, + handle: JoinHandle<()>, pub visible: bool, pub cursor: usize, @@ -29,7 +30,7 @@ impl Tasks { loop { sleep(Duration::from_millis(500)).await; - let new = TasksProgress::from(&*ongoing.lock()); + let new = ongoing.lock().progress(); if last != new { last = new; emit!(Call(Cmd::new("app:update_progress").with_any("progress", new))); diff --git a/yazi-core/src/which/commands/callback.rs b/yazi-core/src/which/commands/callback.rs deleted file mode 100644 index 7526db92..00000000 --- a/yazi-core/src/which/commands/callback.rs +++ /dev/null @@ -1,16 +0,0 @@ -use tracing::error; -use yazi_parser::which::CallbackOpt; - -use crate::which::Which; - -impl Which { - pub fn callback(&mut self, opt: impl TryInto) { - let Ok(opt) = opt.try_into() else { - return; - }; - - if opt.tx.try_send(opt.idx).is_err() { - error!("which callback: send error"); - } - } -} diff --git a/yazi-core/src/which/commands/show.rs b/yazi-core/src/which/commands/show.rs deleted file mode 100644 index 2dcacf11..00000000 --- a/yazi-core/src/which/commands/show.rs +++ /dev/null @@ -1,40 +0,0 @@ -use yazi_config::{KEYMAP, keymap::Key}; -use yazi_macro::render; -use yazi_parser::which::ShowOpt; -use yazi_shared::Layer; - -use crate::which::{Which, WhichSorter}; - -impl Which { - pub fn show(&mut self, opt: impl TryInto) { - let Ok(opt) = opt.try_into() else { - return; - }; - - if opt.cands.is_empty() { - return; - } - - self.times = 0; - self.cands = opt.cands.into_iter().map(|c| c.into()).collect(); - - self.visible = true; - self.silent = opt.silent; - render!(); - } - - pub fn show_with(&mut self, key: Key, layer: Layer) { - self.times = 1; - self.cands = KEYMAP - .get(layer) - .iter() - .filter(|c| c.on.len() > 1 && c.on[0] == key) - .map(|c| c.into()) - .collect(); - - WhichSorter::default().sort(&mut self.cands); - self.visible = true; - self.silent = false; - render!(); - } -} diff --git a/yazi-core/src/which/mod.rs b/yazi-core/src/which/mod.rs index 2ce4088c..add09c1a 100644 --- a/yazi-core/src/which/mod.rs +++ b/yazi-core/src/which/mod.rs @@ -1,3 +1 @@ -yazi_macro::mod_pub!(commands); - yazi_macro::mod_flat!(sorter which); diff --git a/yazi-core/src/which/which.rs b/yazi-core/src/which/which.rs index d499f1a6..3c2c7d68 100644 --- a/yazi-core/src/which/which.rs +++ b/yazi-core/src/which/which.rs @@ -1,5 +1,8 @@ -use yazi_config::keymap::{ChordCow, Key}; -use yazi_macro::{emit, render_and}; +use yazi_config::{KEYMAP, keymap::{ChordCow, Key}}; +use yazi_macro::{emit, render, render_and}; +use yazi_shared::Layer; + +use crate::which::WhichSorter; #[derive(Default)] pub struct Which { @@ -36,4 +39,19 @@ impl Which { self.visible = false; self.silent = false; } + + pub fn show_with(&mut self, key: Key, layer: Layer) { + self.times = 1; + self.cands = KEYMAP + .get(layer) + .iter() + .filter(|c| c.on.len() > 1 && c.on[0] == key) + .map(|c| c.into()) + .collect(); + + WhichSorter::default().sort(&mut self.cands); + self.visible = true; + self.silent = false; + render!(); + } } diff --git a/yazi-fm/Cargo.toml b/yazi-fm/Cargo.toml index 205f4c38..7ee042af 100644 --- a/yazi-fm/Cargo.toml +++ b/yazi-fm/Cargo.toml @@ -23,15 +23,16 @@ default = [ "vendored-lua" ] vendored-lua = [ "mlua/vendored" ] [dependencies] +yazi-actor = { path = "../yazi-actor", version = "25.6.11" } yazi-adapter = { path = "../yazi-adapter", version = "25.6.11" } yazi-binding = { path = "../yazi-binding", version = "25.6.11" } yazi-boot = { path = "../yazi-boot", version = "25.6.11" } -yazi-codegen = { path = "../yazi-codegen", version = "25.6.11" } yazi-config = { path = "../yazi-config", version = "25.6.11" } yazi-core = { path = "../yazi-core", version = "25.6.11" } yazi-dds = { path = "../yazi-dds", version = "25.6.11" } yazi-fs = { path = "../yazi-fs", version = "25.6.11" } yazi-macro = { path = "../yazi-macro", version = "25.6.11" } +yazi-parser = { path = "../yazi-parser", version = "25.6.11" } yazi-plugin = { path = "../yazi-plugin", version = "25.6.11" } yazi-proxy = { path = "../yazi-proxy", version = "25.6.11" } yazi-shared = { path = "../yazi-shared", version = "25.6.11" } diff --git a/yazi-fm/src/app/app.rs b/yazi-fm/src/app/app.rs index 94379740..0c9a0d14 100644 --- a/yazi-fm/src/app/app.rs +++ b/yazi-fm/src/app/app.rs @@ -1,17 +1,15 @@ use std::{sync::atomic::Ordering, time::{Duration, Instant}}; use anyhow::Result; -use crossterm::event::KeyEvent; use tokio::{select, time::sleep}; -use yazi_config::keymap::Key; -use yazi_macro::emit; -use yazi_shared::event::{CmdCow, Event, NEED_RENDER}; -use yazi_widgets::input::InputMode; +use yazi_core::Core; +use yazi_macro::act; +use yazi_shared::event::{Event, NEED_RENDER}; -use crate::{Executor, Router, Signals, Term}; +use crate::{Dispatcher, Signals, Term}; pub(crate) struct App { - pub(crate) core: yazi_core::Core, + pub(crate) core: Core, pub(crate) term: Option, pub(crate) signals: Signals, } @@ -21,22 +19,23 @@ impl App { let term = Term::start()?; let (mut rx, signals) = (Event::take(), Signals::start()?); - let mut app = Self { core: yazi_core::Core::make(), term: Some(term), signals }; - app.render(); + let mut app = Self { core: Core::make(), term: Some(term), signals }; + act!(bootstrap, app)?; + act!(render, app)?; let mut events = Vec::with_capacity(50); let (mut timeout, mut last_render) = (None, Instant::now()); macro_rules! drain_events { () => { for event in events.drain(..) { - app.dispatch(event)?; + Dispatcher::new(&mut app).dispatch(event)?; if !NEED_RENDER.load(Ordering::Relaxed) { continue; } timeout = Duration::from_millis(10).checked_sub(last_render.elapsed()); if timeout.is_none() { - app.render(); + act!(render, app)?; last_render = Instant::now(); } } @@ -47,7 +46,7 @@ impl App { if let Some(t) = timeout.take() { select! { _ = sleep(t) => { - app.render(); + act!(render, app)?; last_render = Instant::now(); } n = rx.recv_many(&mut events, 50) => { @@ -63,50 +62,4 @@ impl App { } Ok(()) } - - #[inline] - fn dispatch(&mut self, event: Event) -> Result<()> { - match event { - Event::Call(cmd) => self.dispatch_call(cmd), - Event::Seq(cmds) => self.dispatch_seq(cmds), - Event::Render => self.dispatch_render(), - Event::Key(key) => self.dispatch_key(key), - Event::Mouse(mouse) => self.mouse(mouse), - Event::Resize => self.resize(()), - Event::Paste(str) => self.dispatch_paste(str), - Event::Quit(opt) => self.quit(opt), - } - Ok(()) - } - - #[inline] - fn dispatch_call(&mut self, cmd: CmdCow) { Executor::new(self).execute(cmd); } - - #[inline] - fn dispatch_seq(&mut self, mut cmds: Vec) { - if let Some(last) = cmds.pop() { - Executor::new(self).execute(last); - } - if !cmds.is_empty() { - emit!(Seq(cmds)); - } - } - - #[inline] - fn dispatch_render(&mut self) { NEED_RENDER.store(true, Ordering::Relaxed); } - - #[inline] - fn dispatch_key(&mut self, key: KeyEvent) { Router::new(self).route(Key::from(key)); } - - #[inline] - fn dispatch_paste(&mut self, str: String) { - if self.core.input.visible { - let input = &mut self.core.input; - if input.mode() == InputMode::Insert { - input.type_str(&str); - } else if input.mode() == InputMode::Replace { - input.replace_str(&str); - } - } - } } diff --git a/yazi-fm/src/app/commands/accept_payload.rs b/yazi-fm/src/app/commands/accept_payload.rs index e6792da6..36584b90 100644 --- a/yazi-fm/src/app/commands/accept_payload.rs +++ b/yazi-fm/src/app/commands/accept_payload.rs @@ -1,32 +1,29 @@ +use anyhow::Result; use mlua::IntoLua; use tracing::error; use yazi_binding::runtime_mut; -use yazi_dds::{LOCAL, Payload, REMOTE}; +use yazi_dds::{LOCAL, REMOTE}; +use yazi_macro::succ; +use yazi_parser::app::AcceptPayload; use yazi_plugin::LUA; -use yazi_shared::event::CmdCow; +use yazi_shared::event::Data; use crate::{app::App, lives::Lives}; impl App { - pub(crate) fn accept_payload(&mut self, mut cmd: CmdCow) { - let Some(payload) = cmd.take_any::("payload") else { - return; - }; - - let kind = payload.body.kind().to_owned(); - let lock = if payload.receiver == 0 || payload.receiver != payload.sender { + pub(crate) fn accept_payload(&mut self, opt: AcceptPayload) -> Result { + let kind = opt.payload.body.kind().to_owned(); + let lock = if opt.payload.receiver == 0 || opt.payload.receiver != opt.payload.sender { REMOTE.read() } else { LOCAL.read() }; - let Some(handlers) = lock.get(&kind).filter(|&m| !m.is_empty()).cloned() else { - return; - }; + let Some(handlers) = lock.get(&kind).filter(|&m| !m.is_empty()).cloned() else { succ!() }; drop(lock); - _ = Lives::scope(&self.core, || { - let body = payload.body.into_lua(&LUA)?; + succ!(Lives::scope(&self.core, || { + let body = opt.payload.body.into_lua(&LUA)?; for (id, cb) in handlers { runtime_mut!(LUA)?.push(&id); if let Err(e) = cb.call::<()>(body.clone()) { @@ -35,6 +32,6 @@ impl App { runtime_mut!(LUA)?.pop(); } Ok(()) - }); + })?); } } diff --git a/yazi-fm/src/app/commands/bootstrap.rs b/yazi-fm/src/app/commands/bootstrap.rs new file mode 100644 index 00000000..7cd3e9f5 --- /dev/null +++ b/yazi-fm/src/app/commands/bootstrap.rs @@ -0,0 +1,28 @@ +use anyhow::Result; +use yazi_actor::Ctx; +use yazi_boot::BOOT; +use yazi_macro::{act, succ}; +use yazi_parser::{VoidOpt, tab::CdSource}; +use yazi_shared::{event::Data, url::Url}; + +use crate::app::App; + +impl App { + pub fn bootstrap(&mut self, _: VoidOpt) -> Result { + for (i, file) in BOOT.files.iter().enumerate() { + let tabs = &mut self.core.mgr.tabs; + if tabs.len() <= i { + tabs.push(Default::default()); + } + + let cx = &mut Ctx { core: &mut self.core, tab: i }; + if file.is_empty() { + act!(mgr:cd, cx, (Url::from(&BOOT.cwds[i]), CdSource::Tab))?; + } else { + act!(mgr:reveal, cx, (Url::from(BOOT.cwds[i].join(file)), CdSource::Tab))?; + } + } + + succ!(); + } +} diff --git a/yazi-fm/src/app/commands/mod.rs b/yazi-fm/src/app/commands/mod.rs index 2e1fc09a..55ce2950 100644 --- a/yazi-fm/src/app/commands/mod.rs +++ b/yazi-fm/src/app/commands/mod.rs @@ -1,5 +1,6 @@ yazi_macro::mod_flat!( accept_payload + bootstrap mouse notify plugin diff --git a/yazi-fm/src/app/commands/mouse.rs b/yazi-fm/src/app/commands/mouse.rs index 128fc8fc..c6984c74 100644 --- a/yazi-fm/src/app/commands/mouse.rs +++ b/yazi-fm/src/app/commands/mouse.rs @@ -1,26 +1,21 @@ -use crossterm::event::{MouseEvent, MouseEventKind}; +use anyhow::Result; +use crossterm::event::MouseEventKind; use mlua::{ObjectLike, Table}; use tracing::error; use yazi_config::YAZI; +use yazi_macro::succ; +use yazi_parser::app::MouseOpt; use yazi_plugin::LUA; +use yazi_shared::event::Data; use crate::{app::App, lives::Lives}; -struct Opt { - event: MouseEvent, -} - -impl From for Opt { - fn from(event: MouseEvent) -> Self { Self { event } } -} - impl App { - #[yazi_codegen::command] - pub fn mouse(&mut self, opt: Opt) { + pub fn mouse(&mut self, opt: MouseOpt) -> Result { let event = yazi_plugin::bindings::MouseEvent::from(opt.event); - let Some(size) = self.term.as_ref().and_then(|t| t.size().ok()) else { return }; + let Some(size) = self.term.as_ref().and_then(|t| t.size().ok()) else { succ!() }; - let res = Lives::scope(&self.core, move || { + let result = Lives::scope(&self.core, move || { let area = yazi_binding::elements::Rect::from(size); let root = LUA.globals().raw_get::("Root")?.call_method::
("new", area)?; @@ -45,8 +40,9 @@ impl App { Ok(()) }); - if let Err(e) = res { + if let Err(ref e) = result { error!("{e}"); } + succ!(result?); } } diff --git a/yazi-fm/src/app/commands/notify.rs b/yazi-fm/src/app/commands/notify.rs index 529bbc83..a0cb89c1 100644 --- a/yazi-fm/src/app/commands/notify.rs +++ b/yazi-fm/src/app/commands/notify.rs @@ -1,13 +1,12 @@ +use anyhow::Result; +use yazi_macro::succ; use yazi_proxy::options::NotifyOpt; +use yazi_shared::event::Data; use crate::app::App; impl App { - pub(crate) fn notify(&mut self, opt: impl TryInto) { - let Ok(opt) = opt.try_into() else { - return; - }; - - self.core.notify.push(opt); + pub(crate) fn notify(&mut self, opt: NotifyOpt) -> Result { + succ!(self.core.notify.push(opt)); } } diff --git a/yazi-fm/src/app/commands/plugin.rs b/yazi-fm/src/app/commands/plugin.rs index 7786441c..7038be8f 100644 --- a/yazi-fm/src/app/commands/plugin.rs +++ b/yazi-fm/src/app/commands/plugin.rs @@ -1,22 +1,18 @@ -use std::fmt::Display; - +use anyhow::Result; use mlua::ObjectLike; use scopeguard::defer; use tracing::{error, warn}; use yazi_binding::runtime_mut; use yazi_dds::Sendable; +use yazi_macro::succ; use yazi_plugin::{LUA, loader::{LOADER, Loader}}; use yazi_proxy::{AppProxy, options::{PluginMode, PluginOpt}}; +use yazi_shared::event::Data; use crate::{app::App, lives::Lives}; impl App { - pub(crate) fn plugin(&mut self, opt: impl TryInto) { - let mut opt = match opt.try_into() { - Ok(opt) => opt as PluginOpt, - Err(e) => return warn!("{e}"), - }; - + pub(crate) fn plugin(&mut self, mut opt: PluginOpt) -> Result { let mut hits = false; if let Some(chunk) = LOADER.read().get(opt.id.as_ref()) { hits = true; @@ -24,7 +20,7 @@ impl App { } if opt.mode == PluginMode::Async { - return self.core.tasks.plugin_micro(opt); + succ!(self.core.tasks.plugin_micro(opt)); } else if opt.mode == PluginMode::Sync && hits { return self.plugin_do(opt); } @@ -35,36 +31,32 @@ impl App { Err(e) => AppProxy::notify_error("Plugin load failed", e), } }); + succ!(); } - pub(crate) fn plugin_do(&mut self, opt: impl TryInto) { - let opt = match opt.try_into() { - Ok(opt) => opt as PluginOpt, - Err(e) => return warn!("{e}"), - }; - + pub(crate) fn plugin_do(&mut self, opt: PluginOpt) -> Result { let loader = LOADER.read(); let Some(chunk) = loader.get(opt.id.as_ref()) else { - return warn!("plugin `{}` not found", opt.id); + succ!(warn!("plugin `{}` not found", opt.id)); }; if let Err(e) = Loader::compatible_or_error(&opt.id, chunk) { - return AppProxy::notify_error("Incompatible plugin", e); + succ!(AppProxy::notify_error("Incompatible plugin", e)); } if opt.mode.auto_then(chunk.sync_entry) != PluginMode::Sync { - return self.core.tasks.plugin_micro(opt); + succ!(self.core.tasks.plugin_micro(opt)); } match runtime_mut!(LUA) { Ok(mut r) => r.push(&opt.id), - Err(e) => return warn!("{e}"), + Err(e) => succ!(warn!("{e}")), } defer! { _ = runtime_mut!(LUA).map(|mut r| r.pop()) } let plugin = match LOADER.load_with(&LUA, &opt.id, chunk) { Ok(t) => t, - Err(e) => return warn!("{e}"), + Err(e) => succ!(warn!("{e}")), }; drop(loader); @@ -76,8 +68,9 @@ impl App { plugin.call_method("entry", job) } }); - if let Err(e) = result { + if let Err(ref e) = result { error!("Sync plugin `{}` failed: {e}", opt.id); } + succ!(result?); } } diff --git a/yazi-fm/src/app/commands/reflow.rs b/yazi-fm/src/app/commands/reflow.rs index fbbb013f..9c73e56d 100644 --- a/yazi-fm/src/app/commands/reflow.rs +++ b/yazi-fm/src/app/commands/reflow.rs @@ -1,26 +1,17 @@ +use anyhow::Result; use mlua::Value; use ratatui::layout::Position; use tracing::error; use yazi_config::LAYOUT; -use yazi_macro::render; -use yazi_shared::event::CmdCow; +use yazi_macro::{render, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; use crate::{Root, app::App, lives::Lives}; -struct Opt; - -impl From for Opt { - fn from(_: CmdCow) -> Self { Self } -} - -impl From<()> for Opt { - fn from(_: ()) -> Self { Self } -} - impl App { - #[yazi_codegen::command] - pub fn reflow(&mut self, _: Opt) { - let Some(size) = self.term.as_ref().and_then(|t| t.size().ok()) else { return }; + pub fn reflow(&mut self, _: VoidOpt) -> Result { + let Some(size) = self.term.as_ref().and_then(|t| t.size().ok()) else { succ!() }; let mut layout = LAYOUT.get(); let result = Lives::scope(&self.core, || { @@ -48,8 +39,9 @@ impl App { render!(); } - if let Err(e) = result { + if let Err(ref e) = result { error!("Failed to `reflow()` the `Root` component:\n{e}"); } + succ!(); } } diff --git a/yazi-fm/src/app/commands/render.rs b/yazi-fm/src/app/commands/render.rs index 4bd5b21b..e9b17b1a 100644 --- a/yazi-fm/src/app/commands/render.rs +++ b/yazi-fm/src/app/commands/render.rs @@ -1,17 +1,21 @@ use std::sync::atomic::{AtomicU8, Ordering}; +use anyhow::Result; use crossterm::{cursor::{MoveTo, SetCursorStyle, Show}, execute, queue, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}}; use ratatui::{CompletedFrame, backend::{Backend, CrosstermBackend}, buffer::Buffer, layout::Position}; +use yazi_actor::Ctx; use yazi_binding::elements::COLLISION; -use yazi_shared::event::NEED_RENDER; +use yazi_macro::{act, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::{Data, NEED_RENDER}; use yazi_term::tty::TTY; use crate::{app::App, lives::Lives, root::Root}; impl App { - pub(crate) fn render(&mut self) { + pub(crate) fn render(&mut self, _: VoidOpt) -> Result { NEED_RENDER.store(false, Ordering::Relaxed); - let Some(term) = &mut self.term else { return }; + let Some(term) = &mut self.term else { succ!() }; Self::routine(true, None); let _guard = scopeguard::guard(self.core.cursor(), |c| Self::routine(false, c)); @@ -27,19 +31,20 @@ impl App { Self::patch(frame); } if !self.core.notify.messages.is_empty() { - self.render_partially(); + act!(render_partially, self)?; } // Reload preview if collision is resolved if collision && !COLLISION.load(Ordering::Relaxed) { - self.core.mgr.peek(true); + act!(mgr:peek, &mut Ctx::active(&mut self.core), true)?; } + succ!(); } - pub(crate) fn render_partially(&mut self) { - let Some(term) = &mut self.term else { return }; + pub(crate) fn render_partially(&mut self, _: VoidOpt) -> Result { + let Some(term) = &mut self.term else { succ!() }; if !term.can_partial() { - return self.render(); + return act!(render, self); } Self::routine(true, None); @@ -58,6 +63,7 @@ impl App { if COLLISION.load(Ordering::Relaxed) { Self::patch(frame); } + succ!(); } #[inline] diff --git a/yazi-fm/src/app/commands/resize.rs b/yazi-fm/src/app/commands/resize.rs index 8da72285..23851b84 100644 --- a/yazi-fm/src/app/commands/resize.rs +++ b/yazi-fm/src/app/commands/resize.rs @@ -1,25 +1,19 @@ -use yazi_shared::event::CmdCow; +use anyhow::Result; +use yazi_actor::Ctx; +use yazi_macro::act; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; use crate::app::App; -struct Opt; - -impl From for Opt { - fn from(_: CmdCow) -> Self { Self } -} - -impl From<()> for Opt { - fn from(_: ()) -> Self { Self } -} - impl App { - #[yazi_codegen::command] - pub fn resize(&mut self, _: Opt) { - self.reflow(()); + pub fn resize(&mut self, _: VoidOpt) -> Result { + act!(reflow, self)?; - self.core.current_mut().sync_page(true); self.core.current_mut().arrow(0); - self.core.mgr.peek(false); - self.core.mgr.parent_mut().map(|f| f.arrow(0)); + self.core.parent_mut().map(|f| f.arrow(0)); + self.core.current_mut().sync_page(true); + + act!(mgr:peek, &mut Ctx::active(&mut self.core)) } } diff --git a/yazi-fm/src/app/commands/resume.rs b/yazi-fm/src/app/commands/resume.rs index c86ee884..b0a17ef4 100644 --- a/yazi-fm/src/app/commands/resume.rs +++ b/yazi-fm/src/app/commands/resume.rs @@ -1,19 +1,21 @@ -use yazi_macro::render; -use yazi_shared::event::CmdCow; +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; use crate::{Term, app::App}; impl App { - pub(crate) fn resume(&mut self, _: CmdCow) { - self.core.active_mut().preview.reset_image(); + pub(crate) fn resume(&mut self, _: VoidOpt) -> 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. - self.resize(()); + act!(resize, self)?; self.signals.resume(None); - render!(); + succ!(render!()); } } diff --git a/yazi-fm/src/app/commands/stop.rs b/yazi-fm/src/app/commands/stop.rs index 2e891f63..45c796c0 100644 --- a/yazi-fm/src/app/commands/stop.rs +++ b/yazi-fm/src/app/commands/stop.rs @@ -1,19 +1,12 @@ -use tokio::sync::oneshot; -use yazi_shared::event::CmdCow; +use anyhow::Result; +use yazi_macro::succ; +use yazi_parser::app::StopOpt; +use yazi_shared::event::Data; use crate::app::App; -struct Opt { - tx: Option>, -} - -impl From for Opt { - fn from(mut c: CmdCow) -> Self { Self { tx: c.take_any("tx") } } -} - impl App { - #[yazi_codegen::command] - pub fn stop(&mut self, opt: Opt) { + pub fn stop(&mut self, opt: StopOpt) -> Result { self.core.active_mut().preview.reset_image(); // We need to destroy the `term` first before stopping the `signals` @@ -22,5 +15,7 @@ impl App { self.term = None; self.signals.stop(opt.tx); + + succ!(); } } diff --git a/yazi-fm/src/app/commands/update_notify.rs b/yazi-fm/src/app/commands/update_notify.rs index e320b5ab..369f0d1c 100644 --- a/yazi-fm/src/app/commands/update_notify.rs +++ b/yazi-fm/src/app/commands/update_notify.rs @@ -1,21 +1,24 @@ +use anyhow::Result; use ratatui::layout::Rect; use yazi_adapter::Dimension; -use yazi_shared::event::CmdCow; +use yazi_macro::act; +use yazi_parser::notify::TickOpt; +use yazi_shared::event::Data; use crate::{app::App, notify}; impl App { - pub(crate) fn update_notify(&mut self, cmd: CmdCow) { + 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(cmd, area); + self.core.notify.tick(opt, area); if self.core.notify.messages.is_empty() { - self.render(); + act!(render, self) } else { - self.render_partially(); + act!(render_partially, self) } } } diff --git a/yazi-fm/src/app/commands/update_progress.rs b/yazi-fm/src/app/commands/update_progress.rs index 21372ecf..d6215422 100644 --- a/yazi-fm/src/app/commands/update_progress.rs +++ b/yazi-fm/src/app/commands/update_progress.rs @@ -1,25 +1,13 @@ -use yazi_core::tasks::TasksProgress; -use yazi_macro::render; -use yazi_shared::event::CmdCow; +use anyhow::Result; +use yazi_actor::Ctx; +use yazi_macro::{act, render, succ}; +use yazi_parser::app::UpdateProgressOpt; +use yazi_shared::event::Data; use crate::app::App; -pub struct Opt { - progress: TasksProgress, -} - -impl TryFrom for Opt { - type Error = (); - - fn try_from(mut c: CmdCow) -> Result { - Ok(Self { progress: c.take_any("progress").ok_or(())? }) - } -} - impl App { - pub(crate) fn update_progress(&mut self, opt: impl TryInto) { - let Ok(opt) = opt.try_into() else { return }; - + pub(crate) fn update_progress(&mut self, opt: UpdateProgressOpt) -> Result { // Update the progress of all tasks. let tasks = &mut self.core.tasks; let progressed = tasks.progress != opt.progress; @@ -30,16 +18,17 @@ impl App { let new = tasks.paginate(); if tasks.summaries != new { tasks.summaries = new; - tasks.arrow(0); - return render!(); + act!(tasks:arrow, &mut Ctx::active(&mut self.core))?; + succ!(render!()); } } if !progressed { + succ!() } else if tasks.progress.total == 0 { - render!(); + succ!(render!()) } else { - self.render_partially(); + act!(render_partially, self) } } } diff --git a/yazi-fm/src/dispatcher.rs b/yazi-fm/src/dispatcher.rs new file mode 100644 index 00000000..83025aba --- /dev/null +++ b/yazi-fm/src/dispatcher.rs @@ -0,0 +1,73 @@ +use std::sync::atomic::Ordering; + +use anyhow::Result; +use crossterm::event::KeyEvent; +use yazi_config::keymap::Key; +use yazi_macro::{act, emit, succ}; +use yazi_shared::event::{CmdCow, Data, Event, NEED_RENDER}; +use yazi_widgets::input::InputMode; + +use crate::{Executor, Router, app::App}; + +pub(super) struct Dispatcher<'a> { + app: &'a mut App, +} + +impl<'a> Dispatcher<'a> { + #[inline] + pub(super) fn new(app: &'a mut App) -> Self { Self { app } } + + #[inline] + pub(super) fn dispatch(&mut self, event: Event) -> Result<()> { + // FIXME: handle errors + _ = match event { + Event::Call(cmd) => self.dispatch_call(cmd), + Event::Seq(cmds) => self.dispatch_seq(cmds), + Event::Render => self.dispatch_render(), + Event::Key(key) => self.dispatch_key(key), + Event::Mouse(mouse) => act!(mouse, self.app, mouse), + Event::Resize => act!(resize, self.app), + Event::Paste(str) => self.dispatch_paste(str), + Event::Quit(opt) => act!(quit, self.app, opt), + }; + Ok(()) + } + + #[inline] + fn dispatch_call(&mut self, cmd: CmdCow) -> Result { Executor::new(self.app).execute(cmd) } + + #[inline] + fn dispatch_seq(&mut self, mut cmds: Vec) -> Result { + if let Some(last) = cmds.pop() { + self.dispatch_call(last)?; + } + if !cmds.is_empty() { + emit!(Seq(cmds)); + } + succ!(); + } + + #[inline] + fn dispatch_render(&mut self) -> Result { + succ!(NEED_RENDER.store(true, Ordering::Relaxed)) + } + + #[inline] + fn dispatch_key(&mut self, key: KeyEvent) -> Result { + Router::new(self.app).route(Key::from(key))?; + succ!(); + } + + #[inline] + fn dispatch_paste(&mut self, str: String) -> Result { + if self.app.core.input.visible { + let input = &mut self.app.core.input; + if input.mode() == InputMode::Insert { + input.type_str(&str)?; + } else if input.mode() == InputMode::Replace { + input.replace_str(&str)?; + } + } + succ!(); + } +} diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index b542d52d..72bb343f 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -1,4 +1,7 @@ -use yazi_shared::{Layer, event::CmdCow}; +use anyhow::Result; +use yazi_actor::Ctx; +use yazi_macro::{act, succ}; +use yazi_shared::{Layer, event::{CmdCow, Data}}; use yazi_widgets::input::InputMode; use crate::app::App; @@ -12,7 +15,7 @@ impl<'a> Executor<'a> { pub(super) fn new(app: &'a mut App) -> Self { Self { app } } #[inline] - pub(super) fn execute(&mut self, cmd: CmdCow) { + pub(super) fn execute(&mut self, cmd: CmdCow) -> Result { match cmd.layer { Layer::App => self.app(cmd), Layer::Mgr => self.mgr(cmd), @@ -27,11 +30,11 @@ impl<'a> Executor<'a> { } } - fn app(&mut self, cmd: CmdCow) { + fn app(&mut self, cmd: CmdCow) -> Result { macro_rules! on { ($name:ident) => { if cmd.name == stringify!($name) { - return self.app.$name(cmd); + return act!($name, self.app, cmd); } }; } @@ -45,116 +48,107 @@ impl<'a> Executor<'a> { on!(resize); on!(stop); on!(resume); + + succ!(); } - fn mgr(&mut self, cmd: CmdCow) { - macro_rules! on { - (MGR, $name:ident $(,$args:expr)*) => { - if cmd.name == stringify!($name) { - return self.app.core.mgr.$name(cmd, $($args),*); - } - }; - (ACTIVE, $name:ident $(,$args:expr)*) => { - if cmd.name == stringify!($name) { - return if let Some(tab) = cmd.get("tab") { - let Some(id) = tab.as_id() else { return }; - let Some(tab) = self.app.core.mgr.tabs.find_mut(id) else { return }; - tab.$name(cmd, $($args),*) - } else { - self.app.core.mgr.active_mut().$name(cmd, $($args),*) - }; - } - }; - (TABS, $name:ident) => { - if cmd.name == concat!("tab_", stringify!($name)) { - return self.app.core.mgr.tabs.$name(cmd); - } - }; - } + fn mgr(&mut self, cmd: CmdCow) -> Result { + let cx = &mut Ctx::new(&mut self.app.core, cmd.id("tab"))?; - on!(MGR, update_tasks); - on!(MGR, update_files, &self.app.core.tasks); - on!(MGR, update_mimes, &self.app.core.tasks); - on!(MGR, update_paged, &self.app.core.tasks); - on!(MGR, update_yanked); - on!(MGR, watch); - on!(MGR, peek); - on!(MGR, seek); - on!(MGR, spot); - on!(MGR, refresh, &self.app.core.tasks); - on!(MGR, quit, &self.app.core.tasks); - on!(MGR, close, &self.app.core.tasks); - on!(MGR, suspend); - on!(ACTIVE, escape); - on!(ACTIVE, update_peeked); - on!(ACTIVE, update_spotted); - - // Navigation - on!(ACTIVE, arrow); - on!(ACTIVE, leave); - on!(ACTIVE, enter); - on!(ACTIVE, back); - on!(ACTIVE, forward); - on!(ACTIVE, cd); - on!(ACTIVE, reveal); - on!(ACTIVE, follow); - - // Toggle - on!(ACTIVE, toggle); - on!(ACTIVE, toggle_all); - on!(ACTIVE, visual_mode); - - // Operation - on!(MGR, open, &self.app.core.tasks); - on!(MGR, open_do, &self.app.core.tasks); - on!(MGR, yank); - on!(MGR, unyank); - on!(MGR, paste, &self.app.core.tasks); - on!(MGR, link, &self.app.core.tasks); - on!(MGR, hardlink, &self.app.core.tasks); - on!(MGR, remove, &self.app.core.tasks); - on!(MGR, remove_do, &self.app.core.tasks); - on!(MGR, create); - on!(MGR, rename); - on!(ACTIVE, copy); - on!(ACTIVE, shell); - on!(ACTIVE, hidden); - on!(ACTIVE, linemode); - on!(ACTIVE, search); - on!(ACTIVE, search_do); - - // Filter - on!(ACTIVE, filter); - on!(ACTIVE, filter_do); - - // Find - on!(ACTIVE, find); - on!(ACTIVE, find_do); - on!(ACTIVE, find_arrow); - - // Sorting - on!(ACTIVE, sort, &self.app.core.tasks); - - // Tabs - on!(TABS, create); // TODO: use `tab_create` instead - on!(TABS, close); - on!(TABS, switch); - on!(TABS, swap); - - match cmd.name.as_ref() { - // Help - "help" => self.app.core.help.toggle(Layer::Mgr), - // Plugin - "plugin" => self.app.plugin(cmd), - _ => {} - } - } - - fn tasks(&mut self, cmd: CmdCow) { macro_rules! on { ($name:ident) => { if cmd.name == stringify!($name) { - return self.app.core.tasks.$name(cmd); + return act!(mgr:$name, cx, cmd); + } + }; + } + + on!(cd); + on!(update_tasks); + on!(update_yanked); + + on!(update_files); + on!(update_mimes); + on!(update_paged); + on!(watch); + on!(peek); + on!(seek); + on!(spot); + on!(refresh); + on!(quit); + on!(close); + on!(suspend); + on!(escape); + on!(update_peeked); + on!(update_spotted); + + // Navigation + on!(arrow); + on!(leave); + on!(enter); + on!(back); + on!(forward); + on!(reveal); + on!(follow); + + // Toggle + on!(toggle); + on!(toggle_all); + on!(visual_mode); + + // Operation + on!(open); + on!(open_do); + on!(yank); + on!(unyank); + on!(paste); + on!(link); + on!(hardlink); + on!(remove); + on!(remove_do); + on!(create); + on!(rename); + on!(copy); + on!(shell); + on!(hidden); + on!(linemode); + on!(search); + on!(search_do); + + // Filter + on!(filter); + on!(filter_do); + + // Find + on!(find); + on!(find_do); + on!(find_arrow); + + // Sorting + on!(sort); + + // Tabs + on!(tab_create); + on!(tab_close); + on!(tab_switch); + on!(tab_swap); + + match cmd.name.as_ref() { + // Help + "help" => act!(help:toggle, cx, Layer::Mgr), + // Plugin + "plugin" => act!(plugin, self.app, cmd), + _ => succ!(), + } + } + + fn tasks(&mut self, cmd: CmdCow) -> Result { + let cx = &mut Ctx::new(&mut self.app.core, cmd.id("tab"))?; + + macro_rules! on { + ($name:ident) => { + if cmd.name == stringify!($name) { + return act!(tasks:$name, cx, cmd); } }; } @@ -169,18 +163,20 @@ impl<'a> Executor<'a> { match cmd.name.as_ref() { // Help - "help" => self.app.core.help.toggle(Layer::Tasks), + "help" => act!(help:toggle, cx, Layer::Tasks), // Plugin - "plugin" => self.app.plugin(cmd), - _ => {} + "plugin" => act!(plugin, self.app, cmd), + _ => succ!(), } } - fn spot(&mut self, cmd: CmdCow) { + fn spot(&mut self, cmd: CmdCow) -> Result { + let cx = &mut Ctx::new(&mut self.app.core, cmd.id("tab"))?; + macro_rules! on { ($name:ident) => { if cmd.name == stringify!($name) { - return self.app.core.active_mut().spot.$name(cmd); + return act!(spot:$name, cx, cmd); } }; } @@ -192,18 +188,20 @@ impl<'a> Executor<'a> { match cmd.name.as_ref() { // Help - "help" => self.app.core.help.toggle(Layer::Spot), + "help" => act!(help:toggle, cx, Layer::Spot), // Plugin - "plugin" => self.app.plugin(cmd), - _ => {} + "plugin" => act!(plugin, self.app, cmd), + _ => succ!(), } } - fn pick(&mut self, cmd: CmdCow) { + fn pick(&mut self, cmd: CmdCow) -> Result { + let cx = &mut Ctx::new(&mut self.app.core, cmd.id("tab"))?; + macro_rules! on { ($name:ident) => { if cmd.name == stringify!($name) { - return self.app.core.pick.$name(cmd); + return act!(pick:$name, cx, cmd); } }; } @@ -214,18 +212,21 @@ impl<'a> Executor<'a> { match cmd.name.as_ref() { // Help - "help" => self.app.core.help.toggle(Layer::Pick), + "help" => act!(help:toggle, cx, Layer::Pick), // Plugin - "plugin" => self.app.plugin(cmd), - _ => {} + "plugin" => act!(plugin, self.app, cmd), + _ => succ!(), } } - fn input(&mut self, cmd: CmdCow) { + fn input(&mut self, cmd: CmdCow) -> Result { + let mode = self.app.core.input.mode(); + let cx = &mut Ctx::new(&mut self.app.core, cmd.id("tab"))?; + macro_rules! on { ($name:ident) => { if cmd.name == stringify!($name) { - return self.app.core.input.$name(cmd); + return act!(input:$name, cx, cmd); } }; } @@ -234,13 +235,13 @@ impl<'a> Executor<'a> { on!(show); on!(close); - match self.app.core.input.mode() { + match mode { InputMode::Normal => { match cmd.name.as_ref() { // Help - "help" => return self.app.core.help.toggle(Layer::Input), + "help" => return act!(help:toggle, cx, Layer::Input), // Plugin - "plugin" => return self.app.plugin(cmd), + "plugin" => return act!(plugin, self.app, cmd), _ => {} } } @@ -250,25 +251,31 @@ impl<'a> Executor<'a> { self.app.core.input.execute(cmd) } - fn confirm(&mut self, cmd: CmdCow) { + fn confirm(&mut self, cmd: CmdCow) -> Result { + let cx = &mut Ctx::new(&mut self.app.core, cmd.id("tab"))?; + macro_rules! on { - ($name:ident $(,$args:expr)*) => { + ($name:ident) => { if cmd.name == stringify!($name) { - return self.app.core.confirm.$name(cmd, $($args),*); + return act!(confirm:$name, cx, cmd); } }; } - on!(arrow, &self.app.core.mgr); + on!(arrow); on!(show); on!(close); + + succ!(); } - fn help(&mut self, cmd: CmdCow) { + fn help(&mut self, cmd: CmdCow) -> Result { + let cx = &mut Ctx::new(&mut self.app.core, cmd.id("tab"))?; + macro_rules! on { ($name:ident) => { if cmd.name == stringify!($name) { - return self.app.core.help.$name(cmd); + return act!(help:$name, cx, cmd); } }; } @@ -278,18 +285,21 @@ impl<'a> Executor<'a> { on!(filter); match cmd.name.as_ref() { - "close" => self.app.core.help.toggle(Layer::Help), + // Help + "close" => act!(help:toggle, cx, Layer::Help), // Plugin - "plugin" => self.app.plugin(cmd), - _ => {} + "plugin" => act!(plugin, self.app, cmd), + _ => succ!(), } } - fn cmp(&mut self, cmd: CmdCow) { + fn cmp(&mut self, cmd: CmdCow) -> Result { + let cx = &mut Ctx::new(&mut self.app.core, cmd.id("tab"))?; + macro_rules! on { ($name:ident) => { if cmd.name == stringify!($name) { - return self.app.core.cmp.$name(cmd); + return act!(cmp:$name, cx, cmd); } }; } @@ -301,23 +311,27 @@ impl<'a> Executor<'a> { match cmd.name.as_ref() { // Help - "help" => self.app.core.help.toggle(Layer::Cmp), + "help" => act!(help:toggle, cx, Layer::Cmp), // Plugin - "plugin" => self.app.plugin(cmd), - _ => {} + "plugin" => act!(plugin, self.app, cmd), + _ => succ!(), } } - fn which(&mut self, cmd: CmdCow) { + fn which(&mut self, cmd: CmdCow) -> Result { + let cx = &mut Ctx::new(&mut self.app.core, cmd.id("tab"))?; + macro_rules! on { ($name:ident) => { if cmd.name == stringify!($name) { - return self.app.core.which.$name(cmd); + return act!(which:$name, cx, cmd); } }; } on!(show); on!(callback); + + succ!(); } } diff --git a/yazi-fm/src/main.rs b/yazi-fm/src/main.rs index 0419a6c9..ff65fe81 100644 --- a/yazi-fm/src/main.rs +++ b/yazi-fm/src/main.rs @@ -6,7 +6,7 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; yazi_macro::mod_pub!(app cmp confirm help input lives mgr notify pick spot tasks which); -yazi_macro::mod_flat!(executor logs panic root router signals term); +yazi_macro::mod_flat!(dispatcher executor logs panic root router signals term); #[tokio::main] async fn main() -> anyhow::Result<()> { diff --git a/yazi-fm/src/router.rs b/yazi-fm/src/router.rs index 2480c675..900a593c 100644 --- a/yazi-fm/src/router.rs +++ b/yazi-fm/src/router.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use yazi_config::{KEYMAP, keymap::{Chord, ChordCow, Key}}; use yazi_macro::emit; use yazi_shared::Layer; @@ -13,26 +14,26 @@ impl<'a> Router<'a> { pub(super) fn new(app: &'a mut App) -> Self { Self { app } } #[inline] - pub(super) fn route(&mut self, key: Key) -> bool { + pub(super) fn route(&mut self, key: Key) -> Result { let core = &mut self.app.core; let layer = core.layer(); - if core.help.visible && core.help.r#type(&key) { - return true; + if core.help.visible && core.help.r#type(&key)? { + return Ok(true); } - if core.input.visible && core.input.r#type(&key) { - return true; + if core.input.visible && core.input.r#type(&key)? { + return Ok(true); } use Layer as L; - match layer { + Ok(match layer { L::App => unreachable!(), L::Mgr | L::Tasks | L::Spot | L::Pick | L::Input | L::Confirm | L::Help => { self.matches(layer, key) } L::Cmp => self.matches(L::Cmp, key) || self.matches(L::Input, key), L::Which => core.which.r#type(key), - } + }) } #[inline] diff --git a/yazi-macro/src/actor.rs b/yazi-macro/src/actor.rs new file mode 100644 index 00000000..157ecf47 --- /dev/null +++ b/yazi-macro/src/actor.rs @@ -0,0 +1,19 @@ +#[macro_export] +macro_rules! act { + ($layer:ident : $name:ident, $cx:expr, $cmd:expr) => {{ + #[allow(unused_imports)] + use ::yazi_actor::Actor; + paste::paste! { ::yazi_actor::$layer::[<$name:camel>]::act($cx, $cmd.try_into()?) } + }}; + ($layer:ident : $name:ident, $cx:expr) => {{ + #[allow(unused_imports)] + use yazi_actor::Actor; + paste::paste! { yazi_actor::$layer::[<$name:camel>]::act($cx, Default::default()) } + }}; + ($name:ident, $cx:expr, $cmd:expr) => { + $cx.$name($cmd.try_into()?) + }; + ($name:ident, $cx:expr) => { + $cx.$name(Default::default()) + }; +} diff --git a/yazi-macro/src/event.rs b/yazi-macro/src/event.rs index 745cdf42..06d50ff9 100644 --- a/yazi-macro/src/event.rs +++ b/yazi-macro/src/event.rs @@ -14,6 +14,16 @@ macro_rules! emit { }; } +#[macro_export] +macro_rules! succ { + ($data:expr) => { + return Ok(yazi_shared::event::Data::from($data)) + }; + () => { + return Ok(yazi_shared::event::Data::Nil) + }; +} + #[macro_export] macro_rules! render { () => { diff --git a/yazi-macro/src/lib.rs b/yazi-macro/src/lib.rs index ed825ad7..90fc0cb9 100644 --- a/yazi-macro/src/lib.rs +++ b/yazi-macro/src/lib.rs @@ -1,3 +1,4 @@ +mod actor; mod asset; mod event; mod fmt; diff --git a/yazi-packing/src/lib.rs b/yazi-packing/src/lib.rs index e69de29b..8b137891 100644 --- a/yazi-packing/src/lib.rs +++ b/yazi-packing/src/lib.rs @@ -0,0 +1 @@ + diff --git a/yazi-parser/Cargo.toml b/yazi-parser/Cargo.toml index c6f3ab57..29025855 100644 --- a/yazi-parser/Cargo.toml +++ b/yazi-parser/Cargo.toml @@ -17,10 +17,11 @@ yazi-fs = { path = "../yazi-fs", version = "25.6.11" } yazi-macro = { path = "../yazi-macro", version = "25.6.11" } yazi-proxy = { path = "../yazi-proxy", version = "25.6.11" } yazi-shared = { path = "../yazi-shared", version = "25.6.11" } -yazi-widgets = { path = "../yazi-widgets", version = "25.6.11" } # External dependencies -anyhow = { workspace = true } -bitflags = { workspace = true } -mlua = { workspace = true } -tokio = { workspace = true } +anyhow = { workspace = true } +bitflags = { workspace = true } +crossterm = { workspace = true } +mlua = { workspace = true } +serde = { workspace = true } +tokio = { workspace = true } diff --git a/yazi-parser/src/app/accept_payload.rs b/yazi-parser/src/app/accept_payload.rs new file mode 100644 index 00000000..b25dfafc --- /dev/null +++ b/yazi-parser/src/app/accept_payload.rs @@ -0,0 +1,19 @@ +use anyhow::bail; +use yazi_dds::Payload; +use yazi_shared::event::CmdCow; + +pub struct AcceptPayload { + pub payload: Payload<'static>, +} + +impl TryFrom for AcceptPayload { + type Error = anyhow::Error; + + fn try_from(mut c: CmdCow) -> Result { + let Some(payload) = c.take_any("payload") else { + bail!("Invalid 'payload' in AcceptPayload"); + }; + + Ok(Self { payload }) + } +} diff --git a/yazi-parser/src/app/mod.rs b/yazi-parser/src/app/mod.rs new file mode 100644 index 00000000..b021820b --- /dev/null +++ b/yazi-parser/src/app/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(accept_payload mouse stop update_progress); diff --git a/yazi-parser/src/app/mouse.rs b/yazi-parser/src/app/mouse.rs new file mode 100644 index 00000000..0ce32d04 --- /dev/null +++ b/yazi-parser/src/app/mouse.rs @@ -0,0 +1,9 @@ +use crossterm::event::MouseEvent; + +pub struct MouseOpt { + pub event: MouseEvent, +} + +impl From for MouseOpt { + fn from(event: MouseEvent) -> Self { Self { event } } +} diff --git a/yazi-parser/src/app/stop.rs b/yazi-parser/src/app/stop.rs new file mode 100644 index 00000000..5ccc91d3 --- /dev/null +++ b/yazi-parser/src/app/stop.rs @@ -0,0 +1,10 @@ +use tokio::sync::oneshot; +use yazi_shared::event::CmdCow; + +pub struct StopOpt { + pub tx: Option>, +} + +impl From for StopOpt { + fn from(mut c: CmdCow) -> Self { Self { tx: c.take_any("tx") } } +} diff --git a/yazi-parser/src/app/update_progress.rs b/yazi-parser/src/app/update_progress.rs new file mode 100644 index 00000000..a8096d84 --- /dev/null +++ b/yazi-parser/src/app/update_progress.rs @@ -0,0 +1,30 @@ +use anyhow::bail; +use serde::Serialize; +use yazi_shared::event::CmdCow; + +pub struct UpdateProgressOpt { + pub progress: TasksProgress, +} + +impl TryFrom for UpdateProgressOpt { + type Error = anyhow::Error; + + fn try_from(mut c: CmdCow) -> Result { + let Some(progress) = c.take_any("progress") else { + bail!("Invalid 'progress' in UpdateProgressOpt"); + }; + + Ok(Self { progress }) + } +} + +// --- Progress +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] +pub struct TasksProgress { + pub total: u32, + pub succ: u32, + pub fail: u32, + + pub found: u64, + pub processed: u64, +} diff --git a/yazi-parser/src/arrow.rs b/yazi-parser/src/arrow.rs new file mode 100644 index 00000000..48f121b9 --- /dev/null +++ b/yazi-parser/src/arrow.rs @@ -0,0 +1,25 @@ +use anyhow::bail; +use yazi_shared::event::CmdCow; + +use crate::Step; + +#[derive(Default)] +pub struct ArrowOpt { + pub step: Step, +} + +impl TryFrom for ArrowOpt { + type Error = anyhow::Error; + + fn try_from(c: CmdCow) -> Result { + let Some(step) = c.first() else { + bail!("'step' is required for ArrowOpt"); + }; + + Ok(Self { step: step.try_into()? }) + } +} + +impl From for ArrowOpt { + fn from(n: isize) -> Self { Self { step: n.into() } } +} diff --git a/yazi-parser/src/cmp/arrow.rs b/yazi-parser/src/cmp/arrow.rs deleted file mode 100644 index c54d5acc..00000000 --- a/yazi-parser/src/cmp/arrow.rs +++ /dev/null @@ -1,12 +0,0 @@ -use yazi_shared::event::CmdCow; -use yazi_widgets::Step; - -pub struct ArrowOpt { - pub step: Step, -} - -impl From for ArrowOpt { - fn from(c: CmdCow) -> Self { - Self { step: c.first().and_then(|d| d.try_into().ok()).unwrap_or_default() } - } -} diff --git a/yazi-parser/src/cmp/close.rs b/yazi-parser/src/cmp/close.rs index fa516687..a0da370d 100644 --- a/yazi-parser/src/cmp/close.rs +++ b/yazi-parser/src/cmp/close.rs @@ -1,5 +1,6 @@ use yazi_shared::event::CmdCow; +#[derive(Default)] pub struct CloseOpt { pub submit: bool, } diff --git a/yazi-parser/src/cmp/mod.rs b/yazi-parser/src/cmp/mod.rs index e1a77788..81bf7af6 100644 --- a/yazi-parser/src/cmp/mod.rs +++ b/yazi-parser/src/cmp/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(arrow close show trigger); +yazi_macro::mod_flat!(close show trigger); diff --git a/yazi-parser/src/cmp/show.rs b/yazi-parser/src/cmp/show.rs index 248048af..c67d69f9 100644 --- a/yazi-parser/src/cmp/show.rs +++ b/yazi-parser/src/cmp/show.rs @@ -1,8 +1,9 @@ use std::path::PathBuf; use yazi_proxy::options::CmpItem; -use yazi_shared::{Id, SStr, event::{Cmd, CmdCow, Data}}; +use yazi_shared::{Id, SStr, event::{Cmd, CmdCow}}; +#[derive(Default)] pub struct ShowOpt { pub cache: Vec, pub cache_name: PathBuf, @@ -16,7 +17,7 @@ impl From for ShowOpt { cache: c.take_any("cache").unwrap_or_default(), cache_name: c.take_any("cache-name").unwrap_or_default(), word: c.take_str("word").unwrap_or_default(), - ticket: c.get("ticket").and_then(Data::as_id).unwrap_or_default(), + ticket: c.id("ticket").unwrap_or_default(), } } } diff --git a/yazi-parser/src/cmp/trigger.rs b/yazi-parser/src/cmp/trigger.rs index 72a55a36..df9087b0 100644 --- a/yazi-parser/src/cmp/trigger.rs +++ b/yazi-parser/src/cmp/trigger.rs @@ -1,4 +1,4 @@ -use yazi_shared::{Id, SStr, event::{CmdCow, Data}}; +use yazi_shared::{Id, SStr, event::CmdCow}; pub struct TriggerOpt { pub word: SStr, @@ -7,9 +7,6 @@ pub struct TriggerOpt { impl From for TriggerOpt { fn from(mut c: CmdCow) -> Self { - Self { - word: c.take_first_str().unwrap_or_default(), - ticket: c.get("ticket").and_then(Data::as_id), - } + Self { word: c.take_first_str().unwrap_or_default(), ticket: c.id("ticket") } } } diff --git a/yazi-parser/src/confirm/arrow.rs b/yazi-parser/src/confirm/arrow.rs deleted file mode 100644 index c54d5acc..00000000 --- a/yazi-parser/src/confirm/arrow.rs +++ /dev/null @@ -1,12 +0,0 @@ -use yazi_shared::event::CmdCow; -use yazi_widgets::Step; - -pub struct ArrowOpt { - pub step: Step, -} - -impl From for ArrowOpt { - fn from(c: CmdCow) -> Self { - Self { step: c.first().and_then(|d| d.try_into().ok()).unwrap_or_default() } - } -} diff --git a/yazi-parser/src/confirm/close.rs b/yazi-parser/src/confirm/close.rs index fa516687..a0da370d 100644 --- a/yazi-parser/src/confirm/close.rs +++ b/yazi-parser/src/confirm/close.rs @@ -1,5 +1,6 @@ use yazi_shared::event::CmdCow; +#[derive(Default)] pub struct CloseOpt { pub submit: bool, } diff --git a/yazi-parser/src/confirm/mod.rs b/yazi-parser/src/confirm/mod.rs index e0fc92ad..37e61514 100644 --- a/yazi-parser/src/confirm/mod.rs +++ b/yazi-parser/src/confirm/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(arrow close show); +yazi_macro::mod_flat!(close show); diff --git a/yazi-parser/src/confirm/show.rs b/yazi-parser/src/confirm/show.rs index 3eb3ada7..6cc804c5 100644 --- a/yazi-parser/src/confirm/show.rs +++ b/yazi-parser/src/confirm/show.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use tokio::sync::oneshot; use yazi_config::popup::ConfirmCfg; use yazi_shared::event::CmdCow; @@ -8,9 +9,17 @@ pub struct ShowOpt { } impl TryFrom for ShowOpt { - type Error = (); + type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { - Ok(Self { cfg: c.take_any("cfg").ok_or(())?, tx: c.take_any("tx").ok_or(())? }) + let Some(cfg) = c.take_any("cfg") else { + bail!("Invalid 'cfg' argument in ShowOpt"); + }; + + let Some(tx) = c.take_any("tx") else { + bail!("Invalid 'tx' argument in ShowOpt"); + }; + + Ok(Self { cfg, tx }) } } diff --git a/yazi-parser/src/help/arrow.rs b/yazi-parser/src/help/arrow.rs deleted file mode 100644 index 29a1e04d..00000000 --- a/yazi-parser/src/help/arrow.rs +++ /dev/null @@ -1,16 +0,0 @@ -use yazi_shared::event::CmdCow; -use yazi_widgets::Step; - -pub struct ArrowOpt { - pub step: Step, -} - -impl From for ArrowOpt { - fn from(c: CmdCow) -> Self { - Self { step: c.first().and_then(|d| d.try_into().ok()).unwrap_or_default() } - } -} - -impl From for ArrowOpt { - fn from(n: isize) -> Self { Self { step: n.into() } } -} diff --git a/yazi-parser/src/help/escape.rs b/yazi-parser/src/help/escape.rs index e69de29b..8b137891 100644 --- a/yazi-parser/src/help/escape.rs +++ b/yazi-parser/src/help/escape.rs @@ -0,0 +1 @@ + diff --git a/yazi-parser/src/help/filter.rs b/yazi-parser/src/help/filter.rs index e69de29b..8b137891 100644 --- a/yazi-parser/src/help/filter.rs +++ b/yazi-parser/src/help/filter.rs @@ -0,0 +1 @@ + diff --git a/yazi-parser/src/help/mod.rs b/yazi-parser/src/help/mod.rs index 11f3a072..50427171 100644 --- a/yazi-parser/src/help/mod.rs +++ b/yazi-parser/src/help/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(arrow escape filter); +yazi_macro::mod_flat!(escape filter toggle); diff --git a/yazi-parser/src/help/toggle.rs b/yazi-parser/src/help/toggle.rs new file mode 100644 index 00000000..7278429f --- /dev/null +++ b/yazi-parser/src/help/toggle.rs @@ -0,0 +1,22 @@ +use anyhow::bail; +use yazi_shared::{Layer, event::CmdCow}; + +pub struct ToggleOpt { + pub layer: Layer, +} + +impl TryFrom for ToggleOpt { + type Error = anyhow::Error; + + fn try_from(c: CmdCow) -> Result { + let Some(layer) = c.first_str() else { + bail!("Invalid 'layer' in Toggle"); + }; + + Ok(Self { layer: layer.parse()? }) + } +} + +impl From for ToggleOpt { + fn from(layer: Layer) -> Self { Self { layer } } +} diff --git a/yazi-parser/src/input/backspace.rs b/yazi-parser/src/input/backspace.rs new file mode 100644 index 00000000..518597fb --- /dev/null +++ b/yazi-parser/src/input/backspace.rs @@ -0,0 +1,14 @@ +use yazi_shared::event::CmdCow; + +#[derive(Default)] +pub struct BackspaceOpt { + pub under: bool, +} + +impl From for BackspaceOpt { + fn from(c: CmdCow) -> Self { Self { under: c.bool("under") } } +} + +impl From for BackspaceOpt { + fn from(under: bool) -> Self { Self { under } } +} diff --git a/yazi-parser/src/input/backward.rs b/yazi-parser/src/input/backward.rs new file mode 100644 index 00000000..d4a40f91 --- /dev/null +++ b/yazi-parser/src/input/backward.rs @@ -0,0 +1,9 @@ +use yazi_shared::event::CmdCow; + +pub struct BackwardOpt { + pub far: bool, +} + +impl From for BackwardOpt { + fn from(c: CmdCow) -> Self { Self { far: c.bool("far") } } +} diff --git a/yazi-parser/src/input/close.rs b/yazi-parser/src/input/close.rs index fa516687..a0da370d 100644 --- a/yazi-parser/src/input/close.rs +++ b/yazi-parser/src/input/close.rs @@ -1,5 +1,6 @@ use yazi_shared::event::CmdCow; +#[derive(Default)] pub struct CloseOpt { pub submit: bool, } diff --git a/yazi-parser/src/input/complete.rs b/yazi-parser/src/input/complete.rs new file mode 100644 index 00000000..6792a761 --- /dev/null +++ b/yazi-parser/src/input/complete.rs @@ -0,0 +1,20 @@ +use anyhow::bail; +use yazi_proxy::options::CmpItem; +use yazi_shared::{Id, event::CmdCow}; + +pub struct CompleteOpt { + pub item: CmpItem, + pub _ticket: Id, // FIXME: not used +} + +impl TryFrom for CompleteOpt { + type Error = anyhow::Error; + + fn try_from(mut c: CmdCow) -> Result { + let Some(item) = c.take_any("item") else { + bail!("Invalid 'item' in CompleteOpt"); + }; + + Ok(Self { item, _ticket: c.id("ticket").unwrap_or_default() }) + } +} diff --git a/yazi-parser/src/input/delete.rs b/yazi-parser/src/input/delete.rs new file mode 100644 index 00000000..52035735 --- /dev/null +++ b/yazi-parser/src/input/delete.rs @@ -0,0 +1,10 @@ +use yazi_shared::event::CmdCow; + +pub struct DeleteOpt { + pub cut: bool, + pub insert: bool, +} + +impl From for DeleteOpt { + fn from(c: CmdCow) -> Self { Self { cut: c.bool("cut"), insert: c.bool("insert") } } +} diff --git a/yazi-parser/src/input/escape.rs b/yazi-parser/src/input/escape.rs deleted file mode 100644 index eae3a61a..00000000 --- a/yazi-parser/src/input/escape.rs +++ /dev/null @@ -1 +0,0 @@ -pub type EscapeOpt = crate::VoidOpt; diff --git a/yazi-parser/src/input/insert.rs b/yazi-parser/src/input/insert.rs new file mode 100644 index 00000000..5db0902e --- /dev/null +++ b/yazi-parser/src/input/insert.rs @@ -0,0 +1,13 @@ +use yazi_shared::event::CmdCow; + +pub struct InsertOpt { + pub append: bool, +} + +impl From for InsertOpt { + fn from(c: CmdCow) -> Self { Self { append: c.bool("append") } } +} + +impl From for InsertOpt { + fn from(append: bool) -> Self { Self { append } } +} diff --git a/yazi-parser/src/input/kill.rs b/yazi-parser/src/input/kill.rs new file mode 100644 index 00000000..1592975f --- /dev/null +++ b/yazi-parser/src/input/kill.rs @@ -0,0 +1,9 @@ +use yazi_shared::{SStr, event::CmdCow}; + +pub struct KillOpt { + pub kind: SStr, +} + +impl From for KillOpt { + fn from(mut c: CmdCow) -> Self { Self { kind: c.take_first_str().unwrap_or_default() } } +} diff --git a/yazi-parser/src/input/mod.rs b/yazi-parser/src/input/mod.rs index d23f3447..344a371e 100644 --- a/yazi-parser/src/input/mod.rs +++ b/yazi-parser/src/input/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(close escape show); +yazi_macro::mod_flat!(backspace backward close complete delete insert kill r#move paste show); diff --git a/yazi-parser/src/input/move.rs b/yazi-parser/src/input/move.rs new file mode 100644 index 00000000..722ab63b --- /dev/null +++ b/yazi-parser/src/input/move.rs @@ -0,0 +1,78 @@ +use std::{num::ParseIntError, str::FromStr}; + +use anyhow::bail; +use yazi_shared::event::{CmdCow, Data}; + +#[derive(Default)] +pub struct MoveOpt { + pub step: MoveOptStep, + pub in_operating: bool, +} + +impl From for MoveOpt { + fn from(c: CmdCow) -> Self { + Self { + step: c.first().and_then(|d| d.try_into().ok()).unwrap_or_default(), + in_operating: c.bool("in-operating"), + } + } +} + +impl From for MoveOpt { + fn from(step: isize) -> Self { Self { step: step.into(), in_operating: false } } +} + +// --- Step +pub enum MoveOptStep { + Offset(isize), + Bol, + Eol, + FirstChar, +} + +impl MoveOptStep { + pub fn add(self, s: &str, cursor: usize) -> usize { + match self { + Self::Offset(n) if n <= 0 => cursor.saturating_add_signed(n), + Self::Offset(n) => s.chars().count().min(cursor + n as usize), + Self::Bol => 0, + Self::Eol => s.chars().count(), + Self::FirstChar => { + s.chars().enumerate().find(|(_, c)| !c.is_whitespace()).map_or(0, |(i, _)| i) + } + } + } +} + +impl Default for MoveOptStep { + fn default() -> Self { 0.into() } +} + +impl FromStr for MoveOptStep { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + Ok(match s { + "bol" => Self::Bol, + "eol" => Self::Eol, + "first-char" => Self::FirstChar, + s => Self::Offset(s.parse()?), + }) + } +} + +impl From for MoveOptStep { + fn from(value: isize) -> Self { Self::Offset(value) } +} + +impl TryFrom<&Data> for MoveOptStep { + type Error = anyhow::Error; + + fn try_from(value: &Data) -> Result { + Ok(match value { + Data::String(s) => s.parse()?, + Data::Integer(i) => Self::from(*i as isize), + _ => bail!("Invalid MoveOptStep data type: {value:?}"), + }) + } +} diff --git a/yazi-parser/src/input/paste.rs b/yazi-parser/src/input/paste.rs new file mode 100644 index 00000000..095a242c --- /dev/null +++ b/yazi-parser/src/input/paste.rs @@ -0,0 +1,9 @@ +use yazi_shared::event::CmdCow; + +pub struct PasteOpt { + pub before: bool, +} + +impl From for PasteOpt { + fn from(c: CmdCow) -> Self { Self { before: c.bool("before") } } +} diff --git a/yazi-parser/src/input/show.rs b/yazi-parser/src/input/show.rs index 4f7fe9a9..707176ec 100644 --- a/yazi-parser/src/input/show.rs +++ b/yazi-parser/src/input/show.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use tokio::sync::mpsc; use yazi_config::popup::InputCfg; use yazi_shared::{errors::InputError, event::CmdCow}; @@ -8,9 +9,17 @@ pub struct ShowOpt { } impl TryFrom for ShowOpt { - type Error = (); + type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { - Ok(Self { cfg: c.take_any("cfg").ok_or(())?, tx: c.take_any("tx").ok_or(())? }) + let Some(cfg) = c.take_any("cfg") else { + bail!("Invalid 'cfg' argument in ShowOpt"); + }; + + let Some(tx) = c.take_any("tx") else { + bail!("Invalid 'tx' argument in ShowOpt"); + }; + + Ok(Self { cfg, tx }) } } diff --git a/yazi-parser/src/lib.rs b/yazi-parser/src/lib.rs index 6806895d..fdf785c7 100644 --- a/yazi-parser/src/lib.rs +++ b/yazi-parser/src/lib.rs @@ -1,3 +1,3 @@ -yazi_macro::mod_pub!(cmp confirm help input mgr notify pick spot tab tasks which); +yazi_macro::mod_pub!(app cmp confirm help input mgr notify pick spot tab tasks which); -yazi_macro::mod_flat!(void); +yazi_macro::mod_flat!(arrow step void); diff --git a/yazi-parser/src/mgr/hidden.rs b/yazi-parser/src/mgr/hidden.rs new file mode 100644 index 00000000..aba2adcd --- /dev/null +++ b/yazi-parser/src/mgr/hidden.rs @@ -0,0 +1,17 @@ +use yazi_shared::event::CmdCow; + +pub struct HiddenOpt { + pub state: Option, +} + +impl From for HiddenOpt { + fn from(c: CmdCow) -> Self { + let state = match c.first_str() { + Some("show") => Some(true), + Some("hide") => Some(false), + _ => None, + }; + + Self { state } + } +} diff --git a/yazi-parser/src/mgr/linemode.rs b/yazi-parser/src/mgr/linemode.rs new file mode 100644 index 00000000..78f33935 --- /dev/null +++ b/yazi-parser/src/mgr/linemode.rs @@ -0,0 +1,22 @@ +use anyhow::bail; +use yazi_shared::{SStr, event::CmdCow}; + +pub struct LinemodeOpt { + pub new: SStr, +} + +impl TryFrom for LinemodeOpt { + type Error = anyhow::Error; + + fn try_from(mut c: CmdCow) -> Result { + let Some(new) = c.take_first_str() else { + bail!("a string argument is required for LinemodeOpt"); + }; + + if new.is_empty() || new.len() > 20 { + bail!("Linemode must be between 1 and 20 characters long"); + } + + Ok(Self { new }) + } +} diff --git a/yazi-parser/src/mgr/mod.rs b/yazi-parser/src/mgr/mod.rs index c62d4b86..bf7d3bc9 100644 --- a/yazi-parser/src/mgr/mod.rs +++ b/yazi-parser/src/mgr/mod.rs @@ -1 +1,25 @@ -yazi_macro::mod_flat!(close create hardlink link open paste peek quit remove rename seek spot tab_close tab_create tab_swap tab_switch unyank update_files update_mimes update_paged update_tasks update_yanked watch yank); +yazi_macro::mod_flat!( + close + create + hardlink + hidden + linemode + link + open + paste + peek + quit + remove + rename + seek + spot + tab_close + tab_create + tab_switch + update_files + update_mimes + update_paged + update_tasks + update_yanked + yank +); diff --git a/yazi-parser/src/mgr/spot.rs b/yazi-parser/src/mgr/spot.rs index 6618e84d..22fc421a 100644 --- a/yazi-parser/src/mgr/spot.rs +++ b/yazi-parser/src/mgr/spot.rs @@ -1,5 +1,6 @@ use yazi_shared::event::{CmdCow, Data}; +#[derive(Default)] pub struct SpotOpt { pub skip: Option, } @@ -7,3 +8,7 @@ pub struct SpotOpt { impl From for SpotOpt { fn from(c: CmdCow) -> Self { Self { skip: c.get("skip").and_then(Data::as_usize) } } } + +impl From for SpotOpt { + fn from(skip: usize) -> Self { Self { skip: Some(skip) } } +} diff --git a/yazi-parser/src/mgr/tab_swap.rs b/yazi-parser/src/mgr/tab_swap.rs deleted file mode 100644 index 49e71df8..00000000 --- a/yazi-parser/src/mgr/tab_swap.rs +++ /dev/null @@ -1,9 +0,0 @@ -use yazi_shared::event::{CmdCow, Data}; - -pub struct TabSwapOpt { - pub step: isize, -} - -impl From for TabSwapOpt { - fn from(c: CmdCow) -> Self { Self { step: c.first().and_then(Data::as_isize).unwrap_or(0) } } -} diff --git a/yazi-parser/src/mgr/unyank.rs b/yazi-parser/src/mgr/unyank.rs deleted file mode 100644 index b2575fae..00000000 --- a/yazi-parser/src/mgr/unyank.rs +++ /dev/null @@ -1 +0,0 @@ -pub type UnyankOpt = crate::VoidOpt; diff --git a/yazi-parser/src/mgr/update_files.rs b/yazi-parser/src/mgr/update_files.rs index c4c7dfb1..5332e610 100644 --- a/yazi-parser/src/mgr/update_files.rs +++ b/yazi-parser/src/mgr/update_files.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use yazi_fs::FilesOp; use yazi_shared::event::CmdCow; @@ -6,9 +7,13 @@ pub struct UpdateFilesOpt { } impl TryFrom for UpdateFilesOpt { - type Error = (); + type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { - Ok(Self { op: c.take_any("op").ok_or(())? }) + let Some(op) = c.take_any("op") else { + bail!("Invalid 'op' argument in UpdateFilesOpt"); + }; + + Ok(Self { op }) } } diff --git a/yazi-parser/src/mgr/update_mimes.rs b/yazi-parser/src/mgr/update_mimes.rs index 637d1f6e..dac6e54d 100644 --- a/yazi-parser/src/mgr/update_mimes.rs +++ b/yazi-parser/src/mgr/update_mimes.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use anyhow::bail; use yazi_shared::event::{CmdCow, Data, DataKey}; pub struct UpdateMimesOpt { @@ -7,9 +8,13 @@ pub struct UpdateMimesOpt { } impl TryFrom for UpdateMimesOpt { - type Error = (); + type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { - Ok(Self { updates: c.try_take("updates").and_then(Data::into_dict).ok_or(())? }) + let Some(updates) = c.try_take("updates").and_then(Data::into_dict) else { + bail!("Invalid 'updates' argument in UpdateMimesOpt"); + }; + + Ok(Self { updates }) } } diff --git a/yazi-parser/src/mgr/update_tasks.rs b/yazi-parser/src/mgr/update_tasks.rs index c86e29f6..ad7d6c87 100644 --- a/yazi-parser/src/mgr/update_tasks.rs +++ b/yazi-parser/src/mgr/update_tasks.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use yazi_shared::{event::CmdCow, url::Url}; pub struct UpdateTasksOpt { @@ -5,9 +6,13 @@ pub struct UpdateTasksOpt { } impl TryFrom for UpdateTasksOpt { - type Error = (); + type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { - Ok(Self { urls: c.take_any("urls").ok_or(())? }) + let Some(urls) = c.take_any("urls") else { + bail!("Invalid 'urls' argument in UpdateTasksOpt"); + }; + + Ok(Self { urls }) } } diff --git a/yazi-parser/src/mgr/update_yanked.rs b/yazi-parser/src/mgr/update_yanked.rs index a1026b29..12088c9c 100644 --- a/yazi-parser/src/mgr/update_yanked.rs +++ b/yazi-parser/src/mgr/update_yanked.rs @@ -1,5 +1,6 @@ use std::collections::HashSet; +use anyhow::bail; use yazi_shared::{event::CmdCow, url::Url}; #[derive(Default)] @@ -9,14 +10,14 @@ pub struct UpdateYankedOpt { } impl TryFrom for UpdateYankedOpt { - type Error = (); + type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { // TODO: remove `BodyYankIter` if let Some(iter) = c.take_any::("urls") { Ok(Self { urls: iter.urls.into_iter().collect(), cut: iter.cut }) } else { - Err(()) + bail!("Invalid 'urls' argument in UpdateYankedOpt"); } } } diff --git a/yazi-parser/src/mgr/watch.rs b/yazi-parser/src/mgr/watch.rs deleted file mode 100644 index a4217560..00000000 --- a/yazi-parser/src/mgr/watch.rs +++ /dev/null @@ -1 +0,0 @@ -pub type WatchOpt = crate::VoidOpt; diff --git a/yazi-parser/src/notify/push.rs b/yazi-parser/src/notify/push.rs index e69de29b..8b137891 100644 --- a/yazi-parser/src/notify/push.rs +++ b/yazi-parser/src/notify/push.rs @@ -0,0 +1 @@ + diff --git a/yazi-parser/src/notify/tick.rs b/yazi-parser/src/notify/tick.rs index f7d97951..1b9070cf 100644 --- a/yazi-parser/src/notify/tick.rs +++ b/yazi-parser/src/notify/tick.rs @@ -1,26 +1,24 @@ use std::time::Duration; -use yazi_shared::event::{Cmd, CmdCow, Data}; +use anyhow::bail; +use yazi_shared::event::{CmdCow, Data}; pub struct TickOpt { pub interval: Duration, } impl TryFrom for TickOpt { - type Error = (); + type Error = anyhow::Error; fn try_from(c: CmdCow) -> Result { - let interval = c.first().and_then(Data::as_f64).ok_or(())?; + let Some(interval) = c.first().and_then(Data::as_f64) else { + bail!("Invalid 'interval' argument in TickOpt"); + }; + if interval < 0.0 { - return Err(()); + bail!("'interval' must be non-negative in TickOpt"); } Ok(Self { interval: Duration::from_secs_f64(interval) }) } } - -impl TryFrom for TickOpt { - type Error = (); - - fn try_from(c: Cmd) -> Result { Self::try_from(CmdCow::from(c)) } -} diff --git a/yazi-parser/src/pick/arrow.rs b/yazi-parser/src/pick/arrow.rs deleted file mode 100644 index c54d5acc..00000000 --- a/yazi-parser/src/pick/arrow.rs +++ /dev/null @@ -1,12 +0,0 @@ -use yazi_shared::event::CmdCow; -use yazi_widgets::Step; - -pub struct ArrowOpt { - pub step: Step, -} - -impl From for ArrowOpt { - fn from(c: CmdCow) -> Self { - Self { step: c.first().and_then(|d| d.try_into().ok()).unwrap_or_default() } - } -} diff --git a/yazi-parser/src/pick/close.rs b/yazi-parser/src/pick/close.rs index fa516687..a0da370d 100644 --- a/yazi-parser/src/pick/close.rs +++ b/yazi-parser/src/pick/close.rs @@ -1,5 +1,6 @@ use yazi_shared::event::CmdCow; +#[derive(Default)] pub struct CloseOpt { pub submit: bool, } diff --git a/yazi-parser/src/pick/mod.rs b/yazi-parser/src/pick/mod.rs index e0fc92ad..37e61514 100644 --- a/yazi-parser/src/pick/mod.rs +++ b/yazi-parser/src/pick/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(arrow close show); +yazi_macro::mod_flat!(close show); diff --git a/yazi-parser/src/pick/show.rs b/yazi-parser/src/pick/show.rs index 8961bd4e..9da97379 100644 --- a/yazi-parser/src/pick/show.rs +++ b/yazi-parser/src/pick/show.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use tokio::sync::oneshot; use yazi_config::popup::PickCfg; use yazi_shared::event::CmdCow; @@ -8,9 +9,17 @@ pub struct ShowOpt { } impl TryFrom for ShowOpt { - type Error = (); + type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { - Ok(Self { cfg: c.take_any("cfg").ok_or(())?, tx: c.take_any("tx").ok_or(())? }) + let Some(cfg) = c.take_any("cfg") else { + bail!("Missing 'cfg' argument in ShowOpt"); + }; + + let Some(tx) = c.take_any("tx") else { + bail!("Missing 'tx' argument in ShowOpt"); + }; + + Ok(Self { cfg, tx }) } } diff --git a/yazi-parser/src/spot/arrow.rs b/yazi-parser/src/spot/arrow.rs deleted file mode 100644 index c54d5acc..00000000 --- a/yazi-parser/src/spot/arrow.rs +++ /dev/null @@ -1,12 +0,0 @@ -use yazi_shared::event::CmdCow; -use yazi_widgets::Step; - -pub struct ArrowOpt { - pub step: Step, -} - -impl From for ArrowOpt { - fn from(c: CmdCow) -> Self { - Self { step: c.first().and_then(|d| d.try_into().ok()).unwrap_or_default() } - } -} diff --git a/yazi-parser/src/spot/close.rs b/yazi-parser/src/spot/close.rs deleted file mode 100644 index d6b58a69..00000000 --- a/yazi-parser/src/spot/close.rs +++ /dev/null @@ -1 +0,0 @@ -pub type CloseOpt = crate::VoidOpt; diff --git a/yazi-parser/src/spot/mod.rs b/yazi-parser/src/spot/mod.rs index ac9dae93..a0d8f95f 100644 --- a/yazi-parser/src/spot/mod.rs +++ b/yazi-parser/src/spot/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(arrow close copy swipe); +yazi_macro::mod_flat!(copy); diff --git a/yazi-parser/src/spot/swipe.rs b/yazi-parser/src/spot/swipe.rs deleted file mode 100644 index 0a4d0e7e..00000000 --- a/yazi-parser/src/spot/swipe.rs +++ /dev/null @@ -1,9 +0,0 @@ -use yazi_shared::{SStr, event::CmdCow}; - -pub struct SwipeOpt { - pub step: SStr, -} - -impl From for SwipeOpt { - fn from(mut c: CmdCow) -> Self { Self { step: c.take_first_str().unwrap_or_default() } } -} diff --git a/yazi-widgets/src/step.rs b/yazi-parser/src/step.rs similarity index 100% rename from yazi-widgets/src/step.rs rename to yazi-parser/src/step.rs diff --git a/yazi-parser/src/tab/arrow.rs b/yazi-parser/src/tab/arrow.rs deleted file mode 100644 index 29a1e04d..00000000 --- a/yazi-parser/src/tab/arrow.rs +++ /dev/null @@ -1,16 +0,0 @@ -use yazi_shared::event::CmdCow; -use yazi_widgets::Step; - -pub struct ArrowOpt { - pub step: Step, -} - -impl From for ArrowOpt { - fn from(c: CmdCow) -> Self { - Self { step: c.first().and_then(|d| d.try_into().ok()).unwrap_or_default() } - } -} - -impl From for ArrowOpt { - fn from(n: isize) -> Self { Self { step: n.into() } } -} diff --git a/yazi-parser/src/tab/back.rs b/yazi-parser/src/tab/back.rs index e69de29b..8b137891 100644 --- a/yazi-parser/src/tab/back.rs +++ b/yazi-parser/src/tab/back.rs @@ -0,0 +1 @@ + diff --git a/yazi-parser/src/tab/enter.rs b/yazi-parser/src/tab/enter.rs index e69de29b..8b137891 100644 --- a/yazi-parser/src/tab/enter.rs +++ b/yazi-parser/src/tab/enter.rs @@ -0,0 +1 @@ + diff --git a/yazi-parser/src/tab/filter_do.rs b/yazi-parser/src/tab/filter_do.rs index e69de29b..2c77dd07 100644 --- a/yazi-parser/src/tab/filter_do.rs +++ b/yazi-parser/src/tab/filter_do.rs @@ -0,0 +1,21 @@ +use anyhow::bail; +use yazi_fs::FilterCase; +use yazi_shared::{SStr, event::CmdCow}; + +pub struct FindDoOpt { + pub query: SStr, + pub prev: bool, + pub case: FilterCase, +} + +impl TryFrom for FindDoOpt { + type Error = anyhow::Error; + + fn try_from(mut c: CmdCow) -> Result { + let Some(query) = c.take_first_str() else { + bail!("'query' is required for FindOpt"); + }; + + Ok(Self { query, prev: c.bool("previous"), case: FilterCase::from(&*c) }) + } +} diff --git a/yazi-parser/src/tab/find.rs b/yazi-parser/src/tab/find.rs index 02621e62..942ffd5a 100644 --- a/yazi-parser/src/tab/find.rs +++ b/yazi-parser/src/tab/find.rs @@ -1,14 +1,15 @@ use yazi_fs::FilterCase; -use yazi_shared::{SStr, event::CmdCow}; +use yazi_shared::event::CmdCow; pub struct FindOpt { - pub query: Option, - pub prev: bool, - pub case: FilterCase, + pub prev: bool, + pub case: FilterCase, } -impl From for FindOpt { - fn from(mut c: CmdCow) -> Self { - Self { query: c.take_first_str(), prev: c.bool("previous"), case: FilterCase::from(&*c) } +impl TryFrom for FindOpt { + type Error = anyhow::Error; + + fn try_from(c: CmdCow) -> Result { + Ok(Self { prev: c.bool("previous"), case: FilterCase::from(&*c) }) } } diff --git a/yazi-parser/src/tab/find_do.rs b/yazi-parser/src/tab/find_do.rs index e69de29b..8b137891 100644 --- a/yazi-parser/src/tab/find_do.rs +++ b/yazi-parser/src/tab/find_do.rs @@ -0,0 +1 @@ + diff --git a/yazi-parser/src/tab/follow.rs b/yazi-parser/src/tab/follow.rs deleted file mode 100644 index c5fd9e27..00000000 --- a/yazi-parser/src/tab/follow.rs +++ /dev/null @@ -1 +0,0 @@ -pub type FollowOpt = crate::VoidOpt; diff --git a/yazi-parser/src/tab/forward.rs b/yazi-parser/src/tab/forward.rs index e69de29b..aea9e0d9 100644 --- a/yazi-parser/src/tab/forward.rs +++ b/yazi-parser/src/tab/forward.rs @@ -0,0 +1,10 @@ +use yazi_shared::event::CmdCow; + +pub struct ForwardOpt { + pub far: bool, + pub end_of_word: bool, +} + +impl From for ForwardOpt { + fn from(c: CmdCow) -> Self { Self { far: c.bool("far"), end_of_word: c.bool("end-of-word") } } +} diff --git a/yazi-parser/src/tab/hidden.rs b/yazi-parser/src/tab/hidden.rs index e69de29b..8b137891 100644 --- a/yazi-parser/src/tab/hidden.rs +++ b/yazi-parser/src/tab/hidden.rs @@ -0,0 +1 @@ + diff --git a/yazi-parser/src/tab/hover.rs b/yazi-parser/src/tab/hover.rs index 8975d333..55568532 100644 --- a/yazi-parser/src/tab/hover.rs +++ b/yazi-parser/src/tab/hover.rs @@ -1,5 +1,6 @@ use yazi_shared::{event::CmdCow, url::Url}; +#[derive(Default)] pub struct HoverOpt { pub url: Option, } @@ -11,3 +12,12 @@ impl From for HoverOpt { impl From> for HoverOpt { fn from(url: Option) -> Self { Self { url } } } + +// --- Do +pub struct HoverDoOpt { + pub url: Url, +} + +impl From for HoverDoOpt { + fn from(url: Url) -> Self { Self { url } } +} diff --git a/yazi-parser/src/tab/leave.rs b/yazi-parser/src/tab/leave.rs deleted file mode 100644 index 04c4b82e..00000000 --- a/yazi-parser/src/tab/leave.rs +++ /dev/null @@ -1 +0,0 @@ -pub type LeaveOpt = crate::VoidOpt; diff --git a/yazi-parser/src/tab/linemode.rs b/yazi-parser/src/tab/linemode.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/yazi-parser/src/tab/mod.rs b/yazi-parser/src/tab/mod.rs index e5f9291c..ecf696d3 100644 --- a/yazi-parser/src/tab/mod.rs +++ b/yazi-parser/src/tab/mod.rs @@ -1,5 +1,4 @@ yazi_macro::mod_flat!( - arrow back cd copy @@ -10,12 +9,9 @@ yazi_macro::mod_flat!( find find_arrow find_do - follow forward hidden hover - leave - linemode reveal search shell diff --git a/yazi-parser/src/tab/search.rs b/yazi-parser/src/tab/search.rs index e69de29b..8b137891 100644 --- a/yazi-parser/src/tab/search.rs +++ b/yazi-parser/src/tab/search.rs @@ -0,0 +1 @@ + diff --git a/yazi-parser/src/tab/sort.rs b/yazi-parser/src/tab/sort.rs index e69de29b..8b137891 100644 --- a/yazi-parser/src/tab/sort.rs +++ b/yazi-parser/src/tab/sort.rs @@ -0,0 +1 @@ + diff --git a/yazi-parser/src/tab/update_peeked.rs b/yazi-parser/src/tab/update_peeked.rs index 856c3aab..eb495f7d 100644 --- a/yazi-parser/src/tab/update_peeked.rs +++ b/yazi-parser/src/tab/update_peeked.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use mlua::Table; use yazi_binding::{FileRef, elements::{Rect, Renderable}}; use yazi_shared::event::CmdCow; @@ -7,10 +8,14 @@ pub struct UpdatePeekedOpt { } impl TryFrom for UpdatePeekedOpt { - type Error = (); + type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { - Ok(Self { lock: c.take_any("lock").ok_or(())? }) + let Some(lock) = c.take_any("lock") else { + bail!("Invalid 'lock' argument in UpdatePeekedOpt"); + }; + + Ok(Self { lock }) } } diff --git a/yazi-parser/src/tab/update_spotted.rs b/yazi-parser/src/tab/update_spotted.rs index 4dd631ae..205b3103 100644 --- a/yazi-parser/src/tab/update_spotted.rs +++ b/yazi-parser/src/tab/update_spotted.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use mlua::Table; use yazi_binding::{FileRef, elements::Renderable}; use yazi_shared::{Id, event::CmdCow}; @@ -7,10 +8,14 @@ pub struct UpdateSpottedOpt { } impl TryFrom for UpdateSpottedOpt { - type Error = (); + type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { - Ok(Self { lock: c.take_any("lock").ok_or(())? }) + let Some(lock) = c.take_any("lock") else { + bail!("Invalid 'lock' argument in UpdateSpottedOpt"); + }; + + Ok(Self { lock }) } } diff --git a/yazi-parser/src/tasks/arrow.rs b/yazi-parser/src/tasks/arrow.rs deleted file mode 100644 index 29a1e04d..00000000 --- a/yazi-parser/src/tasks/arrow.rs +++ /dev/null @@ -1,16 +0,0 @@ -use yazi_shared::event::CmdCow; -use yazi_widgets::Step; - -pub struct ArrowOpt { - pub step: Step, -} - -impl From for ArrowOpt { - fn from(c: CmdCow) -> Self { - Self { step: c.first().and_then(|d| d.try_into().ok()).unwrap_or_default() } - } -} - -impl From for ArrowOpt { - fn from(n: isize) -> Self { Self { step: n.into() } } -} diff --git a/yazi-parser/src/tasks/cancel.rs b/yazi-parser/src/tasks/cancel.rs index e69de29b..8b137891 100644 --- a/yazi-parser/src/tasks/cancel.rs +++ b/yazi-parser/src/tasks/cancel.rs @@ -0,0 +1 @@ + diff --git a/yazi-parser/src/tasks/close.rs b/yazi-parser/src/tasks/close.rs deleted file mode 100644 index d6b58a69..00000000 --- a/yazi-parser/src/tasks/close.rs +++ /dev/null @@ -1 +0,0 @@ -pub type CloseOpt = crate::VoidOpt; diff --git a/yazi-parser/src/tasks/inspect.rs b/yazi-parser/src/tasks/inspect.rs index e69de29b..8b137891 100644 --- a/yazi-parser/src/tasks/inspect.rs +++ b/yazi-parser/src/tasks/inspect.rs @@ -0,0 +1 @@ + diff --git a/yazi-parser/src/tasks/mod.rs b/yazi-parser/src/tasks/mod.rs index 18559820..2ddd592f 100644 --- a/yazi-parser/src/tasks/mod.rs +++ b/yazi-parser/src/tasks/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(arrow cancel close inspect open_with process_exec show); +yazi_macro::mod_flat!(cancel inspect open_with process_exec); diff --git a/yazi-parser/src/tasks/open_with.rs b/yazi-parser/src/tasks/open_with.rs index e69de29b..8b137891 100644 --- a/yazi-parser/src/tasks/open_with.rs +++ b/yazi-parser/src/tasks/open_with.rs @@ -0,0 +1 @@ + diff --git a/yazi-parser/src/tasks/process_exec.rs b/yazi-parser/src/tasks/process_exec.rs index e69de29b..8b137891 100644 --- a/yazi-parser/src/tasks/process_exec.rs +++ b/yazi-parser/src/tasks/process_exec.rs @@ -0,0 +1 @@ + diff --git a/yazi-parser/src/tasks/show.rs b/yazi-parser/src/tasks/show.rs deleted file mode 100644 index 106b02ad..00000000 --- a/yazi-parser/src/tasks/show.rs +++ /dev/null @@ -1 +0,0 @@ -pub type ShowOpt = crate::VoidOpt; diff --git a/yazi-parser/src/void.rs b/yazi-parser/src/void.rs index fd9f98bf..0444efc8 100644 --- a/yazi-parser/src/void.rs +++ b/yazi-parser/src/void.rs @@ -1,5 +1,6 @@ use yazi_shared::event::CmdCow; +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub struct VoidOpt; impl From for VoidOpt { diff --git a/yazi-parser/src/which/callback.rs b/yazi-parser/src/which/callback.rs index ab1e12ec..9e76bfc1 100644 --- a/yazi-parser/src/which/callback.rs +++ b/yazi-parser/src/which/callback.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use tokio::sync::mpsc; use yazi_shared::event::{CmdCow, Data}; @@ -7,12 +8,17 @@ pub struct CallbackOpt { } impl TryFrom for CallbackOpt { - type Error = (); + type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { - Ok(Self { - tx: c.take_any("tx").ok_or(())?, - idx: c.first().and_then(Data::as_usize).ok_or(())?, - }) + let Some(tx) = c.take_any("tx") else { + bail!("Invalid 'tx' argument in CallbackOpt"); + }; + + let Some(idx) = c.first().and_then(Data::as_usize) else { + bail!("Invalid 'idx' argument in CallbackOpt"); + }; + + Ok(Self { tx, idx }) } } diff --git a/yazi-plugin/Cargo.toml b/yazi-plugin/Cargo.toml index ebd929ea..ae7c3da5 100644 --- a/yazi-plugin/Cargo.toml +++ b/yazi-plugin/Cargo.toml @@ -23,12 +23,10 @@ yazi-macro = { path = "../yazi-macro", version = "25.6.11" } yazi-parser = { path = "../yazi-parser", version = "25.6.11" } yazi-proxy = { path = "../yazi-proxy", version = "25.6.11" } yazi-shared = { path = "../yazi-shared", version = "25.6.11" } -yazi-term = { path = "../yazi-term", version = "25.6.11" } yazi-widgets = { path = "../yazi-widgets", version = "25.6.11" } # External dependencies anyhow = { workspace = true } -base64 = { workspace = true } crossterm = { workspace = true } futures = { workspace = true } globset = { workspace = true } diff --git a/yazi-proxy/src/cmp.rs b/yazi-proxy/src/cmp.rs index 769f491e..ca7c5108 100644 --- a/yazi-proxy/src/cmp.rs +++ b/yazi-proxy/src/cmp.rs @@ -4,10 +4,6 @@ use yazi_shared::{Id, event::Cmd}; pub struct CmpProxy; impl CmpProxy { - pub fn close() { - emit!(Call(Cmd::new("cmp:close"))); - } - pub fn trigger(word: &str, ticket: Id) { emit!(Call(Cmd::args("cmp:trigger", [word]).with("ticket", ticket))); } diff --git a/yazi-proxy/src/input.rs b/yazi-proxy/src/input.rs index 973ad303..c23c210e 100644 --- a/yazi-proxy/src/input.rs +++ b/yazi-proxy/src/input.rs @@ -1,9 +1,7 @@ use tokio::sync::mpsc; use yazi_config::popup::InputCfg; use yazi_macro::emit; -use yazi_shared::{Id, errors::InputError, event::Cmd}; - -use crate::options::CmpItem; +use yazi_shared::{errors::InputError, event::Cmd}; pub struct InputProxy; @@ -13,8 +11,4 @@ impl InputProxy { emit!(Call(Cmd::new("input:show").with_any("tx", tx).with_any("cfg", cfg))); rx } - - pub fn complete(item: &CmpItem, ticket: Id) { - emit!(Call(Cmd::new("input:complete").with_any("item", item.clone()).with("ticket", ticket))); - } } diff --git a/yazi-proxy/src/lib.rs b/yazi-proxy/src/lib.rs index 519474ca..2a987ff9 100644 --- a/yazi-proxy/src/lib.rs +++ b/yazi-proxy/src/lib.rs @@ -2,6 +2,6 @@ mod macros; yazi_macro::mod_pub!(options); -yazi_macro::mod_flat!(app cmp confirm input mgr pick semaphore tab tasks); +yazi_macro::mod_flat!(app cmp confirm input mgr pick semaphore tasks); pub fn init() { crate::init_semaphore(); } diff --git a/yazi-proxy/src/mgr.rs b/yazi-proxy/src/mgr.rs index 5517df95..41953082 100644 --- a/yazi-proxy/src/mgr.rs +++ b/yazi-proxy/src/mgr.rs @@ -1,25 +1,23 @@ -use yazi_macro::emit; -use yazi_shared::{event::Cmd, url::Url}; +use std::borrow::Cow; -use crate::options::OpenDoOpt; +use yazi_macro::emit; +use yazi_shared::{SStr, event::Cmd, url::Url}; + +use crate::options::{OpenDoOpt, SearchOpt}; pub struct MgrProxy; impl MgrProxy { - pub fn spot(skip: Option) { - emit!(Call(Cmd::new("mgr:spot").with_opt("skip", skip))); + pub fn cd(target: &Url) { + emit!(Call(Cmd::args("mgr:cd", [target]).with("raw", true))); } - pub fn peek(force: bool) { - emit!(Call(Cmd::new("mgr:peek").with("force", force))); + pub fn reveal(target: &Url) { + emit!(Call(Cmd::args("mgr:reveal", [target]).with("raw", true).with("no-dummy", true))); } - pub fn watch() { - emit!(Call(Cmd::new("mgr:watch"))); - } - - pub fn refresh() { - emit!(Call(Cmd::new("mgr:refresh"))); + pub fn arrow(step: impl Into) { + emit!(Call(Cmd::args("mgr:arrow", [step.into()]))); } pub fn open_do(opt: OpenDoOpt) { @@ -32,12 +30,17 @@ impl MgrProxy { )); } - pub fn update_tasks(url: &Url) { - emit!(Call(Cmd::new("mgr:update_tasks").with_any("urls", vec![url.clone()]))); + pub fn search_do(opt: SearchOpt) { + emit!(Call( + // TODO: use second positional argument instead of `args` parameter + Cmd::args("mgr:search_do", [opt.subject]) + .with("via", Cow::Borrowed(opt.via.into_str())) + .with("args", opt.args_raw.into_owned()) + )); } - pub fn update_paged() { - emit!(Call(Cmd::new("mgr:update_paged"))); + pub fn update_tasks(url: &Url) { + emit!(Call(Cmd::new("mgr:update_tasks").with_any("urls", vec![url.clone()]))); } pub fn update_paged_by(page: usize, only_if: &Url) { diff --git a/yazi-proxy/src/options/notify.rs b/yazi-proxy/src/options/notify.rs index 66376c88..d5bf7c21 100644 --- a/yazi-proxy/src/options/notify.rs +++ b/yazi-proxy/src/options/notify.rs @@ -1,5 +1,6 @@ use std::{str::FromStr, time::Duration}; +use anyhow::anyhow; use mlua::{ExternalError, ExternalResult}; use serde::Deserialize; use yazi_config::{Style, THEME}; @@ -13,9 +14,11 @@ pub struct NotifyOpt { } impl TryFrom for NotifyOpt { - type Error = (); + type Error = anyhow::Error; - fn try_from(mut c: CmdCow) -> Result { c.take_any("option").ok_or(()) } + fn try_from(mut c: CmdCow) -> Result { + c.take_any("option").ok_or_else(|| anyhow!("Invalid 'option' in NotifyOpt")) + } } impl TryFrom for NotifyOpt { diff --git a/yazi-proxy/src/options/open.rs b/yazi-proxy/src/options/open.rs index 89c9f418..f79ac832 100644 --- a/yazi-proxy/src/options/open.rs +++ b/yazi-proxy/src/options/open.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; +use anyhow::anyhow; use yazi_config::opener::OpenerRule; use yazi_shared::{event::CmdCow, url::Url}; @@ -24,7 +25,9 @@ pub struct OpenWithOpt { } impl TryFrom for OpenWithOpt { - type Error = (); + type Error = anyhow::Error; - fn try_from(mut c: CmdCow) -> Result { c.take_any("option").ok_or(()) } + fn try_from(mut c: CmdCow) -> Result { + c.take_any("option").ok_or_else(|| anyhow!("Missing 'option' in OpenWithOpt")) + } } diff --git a/yazi-proxy/src/options/process.rs b/yazi-proxy/src/options/process.rs index f86dbc9a..a8e39e09 100644 --- a/yazi-proxy/src/options/process.rs +++ b/yazi-proxy/src/options/process.rs @@ -1,5 +1,6 @@ use std::{borrow::Cow, ffi::OsString}; +use anyhow::anyhow; use tokio::sync::oneshot; use yazi_config::opener::OpenerRule; use yazi_shared::{event::CmdCow, url::Url}; @@ -13,7 +14,9 @@ pub struct ProcessExecOpt { } impl TryFrom for ProcessExecOpt { - type Error = (); + type Error = anyhow::Error; - fn try_from(mut c: CmdCow) -> Result { c.take_any("option").ok_or(()) } + fn try_from(mut c: CmdCow) -> Result { + c.take_any("option").ok_or_else(|| anyhow!("Missing 'option' in ProcessExecOpt")) + } } diff --git a/yazi-proxy/src/options/search.rs b/yazi-proxy/src/options/search.rs index f7676cf4..e366b59c 100644 --- a/yazi-proxy/src/options/search.rs +++ b/yazi-proxy/src/options/search.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use yazi_shared::{SStr, event::CmdCow}; pub struct SearchOpt { @@ -8,7 +9,7 @@ pub struct SearchOpt { } impl TryFrom for SearchOpt { - type Error = (); + type Error = anyhow::Error; fn try_from(mut c: CmdCow) -> Result { // TODO: remove this @@ -18,13 +19,15 @@ impl TryFrom for SearchOpt { (c.take_first_str().unwrap_or_default().as_ref().into(), "".into()) }; + let Ok(args) = yazi_shared::shell::split_unix(c.str("args").unwrap_or_default(), false) else { + bail!("Invalid 'args' argument in SearchOpt"); + }; + Ok(Self { via, subject, // TODO: use second positional argument instead of `args` parameter - args: yazi_shared::shell::split_unix(c.str("args").unwrap_or_default(), false) - .map_err(|_| ())? - .0, + args: args.0, args_raw: c.take_str("args").unwrap_or_default(), }) } diff --git a/yazi-proxy/src/tab.rs b/yazi-proxy/src/tab.rs deleted file mode 100644 index 66945dc8..00000000 --- a/yazi-proxy/src/tab.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::borrow::Cow; - -use yazi_macro::emit; -use yazi_shared::{SStr, event::Cmd, url::Url}; - -use crate::options::SearchOpt; - -pub struct TabProxy; - -impl TabProxy { - pub fn cd(target: &Url) { - emit!(Call(Cmd::args("mgr:cd", [target]).with("raw", true))); - } - - pub fn reveal(target: &Url) { - emit!(Call(Cmd::args("mgr:reveal", [target]).with("raw", true).with("no-dummy", true))); - } - - pub fn arrow(step: impl Into) { - emit!(Call(Cmd::args("mgr:arrow", [step.into()]))); - } - - pub fn search_do(opt: SearchOpt) { - emit!(Call( - // TODO: use second positional argument instead of `args` parameter - Cmd::args("mgr:search_do", [opt.subject]) - .with("via", Cow::Borrowed(opt.via.into_str())) - .with("args", opt.args_raw.into_owned()) - )); - } -} diff --git a/yazi-scheduler/Cargo.toml b/yazi-scheduler/Cargo.toml index 2e10ba35..310b5509 100644 --- a/yazi-scheduler/Cargo.toml +++ b/yazi-scheduler/Cargo.toml @@ -13,6 +13,7 @@ yazi-config = { path = "../yazi-config", version = "25.6.11" } yazi-dds = { path = "../yazi-dds", version = "25.6.11" } yazi-fs = { path = "../yazi-fs", version = "25.6.11" } yazi-macro = { path = "../yazi-macro", version = "25.6.11" } +yazi-parser = { path = "../yazi-parser", version = "25.6.11" } yazi-plugin = { path = "../yazi-plugin", version = "25.6.11" } yazi-proxy = { path = "../yazi-proxy", version = "25.6.11" } yazi-shared = { path = "../yazi-shared", version = "25.6.11" } diff --git a/yazi-scheduler/src/ongoing.rs b/yazi-scheduler/src/ongoing.rs index b37928d3..05a61703 100644 --- a/yazi-scheduler/src/ongoing.rs +++ b/yazi-scheduler/src/ongoing.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use futures::future::BoxFuture; use yazi_config::YAZI; +use yazi_parser::app::TasksProgress; use yazi_shared::{Id, Ids}; use super::{Task, TaskStage}; @@ -78,4 +79,21 @@ impl Ongoing { } None } + + pub fn progress(&self) -> TasksProgress { + let mut progress = TasksProgress::default(); + if self.is_empty() { + return progress; + } + + for task in self.values() { + progress.total += task.total; + progress.succ += task.succ; + progress.fail += task.fail; + + progress.found += task.found; + progress.processed += task.processed; + } + progress + } } diff --git a/yazi-shared/src/event/cmd.rs b/yazi-shared/src/event/cmd.rs index 2efa8d94..1ad89a37 100644 --- a/yazi-shared/src/event/cmd.rs +++ b/yazi-shared/src/event/cmd.rs @@ -4,7 +4,7 @@ use anyhow::{Result, bail}; use serde::{Deserialize, de}; use super::{Data, DataKey}; -use crate::{Layer, SStr, url::Url}; +use crate::{Id, Layer, SStr, url::Url}; #[derive(Debug, Default)] pub struct Cmd { @@ -97,6 +97,9 @@ impl Cmd { self.get(name).and_then(Data::as_bool) } + #[inline] + pub fn id(&self, name: impl Into) -> Option { self.get(name).and_then(Data::as_id) } + #[inline] pub fn first(&self) -> Option<&Data> { self.get(0) } diff --git a/yazi-shared/src/event/data.rs b/yazi-shared/src/event/data.rs index 5343f7c9..4aec859e 100644 --- a/yazi-shared/src/event/data.rs +++ b/yazi-shared/src/event/data.rs @@ -91,6 +91,10 @@ impl Data { } } +impl From<()> for Data { + fn from(_: ()) -> Self { Self::Nil } +} + impl From for Data { fn from(value: bool) -> Self { Self::Boolean(value) } } @@ -131,6 +135,10 @@ impl From<&str> for Data { fn from(value: &str) -> Self { Self::String(Cow::Owned(value.to_owned())) } } +impl PartialEq for Data { + fn eq(&self, other: &bool) -> bool { self.as_bool() == Some(*other) } +} + // --- Key #[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] diff --git a/yazi-widgets/Cargo.toml b/yazi-widgets/Cargo.toml index 3a575658..504a6328 100644 --- a/yazi-widgets/Cargo.toml +++ b/yazi-widgets/Cargo.toml @@ -10,14 +10,14 @@ repository = "https://github.com/sxyazi/yazi" [dependencies] yazi-binding = { path = "../yazi-binding", version = "25.6.11" } -yazi-codegen = { path = "../yazi-codegen", version = "25.6.11" } yazi-config = { path = "../yazi-config", version = "25.6.11" } yazi-macro = { path = "../yazi-macro", version = "25.6.11" } -yazi-proxy = { path = "../yazi-proxy", version = "25.6.11" } +yazi-parser = { path = "../yazi-parser", version = "25.6.11" } yazi-shared = { path = "../yazi-shared", version = "25.6.11" } yazi-term = { path = "../yazi-term", version = "25.6.11" } # External dependencies +anyhow = { workspace = true } base64 = { workspace = true } crossterm = { workspace = true } futures = { workspace = true } diff --git a/yazi-widgets/src/input/commands/backspace.rs b/yazi-widgets/src/input/commands/backspace.rs index 0e47c28b..d93e304f 100644 --- a/yazi-widgets/src/input/commands/backspace.rs +++ b/yazi-widgets/src/input/commands/backspace.rs @@ -1,38 +1,28 @@ -use yazi_macro::render; -use yazi_shared::event::CmdCow; +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::input::BackspaceOpt; +use yazi_shared::event::Data; use crate::input::Input; -struct Opt { - under: bool, -} - -impl From for Opt { - fn from(c: CmdCow) -> Self { Self { under: c.bool("under") } } -} -impl From for Opt { - fn from(under: bool) -> Self { Self { under } } -} - impl Input { - #[yazi_codegen::command] - pub fn backspace(&mut self, opt: Opt) { + pub fn backspace(&mut self, opt: BackspaceOpt) -> Result { let snap = self.snap_mut(); if !opt.under && snap.cursor < 1 { - return; + succ!(); } else if opt.under && snap.cursor >= snap.count() { - return; + succ!(); } if opt.under { snap.value.remove(snap.idx(snap.cursor).unwrap()); - self.r#move(0); + act!(r#move, self)?; } else { snap.value.remove(snap.idx(snap.cursor - 1).unwrap()); - self.r#move(-1); + act!(r#move, self, -1)?; } self.flush_value(); - render!(); + succ!(render!()); } } diff --git a/yazi-widgets/src/input/commands/backward.rs b/yazi-widgets/src/input/commands/backward.rs index c07674df..3b7cd3b1 100644 --- a/yazi-widgets/src/input/commands/backward.rs +++ b/yazi-widgets/src/input/commands/backward.rs @@ -1,21 +1,15 @@ -use yazi_shared::{CharKind, event::CmdCow}; +use anyhow::Result; +use yazi_macro::{act, succ}; +use yazi_parser::input::BackwardOpt; +use yazi_shared::{CharKind, event::Data}; use crate::input::Input; -struct Opt { - far: bool, -} - -impl From for Opt { - fn from(c: CmdCow) -> Self { Self { far: c.bool("far") } } -} - impl Input { - #[yazi_codegen::command] - pub fn backward(&mut self, opt: Opt) { + pub fn backward(&mut self, opt: BackwardOpt) -> Result { let snap = self.snap(); if snap.cursor == 0 { - return self.r#move(0); + return act!(r#move, self); } let idx = snap.idx(snap.cursor).unwrap_or(snap.len()); @@ -24,13 +18,14 @@ impl Input { for (i, c) in it { let k = CharKind::new(c); if prev != CharKind::Space && prev.vary(k, opt.far) { - return self.r#move(-(i as isize)); + return act!(r#move, self, -(i as isize)); } prev = k; } if prev != CharKind::Space { - self.r#move(-(snap.len() as isize)); + act!(r#move, self, -(snap.len() as isize))?; } + succ!(); } } diff --git a/yazi-widgets/src/input/commands/commands.rs b/yazi-widgets/src/input/commands/commands.rs index 61cd7cfa..881e4d5f 100644 --- a/yazi-widgets/src/input/commands/commands.rs +++ b/yazi-widgets/src/input/commands/commands.rs @@ -1,18 +1,20 @@ -use yazi_shared::event::CmdCow; +use anyhow::Result; +use yazi_macro::{act, succ}; +use yazi_shared::event::{CmdCow, Data}; use crate::input::{Input, InputMode}; impl Input { - pub fn execute(&mut self, cmd: CmdCow) { + pub fn execute(&mut self, cmd: CmdCow) -> Result { macro_rules! on { ($name:ident) => { if cmd.name == stringify!($name) { - return self.$name(cmd); + return act!($name, self, cmd); } }; ($name:ident, $alias:literal) => { if cmd.name == $alias { - return self.$name(cmd); + return act!($name, self, cmd); } }; } @@ -44,5 +46,7 @@ impl Input { } InputMode::Replace => {} } + + succ!(); } } diff --git a/yazi-widgets/src/input/commands/complete.rs b/yazi-widgets/src/input/commands/complete.rs index 86f57bd4..8c85e087 100644 --- a/yazi-widgets/src/input/commands/complete.rs +++ b/yazi-widgets/src/input/commands/complete.rs @@ -1,8 +1,9 @@ use std::path::MAIN_SEPARATOR_STR; -use yazi_macro::render; -use yazi_proxy::options::CmpItem; -use yazi_shared::{Id, event::{CmdCow, Data}}; +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::input::CompleteOpt; +use yazi_shared::event::Data; use crate::input::Input; @@ -12,26 +13,8 @@ const SEPARATOR: [char; 2] = ['/', '\\']; #[cfg(not(windows))] const SEPARATOR: char = std::path::MAIN_SEPARATOR; -pub struct Opt { - item: CmpItem, - _ticket: Id, // FIXME: not used -} - -impl TryFrom for Opt { - type Error = (); - - fn try_from(mut c: CmdCow) -> Result { - Ok(Self { - item: c.take_any("item").ok_or(())?, - _ticket: c.get("ticket").and_then(Data::as_id).unwrap_or_default(), - }) - } -} - impl Input { - pub fn complete(&mut self, opt: impl TryInto) { - let Ok(opt): Result = opt.try_into() else { return }; - + pub fn complete(&mut self, opt: CompleteOpt) -> Result { let (before, after) = self.partition(); let new = if let Some((prefix, _)) = before.rsplit_once(SEPARATOR) { format!("{prefix}/{}{after}", opt.item.completable()).replace(SEPARATOR, MAIN_SEPARATOR_STR) @@ -41,14 +24,14 @@ impl Input { let snap = self.snap_mut(); if new == snap.value { - return; + succ!(); } let delta = new.chars().count() as isize - snap.count() as isize; snap.value = new; - self.r#move(delta); + act!(r#move, self, delta)?; self.flush_value(); - render!(); + succ!(render!()); } } diff --git a/yazi-widgets/src/input/commands/delete.rs b/yazi-widgets/src/input/commands/delete.rs index e3647d96..6eca69a3 100644 --- a/yazi-widgets/src/input/commands/delete.rs +++ b/yazi-widgets/src/input/commands/delete.rs @@ -1,20 +1,12 @@ -use yazi_macro::render; -use yazi_shared::event::CmdCow; +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::input::DeleteOpt; +use yazi_shared::event::Data; use crate::input::{Input, op::InputOp}; -struct Opt { - cut: bool, - insert: bool, -} - -impl From for Opt { - fn from(c: CmdCow) -> Self { Self { cut: c.bool("cut"), insert: c.bool("insert") } } -} - impl Input { - #[yazi_codegen::command] - pub fn delete(&mut self, opt: Opt) { + pub fn delete(&mut self, opt: DeleteOpt) -> Result { match self.snap().op { InputOp::None => { self.snap_mut().op = InputOp::Delete(opt.cut, opt.insert, self.snap().cursor); @@ -22,13 +14,14 @@ impl Input { InputOp::Select(start) => { self.snap_mut().op = InputOp::Delete(opt.cut, opt.insert, start); render!(self.handle_op(self.snap().cursor, true)); - self.r#move(0); + act!(r#move, self)?; } InputOp::Delete(..) => { self.snap_mut().op = InputOp::Delete(opt.cut, opt.insert, 0); - self.r#move(self.snap().len() as isize); + act!(r#move, self, self.snap().len() as isize)?; } _ => {} } + succ!(); } } diff --git a/yazi-widgets/src/input/commands/escape.rs b/yazi-widgets/src/input/commands/escape.rs index fd1dfee9..4e384c12 100644 --- a/yazi-widgets/src/input/commands/escape.rs +++ b/yazi-widgets/src/input/commands/escape.rs @@ -1,14 +1,12 @@ +use anyhow::Result; +use yazi_macro::{act, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; + use crate::input::{Input, InputMode, op::InputOp}; -struct Opt; - -impl From<()> for Opt { - fn from(_: ()) -> Self { Self } -} - impl Input { - #[yazi_codegen::command] - pub fn escape(&mut self, _: Opt) { + pub fn escape(&mut self, _: VoidOpt) -> Result { let snap = self.snap_mut(); match snap.mode { InputMode::Normal => { @@ -16,12 +14,13 @@ impl Input { } InputMode::Insert => { snap.mode = InputMode::Normal; - self.r#move(-1); + act!(r#move, self, -1)?; } InputMode::Replace => { snap.mode = InputMode::Normal; } } self.snaps.tag(self.limit); + succ!(); } } diff --git a/yazi-widgets/src/input/commands/forward.rs b/yazi-widgets/src/input/commands/forward.rs index 32bb326f..002cc3e5 100644 --- a/yazi-widgets/src/input/commands/forward.rs +++ b/yazi-widgets/src/input/commands/forward.rs @@ -1,24 +1,17 @@ -use yazi_shared::{CharKind, event::CmdCow}; +use anyhow::Result; +use yazi_macro::act; +use yazi_parser::tab::ForwardOpt; +use yazi_shared::{CharKind, event::Data}; use crate::input::{Input, op::InputOp}; -struct Opt { - far: bool, - end_of_word: bool, -} - -impl From for Opt { - fn from(c: CmdCow) -> Self { Self { far: c.bool("far"), end_of_word: c.bool("end-of-word") } } -} - impl Input { - #[yazi_codegen::command] - pub fn forward(&mut self, opt: Opt) { + pub fn forward(&mut self, opt: ForwardOpt) -> Result { let snap = self.snap(); let mut it = snap.value.chars().skip(snap.cursor).enumerate(); let Some(mut prev) = it.next().map(|(_, c)| CharKind::new(c)) else { - return self.r#move(0); + return act!(r#move, self); }; for (i, c) in it { @@ -29,13 +22,17 @@ impl Input { k != CharKind::Space && k.vary(prev, opt.far) }; if b && !matches!(snap.op, InputOp::None | InputOp::Select(_)) { - return self.r#move(i as isize); + return act!(r#move, self, i as isize); } else if b { - return self.r#move(if opt.end_of_word { i - snap.mode.delta() } else { i } as isize); + return act!( + r#move, + self, + if opt.end_of_word { i - snap.mode.delta() } else { i } as isize + ); } prev = k; } - self.r#move(snap.len() as isize) + act!(r#move, self, snap.len() as isize) } } diff --git a/yazi-widgets/src/input/commands/insert.rs b/yazi-widgets/src/input/commands/insert.rs index da3a57a1..18b7d6d1 100644 --- a/yazi-widgets/src/input/commands/insert.rs +++ b/yazi-widgets/src/input/commands/insert.rs @@ -1,34 +1,24 @@ -use yazi_macro::render; -use yazi_shared::event::CmdCow; +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::input::InsertOpt; +use yazi_shared::event::Data; use crate::input::{Input, InputMode, op::InputOp}; -struct Opt { - append: bool, -} - -impl From for Opt { - fn from(c: CmdCow) -> Self { Self { append: c.bool("append") } } -} -impl From for Opt { - fn from(append: bool) -> Self { Self { append } } -} - impl Input { - #[yazi_codegen::command] - pub fn insert(&mut self, opt: Opt) { + pub fn insert(&mut self, opt: InsertOpt) -> Result { let snap = self.snap_mut(); if snap.mode == InputMode::Normal { snap.op = InputOp::None; snap.mode = InputMode::Insert; } else { - return; + succ!(); } if opt.append { - self.r#move(1); + act!(r#move, self, 1)?; } - render!(); + succ!(render!()); } } diff --git a/yazi-widgets/src/input/commands/kill.rs b/yazi-widgets/src/input/commands/kill.rs index aac392d2..febceff4 100644 --- a/yazi-widgets/src/input/commands/kill.rs +++ b/yazi-widgets/src/input/commands/kill.rs @@ -1,21 +1,14 @@ use std::ops::RangeBounds; -use yazi_macro::render; -use yazi_shared::{CharKind, SStr, event::CmdCow}; +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::input::KillOpt; +use yazi_shared::{CharKind, event::Data}; use crate::input::Input; -struct Opt { - kind: SStr, -} - -impl From for Opt { - fn from(mut c: CmdCow) -> Self { Self { kind: c.take_first_str().unwrap_or_default() } } -} - impl Input { - #[yazi_codegen::command] - pub fn kill(&mut self, opt: Opt) { + pub fn kill(&mut self, opt: KillOpt) -> Result { let snap = self.snap_mut(); match opt.kind.as_ref() { "all" => self.kill_range(..), @@ -37,11 +30,11 @@ impl Input { let end = start + Self::find_word_boundary(snap.value[start..].chars()); self.kill_range(start..end) } - _ => {} + _ => succ!(), } } - fn kill_range(&mut self, range: impl RangeBounds) { + fn kill_range(&mut self, range: impl RangeBounds) -> Result { let snap = self.snap_mut(); snap.cursor = match range.start_bound() { std::ops::Bound::Included(i) => *i, @@ -49,12 +42,12 @@ impl Input { std::ops::Bound::Unbounded => 0, }; if snap.value.drain(range).next().is_none() { - return; + succ!(); } - self.r#move(0); + act!(r#move, self)?; self.flush_value(); - render!(); + succ!(render!()); } /// Searches for a word boundary and returns the movement in the cursor diff --git a/yazi-widgets/src/input/commands/move.rs b/yazi-widgets/src/input/commands/move.rs index 4e039392..38ee0990 100644 --- a/yazi-widgets/src/input/commands/move.rs +++ b/yazi-widgets/src/input/commands/move.rs @@ -1,42 +1,24 @@ -use std::{num::ParseIntError, str::FromStr}; - -use yazi_macro::render; -use yazi_shared::event::{CmdCow, Data}; +use anyhow::Result; +use yazi_macro::{render, succ}; +use yazi_parser::input::MoveOpt; +use yazi_shared::event::Data; use crate::input::{Input, op::InputOp, snap::InputSnap}; -struct Opt { - step: OptStep, - in_operating: bool, -} - -impl From for Opt { - fn from(c: CmdCow) -> Self { - Self { - step: c.first().and_then(|d| d.try_into().ok()).unwrap_or_default(), - in_operating: c.bool("in-operating"), - } - } -} -impl From for Opt { - fn from(step: isize) -> Self { Self { step: step.into(), in_operating: false } } -} - impl Input { - #[yazi_codegen::command] - pub fn r#move(&mut self, opt: Opt) { + pub fn r#move(&mut self, opt: MoveOpt) -> Result { let snap = self.snap(); if opt.in_operating && snap.op == InputOp::None { - return; + succ!(); } let o_cur = snap.cursor; - render!(self.handle_op(opt.step.cursor(snap), false)); + render!(self.handle_op(opt.step.add(&snap.value, snap.cursor), false)); let n_cur = self.snap().cursor; let (limit, snap) = (self.limit, self.snap_mut()); if snap.value.is_empty() { - return snap.offset = 0; + succ!(snap.offset = 0); } let (o_off, scrolloff) = (snap.offset, 5.min(limit / 2)); @@ -60,60 +42,6 @@ impl Input { n_cur + pad - max } }; - } -} - -// --- Step -enum OptStep { - Offset(isize), - Bol, - Eol, - FirstChar, -} - -impl OptStep { - fn cursor(self, snap: &InputSnap) -> usize { - match self { - Self::Offset(n) if n <= 0 => snap.cursor.saturating_add_signed(n), - Self::Offset(n) => snap.count().min(snap.cursor + n as usize), - Self::Bol => 0, - Self::Eol => snap.count(), - Self::FirstChar => { - snap.value.chars().enumerate().find(|(_, c)| !c.is_whitespace()).map_or(0, |(i, _)| i) - } - } - } -} - -impl Default for OptStep { - fn default() -> Self { 0.into() } -} - -impl FromStr for OptStep { - type Err = ParseIntError; - - fn from_str(s: &str) -> Result { - Ok(match s { - "bol" => Self::Bol, - "eol" => Self::Eol, - "first-char" => Self::FirstChar, - s => Self::Offset(s.parse()?), - }) - } -} - -impl From for OptStep { - fn from(value: isize) -> Self { Self::Offset(value) } -} - -impl TryFrom<&Data> for OptStep { - type Error = (); - - fn try_from(value: &Data) -> Result { - match value { - Data::String(s) => s.parse().map_err(|_| ()), - Data::Integer(i) => Ok(Self::from(*i as isize)), - _ => Err(()), - } + succ!(); } } diff --git a/yazi-widgets/src/input/commands/paste.rs b/yazi-widgets/src/input/commands/paste.rs index 1889dea9..4cd0c072 100644 --- a/yazi-widgets/src/input/commands/paste.rs +++ b/yazi-widgets/src/input/commands/paste.rs @@ -1,19 +1,12 @@ -use yazi_macro::render; -use yazi_shared::event::CmdCow; +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::input::PasteOpt; +use yazi_shared::event::Data; use crate::{CLIPBOARD, input::{Input, op::InputOp}}; -struct Opt { - before: bool, -} - -impl From for Opt { - fn from(c: CmdCow) -> Self { Self { before: c.bool("before") } } -} - impl Input { - #[yazi_codegen::command] - pub fn paste(&mut self, opt: Opt) { + pub fn paste(&mut self, opt: PasteOpt) -> Result { if let Some(start) = self.snap().op.start() { self.snap_mut().op = InputOp::Delete(false, false, start); self.handle_op(self.snap().cursor, true); @@ -21,12 +14,12 @@ impl Input { let s = futures::executor::block_on(CLIPBOARD.get()); if s.is_empty() { - return; + succ!(); } - self.insert(!opt.before); - self.type_str(&s.to_string_lossy()); - self.escape(()); - render!(); + act!(insert, self, !opt.before)?; + self.type_str(&s.to_string_lossy())?; + act!(escape, self)?; + succ!(render!()); } } diff --git a/yazi-widgets/src/input/commands/redo.rs b/yazi-widgets/src/input/commands/redo.rs index ed058e65..7648e58f 100644 --- a/yazi-widgets/src/input/commands/redo.rs +++ b/yazi-widgets/src/input/commands/redo.rs @@ -1,12 +1,14 @@ -use yazi_macro::render; -use yazi_shared::event::CmdCow; +use anyhow::Result; +use yazi_macro::{act, render}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; use crate::input::Input; impl Input { - pub fn redo(&mut self, _: CmdCow) { + pub fn redo(&mut self, _: VoidOpt) -> Result { render!(self.snaps.redo()); - self.r#move(0); + act!(r#move, self) } } diff --git a/yazi-widgets/src/input/commands/replace.rs b/yazi-widgets/src/input/commands/replace.rs index 6aadfd95..c26b5021 100644 --- a/yazi-widgets/src/input/commands/replace.rs +++ b/yazi-widgets/src/input/commands/replace.rs @@ -1,20 +1,22 @@ -use yazi_macro::render; -use yazi_shared::{event::CmdCow, replace_cow}; +use anyhow::Result; +use yazi_macro::{render, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::{event::Data, replace_cow}; use crate::input::{Input, InputMode, op::InputOp}; impl Input { - #[yazi_codegen::command] - pub fn replace(&mut self, _: CmdCow) { + pub fn replace(&mut self, _: VoidOpt) -> Result { let snap = self.snap_mut(); if snap.mode == InputMode::Normal { snap.op = InputOp::None; snap.mode = InputMode::Replace; render!(); } + succ!(); } - pub fn replace_str(&mut self, s: &str) { + pub fn replace_str(&mut self, s: &str) -> Result { let s = replace_cow(replace_cow(s, "\r", " "), "\n", " "); let snap = self.snap_mut(); @@ -28,7 +30,7 @@ impl Input { (Some(_), Some((len, _))) => snap.value.replace_range(start..start + len, &s), } - render!(); self.snaps.tag(self.limit).then(|| self.flush_value()); + succ!(render!()); } } diff --git a/yazi-widgets/src/input/commands/type.rs b/yazi-widgets/src/input/commands/type.rs index 9e32323a..ccd6e7eb 100644 --- a/yazi-widgets/src/input/commands/type.rs +++ b/yazi-widgets/src/input/commands/type.rs @@ -1,25 +1,26 @@ +use anyhow::Result; use yazi_config::keymap::Key; -use yazi_macro::render; -use yazi_shared::replace_cow; +use yazi_macro::{act, render, succ}; +use yazi_shared::{event::Data, replace_cow}; use crate::input::{Input, InputMode}; impl Input { - pub fn r#type(&mut self, key: &Key) -> bool { - let Some(c) = key.plain() else { return false }; + pub fn r#type(&mut self, key: &Key) -> Result { + let Some(c) = key.plain() else { return Ok(false) }; if self.mode() == InputMode::Insert { - self.type_str(c.encode_utf8(&mut [0; 4])); - return true; + self.type_str(c.encode_utf8(&mut [0; 4]))?; + return Ok(true); } else if self.mode() == InputMode::Replace { - self.replace_str(c.encode_utf8(&mut [0; 4])); - return true; + self.replace_str(c.encode_utf8(&mut [0; 4]))?; + return Ok(true); } - false + Ok(false) } - pub fn type_str(&mut self, s: &str) { + pub fn type_str(&mut self, s: &str) -> Result { let s = replace_cow(replace_cow(s, "\r", " "), "\n", " "); let snap = self.snap_mut(); @@ -29,8 +30,8 @@ impl Input { snap.value.insert_str(snap.idx(snap.cursor).unwrap(), &s); } - self.r#move(s.chars().count() as isize); + act!(r#move, self, s.chars().count() as isize)?; self.flush_value(); - render!(); + succ!(render!()); } } diff --git a/yazi-widgets/src/input/commands/undo.rs b/yazi-widgets/src/input/commands/undo.rs index 07a8bb5c..2144403a 100644 --- a/yazi-widgets/src/input/commands/undo.rs +++ b/yazi-widgets/src/input/commands/undo.rs @@ -1,19 +1,21 @@ -use yazi_macro::render; -use yazi_shared::event::CmdCow; +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; use crate::input::{Input, InputMode}; impl Input { - pub fn undo(&mut self, _: CmdCow) { + pub fn undo(&mut self, _: VoidOpt) -> Result { if !self.snaps.undo() { - return; + succ!(); } - self.r#move(0); + act!(r#move, self)?; if self.snap().mode == InputMode::Insert { - self.escape(()); + act!(escape, self)?; } - render!(); + succ!(render!()); } } diff --git a/yazi-widgets/src/input/commands/visual.rs b/yazi-widgets/src/input/commands/visual.rs index 4dd1ca5d..0ed8a271 100644 --- a/yazi-widgets/src/input/commands/visual.rs +++ b/yazi-widgets/src/input/commands/visual.rs @@ -1,12 +1,14 @@ -use yazi_macro::render; -use yazi_shared::event::CmdCow; +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; use crate::input::{Input, InputMode, op::InputOp}; impl Input { - pub fn visual(&mut self, _: CmdCow) { + pub fn visual(&mut self, _: VoidOpt) -> Result { if self.snap().mode != InputMode::Normal { - self.escape(()); + act!(escape, self)?; } let snap = self.snap_mut(); @@ -14,5 +16,6 @@ impl Input { snap.op = InputOp::Select(snap.cursor); render!(); } + succ!(); } } diff --git a/yazi-widgets/src/input/commands/yank.rs b/yazi-widgets/src/input/commands/yank.rs index 34b3275b..36dade19 100644 --- a/yazi-widgets/src/input/commands/yank.rs +++ b/yazi-widgets/src/input/commands/yank.rs @@ -1,10 +1,12 @@ -use yazi_macro::render; -use yazi_shared::event::CmdCow; +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::event::Data; use crate::input::{Input, op::InputOp}; impl Input { - pub fn yank(&mut self, _: CmdCow) { + pub fn yank(&mut self, _: VoidOpt) -> Result { match self.snap().op { InputOp::None => { self.snap_mut().op = InputOp::Yank(self.snap().cursor); @@ -12,13 +14,14 @@ impl Input { InputOp::Select(start) => { self.snap_mut().op = InputOp::Yank(start); render!(self.handle_op(self.snap().cursor, true)); - self.r#move(0); + act!(r#move, self)?; } InputOp::Yank(_) => { self.snap_mut().op = InputOp::Yank(0); - self.r#move(self.snap().len() as isize); + act!(r#move, self, self.snap().len() as isize)?; } _ => {} } + succ!(); } } diff --git a/yazi-widgets/src/lib.rs b/yazi-widgets/src/lib.rs index 410d0cc2..aab44f98 100644 --- a/yazi-widgets/src/lib.rs +++ b/yazi-widgets/src/lib.rs @@ -1,5 +1,5 @@ yazi_macro::mod_pub!(input); -yazi_macro::mod_flat!(clipboard scrollable step); +yazi_macro::mod_flat!(clipboard scrollable); pub fn init() { CLIPBOARD.with(<_>::default); } diff --git a/yazi-widgets/src/scrollable.rs b/yazi-widgets/src/scrollable.rs index 4fde11c5..5af8231a 100644 --- a/yazi-widgets/src/scrollable.rs +++ b/yazi-widgets/src/scrollable.rs @@ -1,4 +1,4 @@ -use crate::Step; +use yazi_parser::Step; pub trait Scrollable { fn total(&self) -> usize;