From b37be0529aca0c49768f0ed731b83af108acd6fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20misaki=20masa?= Date: Sat, 13 Jun 2026 19:37:29 +0800 Subject: [PATCH] feat: new `ui.Input` element (#4040) --- CHANGELOG.md | 2 + Cargo.lock | 132 +++++++++--------------- Cargo.toml | 2 +- yazi-actor/src/cmp/trigger.rs | 2 +- yazi-actor/src/input/close.rs | 26 +++-- yazi-actor/src/input/complete.rs | 9 +- yazi-actor/src/input/escape.rs | 12 ++- yazi-actor/src/input/show.rs | 15 +-- yazi-binding/Cargo.toml | 1 + yazi-binding/src/elements/area.rs | 40 +++---- yazi-binding/src/elements/bar.rs | 15 ++- yazi-binding/src/elements/border.rs | 14 ++- yazi-binding/src/elements/clear.rs | 17 ++- yazi-binding/src/elements/color.rs | 10 +- yazi-binding/src/elements/elements.rs | 1 + yazi-binding/src/elements/fill.rs | 17 ++- yazi-binding/src/elements/gauge.rs | 16 ++- yazi-binding/src/elements/input.rs | 117 +++++++++++++++++++++ yazi-binding/src/elements/line.rs | 45 ++++---- yazi-binding/src/elements/list.rs | 17 ++- yazi-binding/src/elements/mod.rs | 2 +- yazi-binding/src/elements/pos.rs | 34 +++--- yazi-binding/src/elements/renderable.rs | 91 ++++++++-------- yazi-binding/src/elements/row.rs | 22 ++-- yazi-binding/src/elements/span.rs | 22 ++-- yazi-binding/src/elements/spatial.rs | 7 ++ yazi-binding/src/elements/table.rs | 14 ++- yazi-binding/src/elements/text.rs | 57 +++++++--- yazi-binding/src/macros.rs | 7 +- yazi-config/src/popup/input.rs | 4 - yazi-config/src/popup/position.rs | 40 ++++++- yazi-core/src/core.rs | 20 ++-- yazi-core/src/input/guard.rs | 46 +++++++++ yazi-core/src/input/input.rs | 40 +++++-- yazi-core/src/input/mod.rs | 2 +- yazi-core/src/pick/pick.rs | 4 +- yazi-core/src/proxy.rs | 2 +- yazi-core/src/tab/folder.rs | 3 +- yazi-fm/src/app/mod.rs | 2 +- yazi-fm/src/app/render.rs | 42 ++------ yazi-fm/src/app/sync_guard.rs | 52 ++++++++++ yazi-fm/src/cmp/cmp.rs | 2 +- yazi-fm/src/dispatcher.rs | 11 +- yazi-fm/src/executor.rs | 31 +++--- yazi-fm/src/input/input.rs | 10 +- yazi-fm/src/mgr/preview.rs | 1 + yazi-fm/src/renderer.rs | 12 ++- yazi-fm/src/root.rs | 2 +- yazi-fm/src/router.rs | 11 +- yazi-fm/src/spot/spot.rs | 1 + yazi-macro/src/input.rs | 2 +- yazi-parser/src/input/close.rs | 10 +- yazi-parser/src/spark/kind.rs | 4 + yazi-parser/src/spark/spark.rs | 4 + yazi-plugin/preset/components/root.lua | 6 +- yazi-plugin/src/utils/preview.rs | 6 +- yazi-plugin/src/utils/spot.rs | 10 +- yazi-shared/Cargo.toml | 2 +- yazi-shared/src/id.rs | 1 + yazi-shim/src/cell/ro_cell.rs | 1 - yazi-version/Cargo.toml | 2 +- yazi-version/build.rs | 13 +-- yazi-widgets/src/input/actor/casefy.rs | 2 +- yazi-widgets/src/input/actor/escape.rs | 2 +- yazi-widgets/src/input/actor/move.rs | 2 +- yazi-widgets/src/input/actor/replace.rs | 2 +- yazi-widgets/src/input/input.rs | 25 +++-- yazi-widgets/src/input/option.rs | 10 +- yazi-widgets/src/input/snap.rs | 1 - yazi-widgets/src/input/snaps.rs | 2 +- yazi-widgets/src/input/widget.rs | 2 - 71 files changed, 802 insertions(+), 411 deletions(-) create mode 100644 yazi-binding/src/elements/input.rs create mode 100644 yazi-binding/src/elements/spatial.rs create mode 100644 yazi-core/src/input/guard.rs create mode 100644 yazi-fm/src/app/sync_guard.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ce4e5940..dc2923f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/): - Drag and drop ([#4005]) - Bulk create ([#3793]) - Dynamic keymap Lua API ([#4031]) +- New `ui.Input` element ([#4040]) - Image preview with Überzug++ on Niri ([#3990]) - New gait for input `backward` and `forward` actions ([#4012]) @@ -1744,3 +1745,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/): [#4012]: https://github.com/sxyazi/yazi/pull/4012 [#4022]: https://github.com/sxyazi/yazi/pull/4022 [#4031]: https://github.com/sxyazi/yazi/pull/4031 +[#4040]: https://github.com/sxyazi/yazi/pull/4040 diff --git a/Cargo.lock b/Cargo.lock index 0f36b1b3..0b8d5ae9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -462,6 +462,31 @@ dependencies = [ "cipher", ] +[[package]] +name = "bon" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f04f6fef12d70d42a77b1433c9e0f065238479a6cefc4f5bab105e9873a3c3" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d0bd4c2f75335ad98052a37efb54f428b492f64340257143b3429c8a508fa7b" +dependencies = [ + "darling", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.117", +] + [[package]] name = "bs58" version = "0.5.1" @@ -1000,38 +1025,14 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", -] - [[package]] name = "darling" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "darling_core 0.23.0", - "darling_macro 0.23.0", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", + "darling_core", + "darling_macro", ] [[package]] @@ -1047,24 +1048,13 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core 0.20.11", - "quote", - "syn 2.0.117", -] - [[package]] name = "darling_macro" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "darling_core 0.23.0", + "darling_core", "quote", "syn 2.0.117", ] @@ -1133,37 +1123,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "derive_builder" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" -dependencies = [ - "derive_builder_core", - "syn 2.0.117", -] - [[package]] name = "derive_more" version = "2.1.1" @@ -2007,7 +1966,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ - "darling 0.23.0", + "darling", "indoc", "proc-macro2", "quote", @@ -2310,9 +2269,9 @@ checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memmem" @@ -3532,9 +3491,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -3555,9 +3514,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rfc6979" @@ -3940,7 +3899,7 @@ version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" dependencies = [ - "darling 0.23.0", + "darling", "proc-macro2", "quote", "syn 2.0.117", @@ -4847,12 +4806,12 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vergen" -version = "9.1.0" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b849a1f6d8639e8de261e81ee0fc881e3e3620db1af9f2e0da015d4382ceaf75" +checksum = "7bdf18a54cf91b4d98a8e8b67f6321606539fbcdcac02536286ad1de37b53fd2" dependencies = [ "anyhow", - "derive_builder", + "bon", "rustc_version", "rustversion", "time", @@ -4861,12 +4820,12 @@ dependencies = [ [[package]] name = "vergen-gitcl" -version = "9.1.0" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ff3b5300a085d6bcd8fc96a507f706a28ae3814693236c9b409db71a1d15b9" +checksum = "4961429ed12888cb3c6dd20f7dc9508c821091a3ba5fec0156ed5a654c1c4572" dependencies = [ "anyhow", - "derive_builder", + "bon", "rustversion", "time", "vergen", @@ -4875,12 +4834,12 @@ dependencies = [ [[package]] name = "vergen-lib" -version = "9.1.0" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b34a29ba7e9c59e62f229ae1932fb1b8fb8a6fdcc99215a641913f5f5a59a569" +checksum = "910e8471e27130bbc019e9bfa6bda16dfc4c6dd7c5d0793da70a9256caeae984" dependencies = [ "anyhow", - "derive_builder", + "bon", "rustversion", ] @@ -5672,6 +5631,7 @@ dependencies = [ "libc", "mlua", "ordered-float 5.3.0", + "parking_lot", "paste", "ratatui", "serde", diff --git a/Cargo.toml b/Cargo.toml index 8e5b77ef..451b5a5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ paste = "1.0.15" percent-encoding = "2.3.2" rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] } ratatui = { version = "0.30.1", default-features = false, features = [ "std", "layout-cache", "serde", "underline-color", "unstable-rendered-line-info", "unstable-widget-ref" ] } -regex = "1.12.3" +regex = "1.12.4" russh = { version = "0.61.2", default-features = false, features = [ "ring", "rsa" ] } scopeguard = "1.2.0" serde = { version = "1.0.228", features = [ "derive" ] } diff --git a/yazi-actor/src/cmp/trigger.rs b/yazi-actor/src/cmp/trigger.rs index ce9a3468..7c40a551 100644 --- a/yazi-actor/src/cmp/trigger.rs +++ b/yazi-actor/src/cmp/trigger.rs @@ -22,7 +22,7 @@ impl Actor for Trigger { if form.ticket.is_some_and(|t| t != cx.cmp.ticket) { succ!(); } else if form.ticket.is_none() { - cx.cmp.ticket = cx.input.ticket.current(); + cx.cmp.ticket = cx.input.lock().map(|g| g.ticket.current()).unwrap_or_default(); } cx.cmp.handle.take().map(|h| h.abort()); diff --git a/yazi-actor/src/input/close.rs b/yazi-actor/src/input/close.rs index 0c0a8eb5..9f67ad23 100644 --- a/yazi-actor/src/input/close.rs +++ b/yazi-actor/src/input/close.rs @@ -1,7 +1,7 @@ use anyhow::Result; use yazi_macro::{act, render, succ}; -use yazi_parser::input::CloseForm; -use yazi_shared::data::Data; +use yazi_parser::{input::CloseForm, spark::SparkKind}; +use yazi_shared::{Source, data::Data}; use yazi_widgets::input::InputEvent; use crate::{Actor, Ctx}; @@ -14,16 +14,28 @@ impl Actor for Close { const NAME: &str = "close"; fn act(cx: &mut Ctx, form: Self::Form) -> Result { - let input = &mut cx.input; - input.visible = false; - input.ticket.next(); + let Some(mut guard) = cx.input.lock_mut() else { + succ!(); + }; - if let Some(tx) = input.tx.take() { - let value = input.snap().value.clone(); + guard.ticket.next(); + if let Some(tx) = guard.tx.take() { + let value = guard.snap().value.clone(); _ = tx.send(if form.submit { InputEvent::Submit(value) } else { InputEvent::Cancel(value) }); } + drop(guard); + cx.input.main_visible = false; + act!(cmp:close, cx)?; succ!(render!()); } + + fn hook(cx: &Ctx, _form: &Self::Form) -> Option { + match cx.source() { + Source::Key => Some(SparkKind::KeyInputClose), + Source::Ind => Some(SparkKind::IndInputClose), + _ => None, + } + } } diff --git a/yazi-actor/src/input/complete.rs b/yazi-actor/src/input/complete.rs index 5380618d..394c92c8 100644 --- a/yazi-actor/src/input/complete.rs +++ b/yazi-actor/src/input/complete.rs @@ -13,11 +13,14 @@ impl Actor for Complete { const NAME: &str = "complete"; fn act(cx: &mut Ctx, form: Self::Form) -> Result { - let input = &mut cx.input; - if !input.visible || input.ticket.current() != form.ticket { + let Some(mut guard) = cx.input.lock_mut() else { + succ!(); + }; + + if guard.ticket.current() != form.ticket { succ!(); } - act!(complete, input, form) + act!(complete, guard, form) } } diff --git a/yazi-actor/src/input/escape.rs b/yazi-actor/src/input/escape.rs index 495e0528..6977792c 100644 --- a/yazi-actor/src/input/escape.rs +++ b/yazi-actor/src/input/escape.rs @@ -15,16 +15,20 @@ impl Actor for Escape { fn act(cx: &mut Ctx, _: Self::Form) -> Result { use yazi_widgets::input::InputMode as M; - let input = &mut cx.input; + let Some(mut guard) = cx.input.lock_mut() else { + succ!(); + }; - let mode = input.snap().mode; + let (mode, op) = (guard.snap().mode, guard.snap().op); + act!(escape, guard)?; + + drop(guard); match mode { - M::Normal if input.snap_mut().op == InputOp::None => act!(input:close, cx), + M::Normal if 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-actor/src/input/show.rs b/yazi-actor/src/input/show.rs index 9b3ee137..a9cb9a9c 100644 --- a/yazi-actor/src/input/show.rs +++ b/yazi-actor/src/input/show.rs @@ -1,6 +1,5 @@ -use std::ops::DerefMut; - use anyhow::Result; +use ratatui::widgets::Padding; use yazi_macro::{act, render, succ}; use yazi_shared::data::Data; use yazi_widgets::input::InputOpt; @@ -14,14 +13,16 @@ impl Actor for Show { const NAME: &str = "show"; - fn act(cx: &mut Ctx, form: Self::Form) -> Result { + fn act(cx: &mut Ctx, mut form: Self::Form) -> Result { act!(input:close, cx)?; let input = &mut cx.input; - input.visible = true; - input.title = form.cfg.title.clone(); - input.position = form.cfg.position; - *input.deref_mut() = yazi_widgets::input::Input::new(form)?; + input.main_visible = true; + input.main_title = form.cfg.title.clone(); + input.main_position = form.cfg.position; + + form.cfg.position = form.cfg.position.padding(Padding::uniform(1)); + input.main = yazi_widgets::input::Input::new(form)?; succ!(render!()); } diff --git a/yazi-binding/Cargo.toml b/yazi-binding/Cargo.toml index 52506079..f5fdd02e 100644 --- a/yazi-binding/Cargo.toml +++ b/yazi-binding/Cargo.toml @@ -37,6 +37,7 @@ hashbrown = { workspace = true } inventory = { workspace = true } mlua = { workspace = true } ordered-float = { workspace = true } +parking_lot = { workspace = true } paste = { workspace = true } ratatui = { workspace = true } serde = { workspace = true } diff --git a/yazi-binding/src/elements/area.rs b/yazi-binding/src/elements/area.rs index f2cf04cd..5abb3991 100644 --- a/yazi-binding/src/elements/area.rs +++ b/yazi-binding/src/elements/area.rs @@ -8,31 +8,38 @@ const EXPECTED: &str = "expected a Pos or Rect"; #[derive(Clone, Copy)] pub enum Area { - Pos(Pos), Rect(Rect), + Pos(Pos), } impl Default for Area { fn default() -> Self { Self::Rect(Default::default()) } } +impl Debug for Area { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Rect(rect) => write!(f, "{:?}", **rect), + Self::Pos(pos) => write!(f, "{:?}", **pos), + } + } +} + impl Area { pub fn size(self) -> ratatui::layout::Size { match self { - Self::Pos(pos) => { - ratatui::layout::Size { width: pos.offset.width, height: pos.offset.height } - } Self::Rect(rect) => (*rect).into(), + Self::Pos(pos) => ratatui::layout::Size { width: pos.width, height: pos.height }, } } pub fn inner(self, padding: ratatui::widgets::Padding) -> Self { match self { + Self::Rect(rect) => Self::Rect(rect.pad(padding.into())), Self::Pos(mut pos) => { pos.pad += padding; Self::Pos(pos) } - Self::Rect(rect) => Self::Rect(rect.pad(padding.into())), } } @@ -41,8 +48,8 @@ impl Area { f: impl FnOnce(yazi_config::popup::Position) -> ratatui::layout::Rect, ) -> ratatui::layout::Rect { match self { - Self::Pos(pos) => *Rect(f(*pos)).pad(pos.pad), Self::Rect(rect) => *rect, + Self::Pos(pos) => *Rect(f(*pos)).pad(pos.pad), } } } @@ -55,10 +62,14 @@ impl From for Area { fn from(rect: ratatui::layout::Rect) -> Self { Self::Rect(rect.into()) } } -impl TryFrom for Area { +impl From for Area { + fn from(pos: yazi_config::popup::Position) -> Self { Self::Pos(pos.into()) } +} + +impl TryFrom<&AnyUserData> for Area { type Error = mlua::Error; - fn try_from(value: AnyUserData) -> Result { + fn try_from(value: &AnyUserData) -> Result { Ok(if let Ok(rect) = value.borrow::() { Self::Rect(*rect) } else if let Ok(pos) = value.borrow::() { @@ -72,7 +83,7 @@ impl TryFrom for Area { impl FromLua for Area { fn from_lua(value: Value, _: &Lua) -> mlua::Result { match value { - Value::UserData(ud) => Self::try_from(ud), + Value::UserData(ud) => Self::try_from(&ud), _ => Err(EXPECTED.into_lua_err()), } } @@ -81,17 +92,8 @@ impl FromLua for Area { impl IntoLua for Area { fn into_lua(self, lua: &Lua) -> mlua::Result { match self { - Self::Pos(pos) => pos.into_lua(lua), Self::Rect(rect) => rect.into_lua(lua), - } - } -} - -impl Debug for Area { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Pos(pos) => write!(f, "{:?}", **pos), - Self::Rect(rect) => write!(f, "{:?}", **rect), + Self::Pos(pos) => pos.into_lua(lua), } } } diff --git a/yazi-binding/src/elements/bar.rs b/yazi-binding/src/elements/bar.rs index eb94303b..42d4a549 100644 --- a/yazi-binding/src/elements/bar.rs +++ b/yazi-binding/src/elements/bar.rs @@ -2,10 +2,11 @@ use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMetho use ratatui::widgets::{Borders, Widget}; use super::{Area, Edge}; +use crate::elements::Spatial; #[derive(Clone, Debug, Default)] pub struct Bar { - pub(super) area: Area, + area: Area, edge: Edge, symbol: String, @@ -23,6 +24,18 @@ impl Bar { } } +impl TryFrom<&AnyUserData> for Bar { + type Error = mlua::Error; + + fn try_from(value: &AnyUserData) -> Result { value.take() } +} + +impl Spatial for Bar { + fn area(&self) -> Area { self.area } + + fn set_area(&mut self, area: Area) { self.area = area; } +} + impl Widget for Bar { fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) where diff --git a/yazi-binding/src/elements/border.rs b/yazi-binding/src/elements/border.rs index a56ac58d..bb1e2497 100644 --- a/yazi-binding/src/elements/border.rs +++ b/yazi-binding/src/elements/border.rs @@ -2,7 +2,7 @@ use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMetho use ratatui::widgets::{Borders, Widget}; use super::{Area, Edge}; -use crate::elements::Line; +use crate::elements::{Line, Spatial}; // Type const PLAIN: u8 = 0; @@ -44,6 +44,18 @@ impl Border { } } +impl TryFrom<&AnyUserData> for Border { + type Error = mlua::Error; + + fn try_from(value: &AnyUserData) -> Result { value.take() } +} + +impl Spatial for Border { + fn area(&self) -> Area { self.area } + + fn set_area(&mut self, area: Area) { self.area = area; } +} + impl Widget for Border { fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) where diff --git a/yazi-binding/src/elements/clear.rs b/yazi-binding/src/elements/clear.rs index 5bcf0c2b..dac63093 100644 --- a/yazi-binding/src/elements/clear.rs +++ b/yazi-binding/src/elements/clear.rs @@ -1,11 +1,12 @@ -use mlua::{IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; +use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; use ratatui::widgets::Widget; use super::Area; +use crate::elements::Spatial; #[derive(Clone, Copy, Debug, Default)] pub struct Clear { - pub area: Area, + area: Area, } impl Clear { @@ -19,6 +20,18 @@ impl Clear { } } +impl TryFrom<&AnyUserData> for Clear { + type Error = mlua::Error; + + fn try_from(value: &AnyUserData) -> Result { Ok(*value.borrow()?) } +} + +impl Spatial for Clear { + fn area(&self) -> Area { self.area } + + fn set_area(&mut self, area: Area) { self.area = area; } +} + impl Widget for Clear { fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) where diff --git a/yazi-binding/src/elements/color.rs b/yazi-binding/src/elements/color.rs index 503f6d5d..cbd0eedd 100644 --- a/yazi-binding/src/elements/color.rs +++ b/yazi-binding/src/elements/color.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use mlua::{ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, Value}; +use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, Value}; #[derive(Clone, Copy, Default)] pub struct Color(pub ratatui::style::Color); @@ -16,12 +16,18 @@ impl Color { } } +impl TryFrom<&AnyUserData> for Color { + type Error = mlua::Error; + + fn try_from(ud: &AnyUserData) -> Result { Ok(*ud.borrow()?) } +} + impl FromLua for Color { fn from_lua(value: Value, _: &Lua) -> mlua::Result { Ok(Self(match value { Value::Nil => ratatui::style::Color::Reset, Value::String(s) => ratatui::style::Color::from_str(&s.to_str()?).into_lua_err()?, - Value::UserData(ud) => ud.borrow::()?.0, + Value::UserData(ud) => return Self::try_from(&ud), _ => Err("expected a Color".into_lua_err())?, })) } diff --git a/yazi-binding/src/elements/elements.rs b/yazi-binding/src/elements/elements.rs index 7d73a134..6ef49bb4 100644 --- a/yazi-binding/src/elements/elements.rs +++ b/yazi-binding/src/elements/elements.rs @@ -14,6 +14,7 @@ pub fn compose(p_get: ComposerGet, p_set: ComposerSet) -> Composer super::Edge::compose(lua)?, b"Fill" => super::Fill::compose(lua)?, b"Gauge" => super::Gauge::compose(lua)?, + b"Input" => super::Input::compose(lua)?, b"Layout" => super::Layout::compose(lua)?, b"Line" => super::Line::compose(lua)?, b"List" => super::List::compose(lua)?, diff --git a/yazi-binding/src/elements/fill.rs b/yazi-binding/src/elements/fill.rs index cdc23eb7..6ca356b9 100644 --- a/yazi-binding/src/elements/fill.rs +++ b/yazi-binding/src/elements/fill.rs @@ -1,11 +1,12 @@ -use mlua::{IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; +use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; use ratatui::widgets::Widget; use super::Area; +use crate::elements::Spatial; #[derive(Clone, Copy, Debug, Default)] pub struct Fill { - pub area: Area, + area: Area, style: ratatui::style::Style, } @@ -22,6 +23,18 @@ impl Fill { } } +impl TryFrom<&AnyUserData> for Fill { + type Error = mlua::Error; + + fn try_from(value: &AnyUserData) -> Result { Ok(*value.borrow()?) } +} + +impl Spatial for Fill { + fn area(&self) -> Area { self.area } + + fn set_area(&mut self, area: Area) { self.area = area; } +} + impl Widget for Fill { fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) where diff --git a/yazi-binding/src/elements/gauge.rs b/yazi-binding/src/elements/gauge.rs index d9eb611c..4d9dd3d6 100644 --- a/yazi-binding/src/elements/gauge.rs +++ b/yazi-binding/src/elements/gauge.rs @@ -2,11 +2,11 @@ use mlua::{AnyUserData, ExternalError, IntoLua, Lua, MetaMethod, Table, UserData use ratatui::widgets::Widget; use super::{Area, Span}; -use crate::Style; +use crate::{Style, elements::Spatial}; #[derive(Clone, Debug, Default)] pub struct Gauge { - pub(super) area: Area, + area: Area, ratio: f64, label: Option>, @@ -25,6 +25,18 @@ impl Gauge { } } +impl TryFrom<&AnyUserData> for Gauge { + type Error = mlua::Error; + + fn try_from(value: &AnyUserData) -> Result { value.take() } +} + +impl Spatial for Gauge { + fn area(&self) -> Area { self.area } + + fn set_area(&mut self, area: Area) { self.area = area; } +} + impl Widget for Gauge { fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) where diff --git a/yazi-binding/src/elements/input.rs b/yazi-binding/src/elements/input.rs new file mode 100644 index 00000000..f6ea1399 --- /dev/null +++ b/yazi-binding/src/elements/input.rs @@ -0,0 +1,117 @@ +use std::sync::Arc; + +use mlua::{AnyUserData, ExternalError, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; +use parking_lot::Mutex; +use ratatui::widgets::Widget; + +use super::Area; +use crate::{elements::Spatial, impl_area_method}; + +const EXPECTED: &str = "expected a table or a Input"; + +#[derive(Clone, Debug, Default)] +pub struct Input { + pub focus: bool, + + inner: Arc>, +} + +impl Input { + pub fn compose(lua: &Lua) -> mlua::Result { + let new = lua.create_function(|_, (_, input): (Table, Self)| Ok(input))?; + + let input = lua.create_table()?; + input.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?; + input.into_lua(lua) + } +} + +impl From<&Input> for Arc> { + fn from(input: &Input) -> Self { input.inner.clone() } +} + +impl TryFrom<&AnyUserData> for Input { + type Error = mlua::Error; + + fn try_from(value: &AnyUserData) -> Result { + Ok(value.borrow::()?.clone()) + } +} + +impl TryFrom for Input { + type Error = mlua::Error; + + fn try_from(tb: Table) -> Result { + let input = yazi_widgets::input::Input::new(yazi_widgets::input::InputOpt { + cfg: yazi_config::popup::InputCfg { + title: String::new(), + value: tb.raw_get("value")?, + cursor: None, + obscure: false, + position: Default::default(), + realtime: false, + completion: false, + }, + tx: None, + })?; + + Ok(Self { inner: Arc::new(Mutex::new(input)), ..Default::default() }) + } +} + +impl Spatial for Input { + fn area(&self) -> Area { self.inner.lock().pos.into() } + + fn set_area(&mut self, area: Area) { + self.inner.lock().repos(match area { + Area::Rect(rect) => yazi_config::popup::Position { + origin: yazi_config::popup::Origin::TopLeft, + offset: yazi_config::popup::Offset { + x: rect.x as i16, + y: rect.y as i16, + width: rect.width, + height: rect.height, + }, + }, + Area::Pos(pos) => pos.into(), + }); + } +} + +impl Widget for Input { + fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) + where + Self: Sized, + { + (&self).render(rect, buf); + } +} + +impl Widget for &Input { + fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) + where + Self: Sized, + { + self.inner.lock().render(rect, buf) + } +} + +impl FromLua for Input { + fn from_lua(value: Value, _: &Lua) -> mlua::Result { + match value { + Value::Table(tb) => Self::try_from(tb), + _ => Err(EXPECTED.into_lua_err()), + } + } +} + +impl UserData for Input { + fn add_methods>(methods: &mut M) { + impl_area_method!(methods); + + methods.add_function("focus", |lua, (ud, focus): (AnyUserData, bool)| { + ud.borrow_mut::()?.focus = focus; + ud.into_lua(lua) + }); + } +} diff --git a/yazi-binding/src/elements/line.rs b/yazi-binding/src/elements/line.rs index 2d4fd5de..31cb4f21 100644 --- a/yazi-binding/src/elements/line.rs +++ b/yazi-binding/src/elements/line.rs @@ -6,13 +6,13 @@ use ratatui::widgets::Widget; use unicode_width::UnicodeWidthChar; use super::{Area, Span}; -use crate::elements::Align; +use crate::elements::{Align, Spatial}; const EXPECTED: &str = "expected a string, Span, Line, or a table of them"; #[derive(Clone, Debug, Default)] pub struct Line { - pub(super) area: Area, + area: Area, pub(super) inner: ratatui::text::Line<'static>, } @@ -57,6 +57,20 @@ impl From> for Line { } } +impl TryFrom<&AnyUserData> for Line { + type Error = mlua::Error; + + fn try_from(value: &AnyUserData) -> Result { + Ok(if let Ok(line) = value.take() { + line + } else if let Ok(Span(span)) = value.take() { + Self { inner: span.into(), ..Default::default() } + } else { + Err(EXPECTED.into_lua_err())? + }) + } +} + impl TryFrom
for Line { type Error = mlua::Error; @@ -86,6 +100,12 @@ impl From for ratatui::text::Line<'static> { fn from(value: Line) -> Self { value.inner } } +impl Spatial for Line { + fn area(&self) -> Area { self.area } + + fn set_area(&mut self, area: Area) { self.area = area; } +} + impl Widget for Line { fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) where @@ -106,22 +126,11 @@ impl Widget for &Line { impl FromLua for Line { fn from_lua(value: Value, _: &Lua) -> mlua::Result { - Ok(Self { - inner: match value { - Value::Table(tb) => return Self::try_from(tb), - Value::String(s) => s.to_string_lossy().into(), - Value::UserData(ud) => { - if let Ok(Span(span)) = ud.take() { - span.into() - } else if let Ok(line) = ud.take() { - return Ok(line); - } else { - Err(EXPECTED.into_lua_err())? - } - } - _ => Err(EXPECTED.into_lua_err())?, - }, - ..Default::default() + Ok(match value { + Value::Table(tb) => Self::try_from(tb)?, + Value::String(s) => Self { inner: s.to_string_lossy().into(), ..Default::default() }, + Value::UserData(ud) => Self::try_from(&ud)?, + _ => Err(EXPECTED.into_lua_err())?, }) } } diff --git a/yazi-binding/src/elements/list.rs b/yazi-binding/src/elements/list.rs index 09f7b973..55c83972 100644 --- a/yazi-binding/src/elements/list.rs +++ b/yazi-binding/src/elements/list.rs @@ -1,12 +1,13 @@ -use mlua::{IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; +use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; use ratatui::widgets::Widget; use super::{Area, Text}; +use crate::elements::Spatial; // --- List #[derive(Clone, Debug, Default)] pub struct List { - pub(super) area: Area, + area: Area, inner: ratatui::widgets::List<'static>, } @@ -24,6 +25,18 @@ impl List { } } +impl TryFrom<&AnyUserData> for List { + type Error = mlua::Error; + + fn try_from(value: &AnyUserData) -> Result { value.take() } +} + +impl Spatial for List { + fn area(&self) -> Area { self.area } + + fn set_area(&mut self, area: Area) { self.area = area; } +} + impl Widget for List { fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) where diff --git a/yazi-binding/src/elements/mod.rs b/yazi-binding/src/elements/mod.rs index b9c31b3d..2aad50fe 100644 --- a/yazi-binding/src/elements/mod.rs +++ b/yazi-binding/src/elements/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(align area bar border cell clear color constraint edge elements fill gauge layout line list pad pos rect renderable renderables row span table text wrap); +yazi_macro::mod_flat!(align area bar border cell clear color constraint edge elements fill gauge input layout line list pad pos rect renderable renderables row span spatial table text wrap); diff --git a/yazi-binding/src/elements/pos.rs b/yazi-binding/src/elements/pos.rs index d509d945..24a4a1e8 100644 --- a/yazi-binding/src/elements/pos.rs +++ b/yazi-binding/src/elements/pos.rs @@ -30,6 +30,14 @@ impl From for yazi_config::popup::Position { fn from(value: Pos) -> Self { value.inner } } +impl TryFrom<&AnyUserData> for Pos { + type Error = mlua::Error; + + fn try_from(ud: &AnyUserData) -> Result { + if let Ok(pos) = ud.borrow() { Ok(*pos) } else { Err(EXPECTED.into_lua_err()) } + } +} + impl TryFrom
for Pos { type Error = mlua::Error; @@ -50,17 +58,11 @@ impl TryFrom
for Pos { impl FromLua for Pos { fn from_lua(value: Value, _: &Lua) -> mlua::Result { - Ok(match value { - Value::Table(tbl) => Self::try_from(tbl)?, - Value::UserData(ud) => { - if let Ok(pos) = ud.borrow() { - *pos - } else { - Err(EXPECTED.into_lua_err())? - } - } - _ => Err(EXPECTED.into_lua_err())?, - }) + match value { + Value::Table(tbl) => Self::try_from(tbl), + Value::UserData(ud) => Self::try_from(&ud), + _ => Err(EXPECTED.into_lua_err()), + } } } @@ -75,7 +77,7 @@ impl Pos { } pub fn with_height(mut self, height: u16) -> Self { - self.inner.offset.height = height; + self.inner.height = height; self } } @@ -84,10 +86,10 @@ impl UserData for Pos { fn add_fields>(fields: &mut F) { // TODO: cache fields.add_field_method_get("1", |_, me| Ok(me.origin.into_str())); - fields.add_field_method_get("x", |_, me| Ok(me.offset.x)); - fields.add_field_method_get("y", |_, me| Ok(me.offset.y)); - fields.add_field_method_get("w", |_, me| Ok(me.offset.width)); - fields.add_field_method_get("h", |_, me| Ok(me.offset.height)); + fields.add_field_method_get("x", |_, me| Ok(me.x)); + fields.add_field_method_get("y", |_, me| Ok(me.y)); + fields.add_field_method_get("w", |_, me| Ok(me.width)); + fields.add_field_method_get("h", |_, me| Ok(me.height)); } fn add_methods>(methods: &mut M) { diff --git a/yazi-binding/src/elements/renderable.rs b/yazi-binding/src/elements/renderable.rs index 8e9008de..cb7f889e 100644 --- a/yazi-binding/src/elements/renderable.rs +++ b/yazi-binding/src/elements/renderable.rs @@ -3,8 +3,8 @@ use std::any::TypeId; use mlua::{AnyUserData, ExternalError, FromLua, Lua, Value}; use ratatui::widgets::Widget; -use super::{Area, Bar, Border, Clear, Fill, Gauge, Line, List, Table, Text}; -use crate::Error; +use super::{Area, Bar, Border, Clear, Fill, Gauge, Input, Line, List, Table, Text}; +use crate::{Error, elements::Spatial}; #[derive(Clone, Debug)] pub enum Renderable { @@ -15,38 +15,14 @@ pub enum Renderable { Clear(Clear), Fill(Fill), Border(Border), + Input(Input), Gauge(Box), Table(Box
), } impl Renderable { - pub fn area(&self) -> Area { - match self { - Self::Line(line) => line.area, - Self::Text(text) => text.area, - Self::List(list) => list.area, - Self::Bar(bar) => bar.area, - Self::Clear(clear) => clear.area, - Self::Fill(fill) => fill.area, - Self::Border(border) => border.area, - Self::Gauge(gauge) => gauge.area, - Self::Table(table) => table.area, - } - } - pub fn with_area(mut self, area: impl Into) -> Self { - let area = area.into(); - match &mut self { - Self::Line(line) => line.area = area, - Self::Text(text) => text.area = area, - Self::List(list) => list.area = area, - Self::Bar(bar) => bar.area = area, - Self::Clear(clear) => clear.area = area, - Self::Fill(fill) => fill.area = area, - Self::Border(border) => border.area = area, - Self::Gauge(gauge) => gauge.area = area, - Self::Table(table) => table.area = area, - } + self.set_area(area.into()); self } @@ -64,15 +40,16 @@ impl TryFrom<&AnyUserData> for Renderable { fn try_from(ud: &AnyUserData) -> Result { Ok(match ud.type_id() { - Some(t) if t == TypeId::of::() => Self::Line(ud.take()?), - Some(t) if t == TypeId::of::() => Self::Text(ud.take()?), - Some(t) if t == TypeId::of::() => Self::List(Box::new(ud.take()?)), - Some(t) if t == TypeId::of::() => Self::Bar(ud.take()?), - Some(t) if t == TypeId::of::() => Self::Clear(ud.take()?), - Some(t) if t == TypeId::of::() => Self::Fill(ud.take()?), - Some(t) if t == TypeId::of::() => Self::Border(ud.take()?), - Some(t) if t == TypeId::of::() => Self::Gauge(Box::new(ud.take()?)), - Some(t) if t == TypeId::of::
() => Self::Table(Box::new(ud.take()?)), + Some(t) if t == TypeId::of::() => Self::Line(ud.try_into()?), + Some(t) if t == TypeId::of::() => Self::Text(ud.try_into()?), + Some(t) if t == TypeId::of::() => Self::List(Box::new(ud.try_into()?)), + Some(t) if t == TypeId::of::() => Self::Bar(ud.try_into()?), + Some(t) if t == TypeId::of::() => Self::Clear(ud.try_into()?), + Some(t) if t == TypeId::of::() => Self::Fill(ud.try_into()?), + Some(t) if t == TypeId::of::() => Self::Border(ud.try_into()?), + Some(t) if t == TypeId::of::() => Self::Input(ud.try_into()?), + Some(t) if t == TypeId::of::() => Self::Gauge(Box::new(ud.try_into()?)), + Some(t) if t == TypeId::of::
() => Self::Table(Box::new(ud.try_into()?)), _ => Err(format!("expected a renderable userdata, not: {ud:#?}").into_lua_err())?, }) } @@ -80,11 +57,39 @@ impl TryFrom<&AnyUserData> for Renderable { impl From for Renderable { fn from(error: Error) -> Self { - Self::Text(Text { - inner: error.into_string().into(), - wrap: ratatui::widgets::Wrap { trim: false }.into(), - ..Default::default() - }) + Self::Text(Text::from(error.into_string()).wrap(ratatui::widgets::Wrap { trim: false })) + } +} + +impl Spatial for Renderable { + fn area(&self) -> Area { + match self { + Self::Line(line) => line.area(), + Self::Text(text) => text.area(), + Self::List(list) => list.area(), + Self::Bar(bar) => bar.area(), + Self::Clear(clear) => clear.area(), + Self::Fill(fill) => fill.area(), + Self::Border(border) => border.area(), + Self::Input(input) => input.area(), + Self::Gauge(gauge) => gauge.area(), + Self::Table(table) => table.area(), + } + } + + fn set_area(&mut self, area: Area) { + match self { + Self::Line(line) => line.set_area(area), + Self::Text(text) => text.set_area(area), + Self::List(list) => list.set_area(area), + Self::Bar(bar) => bar.set_area(area), + Self::Clear(clear) => clear.set_area(area), + Self::Fill(fill) => fill.set_area(area), + Self::Border(border) => border.set_area(area), + Self::Input(input) => input.set_area(area), + Self::Gauge(gauge) => gauge.set_area(area), + Self::Table(table) => table.set_area(area), + } } } @@ -101,6 +106,7 @@ impl Widget for Renderable { Self::Clear(clear) => clear.render(rect, buf), Self::Fill(fill) => fill.render(rect, buf), Self::Border(border) => border.render(rect, buf), + Self::Input(input) => input.render(rect, buf), Self::Gauge(gauge) => gauge.render(rect, buf), Self::Table(table) => table.render(rect, buf), } @@ -120,6 +126,7 @@ impl Widget for &Renderable { Renderable::Clear(clear) => clear.render(rect, buf), Renderable::Fill(fill) => fill.render(rect, buf), Renderable::Border(border) => border.render(rect, buf), + Renderable::Input(input) => input.render(rect, buf), Renderable::Gauge(gauge) => (&**gauge).render(rect, buf), Renderable::Table(table) => (&**table).render(rect, buf), } diff --git a/yazi-binding/src/elements/row.rs b/yazi-binding/src/elements/row.rs index 969f81ef..9b31f5de 100644 --- a/yazi-binding/src/elements/row.rs +++ b/yazi-binding/src/elements/row.rs @@ -36,18 +36,20 @@ impl From for ratatui::widgets::Row<'static> { } } +impl TryFrom<&AnyUserData> for Row { + type Error = mlua::Error; + + fn try_from(value: &AnyUserData) -> Result { + if let Ok(row) = value.take() { Ok(row) } else { Err(EXPECTED.into_lua_err()) } + } +} + impl FromLua for Row { fn from_lua(value: Value, _: &Lua) -> mlua::Result { - Ok(match value { - Value::UserData(ud) => { - if let Ok(row) = ud.take() { - row - } else { - Err(EXPECTED.into_lua_err())? - } - } - _ => Err(EXPECTED.into_lua_err())?, - }) + match value { + Value::UserData(ud) => Self::try_from(&ud), + _ => Err(EXPECTED.into_lua_err()), + } } } diff --git a/yazi-binding/src/elements/span.rs b/yazi-binding/src/elements/span.rs index 717a0d73..7b53f8ce 100644 --- a/yazi-binding/src/elements/span.rs +++ b/yazi-binding/src/elements/span.rs @@ -58,19 +58,21 @@ impl Span { } } +impl TryFrom<&AnyUserData> for Span { + type Error = mlua::Error; + + fn try_from(value: &AnyUserData) -> Result { + if let Ok(span) = value.take() { Ok(span) } else { Err(EXPECTED.into_lua_err()) } + } +} + impl FromLua for Span { fn from_lua(value: Value, _: &Lua) -> mlua::Result { - Ok(Self(match value { - Value::String(s) => s.to_string_lossy().into(), - Value::UserData(ud) => { - if let Ok(Self(span)) = ud.take() { - span - } else { - Err(EXPECTED.into_lua_err())? - } - } + Ok(match value { + Value::String(s) => Self(s.to_string_lossy().into()), + Value::UserData(ud) => Self::try_from(&ud)?, _ => Err(EXPECTED.into_lua_err())?, - })) + }) } } diff --git a/yazi-binding/src/elements/spatial.rs b/yazi-binding/src/elements/spatial.rs new file mode 100644 index 00000000..665e44d4 --- /dev/null +++ b/yazi-binding/src/elements/spatial.rs @@ -0,0 +1,7 @@ +use crate::elements::Area; + +pub trait Spatial { + fn area(&self) -> Area; + + fn set_area(&mut self, area: Area); +} diff --git a/yazi-binding/src/elements/table.rs b/yazi-binding/src/elements/table.rs index f7defacf..d8077904 100644 --- a/yazi-binding/src/elements/table.rs +++ b/yazi-binding/src/elements/table.rs @@ -2,12 +2,12 @@ use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, UserData, UserDataMethods, Val use ratatui::widgets::{StatefulWidget, Widget}; use super::{Area, Row}; -use crate::{Style, elements::Constraint}; +use crate::{Style, elements::{Constraint, Spatial}}; // --- Table #[derive(Clone, Debug, Default)] pub struct Table { - pub area: Area, + area: Area, rows: Vec, header: Option>, @@ -60,10 +60,16 @@ impl Table { } } -impl TryFrom for Table { +impl TryFrom<&AnyUserData> for Table { type Error = mlua::Error; - fn try_from(value: AnyUserData) -> Result { value.take() } + fn try_from(value: &AnyUserData) -> Result { value.take() } +} + +impl Spatial for Table { + fn area(&self) -> Area { self.area } + + fn set_area(&mut self, area: Area) { self.area = area; } } impl Widget for Table { diff --git a/yazi-binding/src/elements/text.rs b/yazi-binding/src/elements/text.rs index 64b0f8cf..cf532308 100644 --- a/yazi-binding/src/elements/text.rs +++ b/yazi-binding/src/elements/text.rs @@ -3,15 +3,16 @@ use std::{any::TypeId, mem}; use ansi_to_tui::IntoText; use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; use ratatui::widgets::Widget; +use yazi_shared::SStr; use super::{Area, Line, Span, Wrap}; -use crate::{Error, elements::Align}; +use crate::{Error, elements::{Align, Spatial}}; const EXPECTED: &str = "expected a string, Line, Span, or a table of them"; #[derive(Clone, Debug, Default)] pub struct Text { - pub area: Area, + area: Area, // TODO: block pub inner: ratatui::text::Text<'static>, @@ -31,6 +32,19 @@ impl Text { text.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?; text.into_lua(lua) } + + pub fn wrap(mut self, wrap: impl Into) -> Self { + self.wrap = wrap.into(); + self + } +} + +impl From> for Text { + fn from(inner: ratatui::text::Text<'static>) -> Self { Self { inner, ..Default::default() } } +} + +impl From for Text { + fn from(value: SStr) -> Self { Self { inner: value.into(), ..Default::default() } } } impl TryFrom
for Text { @@ -74,6 +88,12 @@ impl From for ratatui::widgets::Paragraph<'static> { } } +impl Spatial for Text { + fn area(&self) -> Area { self.area } + + fn set_area(&mut self, area: Area) { self.area = area; } +} + impl Widget for Text { fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) where @@ -100,24 +120,33 @@ impl Widget for &Text { } } -impl FromLua for Text { - fn from_lua(value: Value, _: &Lua) -> mlua::Result { - let inner = match value { - Value::Table(tb) => return Self::try_from(tb), - Value::String(s) => s.to_string_lossy().into(), - Value::UserData(ud) => match ud.type_id() { - Some(t) if t == TypeId::of::() => ud.take::()?.inner.into(), - Some(t) if t == TypeId::of::() => ud.take::()?.0.into(), - Some(t) if t == TypeId::of::() => return ud.take(), - Some(t) if t == TypeId::of::() => ud.take::()?.into_string().into(), - _ => Err(EXPECTED.into_lua_err())?, - }, +impl TryFrom<&AnyUserData> for Text { + type Error = mlua::Error; + + fn try_from(value: &AnyUserData) -> Result { + let inner = match value.type_id() { + Some(t) if t == TypeId::of::() => return value.take(), + Some(t) if t == TypeId::of::() => value.take::()?.inner.into(), + Some(t) if t == TypeId::of::() => value.take::()?.0.into(), + Some(t) if t == TypeId::of::() => value.take::()?.into_string().into(), _ => Err(EXPECTED.into_lua_err())?, }; + Ok(Self { inner, ..Default::default() }) } } +impl FromLua for Text { + fn from_lua(value: Value, _: &Lua) -> mlua::Result { + Ok(match value { + Value::Table(tb) => Self::try_from(tb)?, + Value::String(s) => Self { inner: s.to_string_lossy().into(), ..Default::default() }, + Value::UserData(ud) => Self::try_from(&ud)?, + _ => Err(EXPECTED.into_lua_err())?, + }) + } +} + impl UserData for Text { fn add_methods>(methods: &mut M) { crate::impl_area_method!(methods); diff --git a/yazi-binding/src/macros.rs b/yazi-binding/src/macros.rs index 18b415e6..bba655bf 100644 --- a/yazi-binding/src/macros.rs +++ b/yazi-binding/src/macros.rs @@ -50,11 +50,14 @@ macro_rules! impl_area_method { "area", |lua, (ud, area): (mlua::AnyUserData, Option)| { use mlua::IntoLua; + + use crate::elements::Spatial; + if let Some(v) = area { - ud.borrow_mut::()?.area = $crate::elements::Area::try_from(v)?; + ud.borrow_mut::()?.set_area((&v).try_into()?); ud.into_lua(lua) } else { - ud.borrow::()?.area.into_lua(lua) + ud.borrow::()?.area().into_lua(lua) } }, ); diff --git a/yazi-config/src/popup/input.rs b/yazi-config/src/popup/input.rs index 55e335d7..71aababc 100644 --- a/yazi-config/src/popup/input.rs +++ b/yazi-config/src/popup/input.rs @@ -42,7 +42,3 @@ pub struct Input { pub shell_origin: Origin, pub shell_offset: Offset, } - -impl Input { - pub const fn border(&self) -> u16 { 2 } -} diff --git a/yazi-config/src/popup/position.rs b/yazi-config/src/popup/position.rs index 3ea8bd89..fa48c69b 100644 --- a/yazi-config/src/popup/position.rs +++ b/yazi-config/src/popup/position.rs @@ -1,4 +1,6 @@ -use ratatui::layout::Rect; +use std::ops::{Deref, DerefMut}; + +use ratatui::{layout::Rect, widgets::Padding}; use yazi_term::Dimension; use super::{Offset, Origin}; @@ -9,6 +11,16 @@ pub struct Position { pub offset: Offset, } +impl Deref for Position { + type Target = Offset; + + fn deref(&self) -> &Self::Target { &self.offset } +} + +impl DerefMut for Position { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.offset } +} + impl Position { pub const fn new(origin: Origin, offset: Offset) -> Self { Self { origin, offset } } @@ -62,4 +74,30 @@ impl Position { height: height.min(rows.saturating_sub(new_y)), } } + + pub fn padding(mut self, padding: Padding) -> Self { + use Origin::*; + let h_reduction = padding.left + padding.right; + let v_reduction = padding.top + padding.bottom; + + self.x = self.x.saturating_add_unsigned(padding.left); + self.y = self.y.saturating_add_unsigned(padding.top); + + self.width = self.width.saturating_sub(h_reduction); + self.height = self.height.saturating_sub(v_reduction); + + self.x = self.x.saturating_sub_unsigned(match self.origin { + TopCenter | BottomCenter | Center => h_reduction / 2, + TopRight | BottomRight => h_reduction, + _ => 0, + }); + + self.y = self.y.saturating_sub_unsigned(match self.origin { + BottomLeft | BottomCenter | BottomRight => v_reduction, + Center => v_reduction / 2, + _ => 0, + }); + + self + } } diff --git a/yazi-core/src/core.rs b/yazi-core/src/core.rs index fcb940cd..0e924c26 100644 --- a/yazi-core/src/core.rs +++ b/yazi-core/src/core.rs @@ -1,8 +1,8 @@ -use ratatui::layout::{Position, Rect}; +use ratatui::layout::{Margin, Position, Rect}; use yazi_shared::Layer; use yazi_term::CursorStyle; -use crate::{cmp::Cmp, confirm::Confirm, help::Help, input::Input, mgr::Mgr, notify::Notify, pick::Pick, tab::{Folder, Tab}, tasks::Tasks, which::Which}; +use crate::{cmp::Cmp, confirm::Confirm, help::Help, input::{Input, InputGuard}, mgr::Mgr, notify::Notify, pick::Pick, tab::{Folder, Tab}, tasks::Tasks, which::Which}; pub struct Core { pub mgr: Mgr, @@ -32,16 +32,18 @@ impl Core { } pub fn cursor(&self) -> Option<(Position, CursorStyle)> { - if self.input.visible { - let Rect { x, y, .. } = self.mgr.area(self.input.position); - return Some(( - Position { x: x + 1 + self.input.cursor(), y: y + 1 }, - self.input.cursor_shape(), - )); + if let Some(guard) = self.input.lock() { + let Rect { x, y, .. } = match &guard { + InputGuard::Main(_) => self.mgr.area(self.input.main_position).inner(Margin::new(1, 1)), + InputGuard::Alt(alt) => self.mgr.area(alt.pos), + }; + return Some((Position { x: x + guard.cursor(), y }, guard.cursor_shape())); } + if let Some((x, y)) = self.help.cursor() { return Some((Position { x, y }, self.help.cursor_shape())); } + None } @@ -54,7 +56,7 @@ impl Core { Layer::Help } else if self.confirm.visible { Layer::Confirm - } else if self.input.visible { + } else if self.input.focus() { Layer::Input } else if self.pick.visible { Layer::Pick diff --git a/yazi-core/src/input/guard.rs b/yazi-core/src/input/guard.rs new file mode 100644 index 00000000..c0328928 --- /dev/null +++ b/yazi-core/src/input/guard.rs @@ -0,0 +1,46 @@ +use std::ops::{Deref, DerefMut}; + +use parking_lot::MutexGuard; + +// --- InputGuard +pub enum InputGuard<'a> { + Main(&'a yazi_widgets::input::Input), + Alt(MutexGuard<'a, yazi_widgets::input::Input>), +} + +impl Deref for InputGuard<'_> { + type Target = yazi_widgets::input::Input; + + fn deref(&self) -> &Self::Target { + match self { + Self::Main(main) => main, + Self::Alt(alt) => alt, + } + } +} + +// --- InputMutGuard +pub enum InputMutGuard<'a> { + Main(&'a mut yazi_widgets::input::Input), + Alt(MutexGuard<'a, yazi_widgets::input::Input>), +} + +impl Deref for InputMutGuard<'_> { + type Target = yazi_widgets::input::Input; + + fn deref(&self) -> &Self::Target { + match self { + Self::Main(main) => main, + Self::Alt(alt) => alt, + } + } +} + +impl DerefMut for InputMutGuard<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + Self::Main(main) => main, + Self::Alt(alt) => alt, + } + } +} diff --git a/yazi-core/src/input/input.rs b/yazi-core/src/input/input.rs index 130c4e5a..ed057875 100644 --- a/yazi-core/src/input/input.rs +++ b/yazi-core/src/input/input.rs @@ -1,22 +1,40 @@ -use std::ops::{Deref, DerefMut}; +use std::sync::Arc; +use parking_lot::Mutex; use yazi_config::popup::Position; +use crate::input::{InputGuard, InputMutGuard}; + #[derive(Default)] pub struct Input { - pub(super) inner: yazi_widgets::input::Input, + pub main: yazi_widgets::input::Input, + pub alt: Option>>, - pub visible: bool, - pub title: String, - pub position: Position, + pub main_visible: bool, + pub main_title: String, + pub main_position: Position, } -impl Deref for Input { - type Target = yazi_widgets::input::Input; +impl Input { + pub fn focus(&self) -> bool { self.main_visible || self.alt.is_some() } - fn deref(&self) -> &Self::Target { &self.inner } -} + pub fn lock(&self) -> Option> { + if self.main_visible { + Some(InputGuard::Main(&self.main)) + } else if let Some(alt) = &self.alt { + Some(InputGuard::Alt(alt.lock())) + } else { + None + } + } -impl DerefMut for Input { - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } + pub fn lock_mut(&mut self) -> Option> { + if self.main_visible { + Some(InputMutGuard::Main(&mut self.main)) + } else if let Some(alt) = &self.alt { + Some(InputMutGuard::Alt(alt.lock())) + } else { + None + } + } } diff --git a/yazi-core/src/input/mod.rs b/yazi-core/src/input/mod.rs index 9ad1aafa..60b5cf6d 100644 --- a/yazi-core/src/input/mod.rs +++ b/yazi-core/src/input/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(input); +yazi_macro::mod_flat!(guard input); diff --git a/yazi-core/src/pick/pick.rs b/yazi-core/src/pick/pick.rs index 6808b14c..0123964f 100644 --- a/yazi-core/src/pick/pick.rs +++ b/yazi-core/src/pick/pick.rs @@ -26,9 +26,7 @@ impl Pick { impl Scrollable for Pick { fn total(&self) -> usize { self.items.len() } - fn limit(&self) -> usize { - self.position.offset.height.saturating_sub(YAZI.pick.border()) as usize - } + fn limit(&self) -> usize { self.position.height.saturating_sub(YAZI.pick.border()) as usize } fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor } diff --git a/yazi-core/src/proxy.rs b/yazi-core/src/proxy.rs index a8e1457e..626b934c 100644 --- a/yazi-core/src/proxy.rs +++ b/yazi-core/src/proxy.rs @@ -50,7 +50,7 @@ impl MgrProxy { skip: job.skip, area: area.into(), data: vec![ - Renderable::Clear(yazi_binding::elements::Clear { area: area.into() }), + Renderable::Clear(Default::default()).with_area(area), Renderable::from(Error::custom(error)).with_area(area), ], }); diff --git a/yazi-core/src/tab/folder.rs b/yazi-core/src/tab/folder.rs index 6f2acea0..a32da3a7 100644 --- a/yazi-core/src/tab/folder.rs +++ b/yazi-core/src/tab/folder.rs @@ -155,7 +155,8 @@ impl Folder { len.saturating_sub(limit).min(self.offset) } else { len.saturating_sub(limit).min(self.cursor.saturating_sub(limit) + 1 + scrolloff) - }; + } + .min(self.cursor); old != self.offset } diff --git a/yazi-fm/src/app/mod.rs b/yazi-fm/src/app/mod.rs index 723744fb..694aef48 100644 --- a/yazi-fm/src/app/mod.rs +++ b/yazi-fm/src/app/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(app render); +yazi_macro::mod_flat!(app render sync_guard); diff --git a/yazi-fm/src/app/render.rs b/yazi-fm/src/app/render.rs index a137d3ad..7f948463 100644 --- a/yazi-fm/src/app/render.rs +++ b/yazi-fm/src/app/render.rs @@ -1,17 +1,15 @@ -use std::{io::Write, sync::atomic::{AtomicU8, Ordering}, time::Instant}; +use std::{sync::atomic::Ordering, time::Instant}; use anyhow::Result; -use ratatui::layout::Position; use yazi_actor::{Ctx, lives::Lives}; use yazi_binding::runtime_scope; use yazi_config::LAYOUT; -use yazi_macro::{act, succ, writef}; +use yazi_macro::{act, succ}; use yazi_plugin::LUA; use yazi_shared::{data::Data, event::NEED_RENDER}; -use yazi_term::{CursorStyle, sequence::{BeginSyncUpdate, EndSyncUpdate, MoveTo, SetCursorStyle, ShowCursor}}; -use yazi_tty::TTY; use yazi_widgets::COLLISION; +use super::SyncGuard; use crate::{app::App, root::Root}; impl App { @@ -24,9 +22,7 @@ impl App { return self.render_partially(); } - Self::routine(true, None); - let _guard = scopeguard::guard(self.core.cursor(), |c| Self::routine(false, c)); - + let guard = SyncGuard::enter(); let collision = COLLISION.swap(false, Ordering::Relaxed); let preview_rect = LAYOUT.get().preview; term.draw(|f| { @@ -45,6 +41,8 @@ impl App { } else if preview_rect != LAYOUT.get().preview { act!(mgr:peek, cx)?; // Reload preview if layout changed } + + guard.finish(self.core.cursor()); succ!(); } @@ -54,9 +52,7 @@ impl App { return self.render(false); } - Self::routine(true, None); - let _guard = scopeguard::guard(self.core.cursor(), |c| Self::routine(false, c)); - + let guard = SyncGuard::enter(); term.draw_partial(|f| { _ = Lives::scope(&mut self.core, |core| { runtime_scope!(LUA, "root", { @@ -67,29 +63,7 @@ impl App { }); })?; + guard.finish(self.core.cursor()); succ!(); } - - fn routine(push: bool, cursor: Option<(Position, CursorStyle)>) { - static COUNT: AtomicU8 = AtomicU8::new(0); - if push && COUNT.fetch_add(1, Ordering::Relaxed) != 0 { - return; - } else if !push && COUNT.fetch_sub(1, Ordering::Relaxed) != 1 { - return; - } - - if push { - write!(TTY.writer(), "{BeginSyncUpdate}").ok(); - } else if let Some((Position { x, y }, shape)) = cursor { - writef!( - TTY.writer(), - "{}{}{ShowCursor}{EndSyncUpdate}", - SetCursorStyle(shape as u8), - MoveTo(x, y), - ) - .ok(); - } else { - writef!(TTY.writer(), "{EndSyncUpdate}").ok(); - }; - } } diff --git a/yazi-fm/src/app/sync_guard.rs b/yazi-fm/src/app/sync_guard.rs new file mode 100644 index 00000000..19724c2f --- /dev/null +++ b/yazi-fm/src/app/sync_guard.rs @@ -0,0 +1,52 @@ +use std::{io::Write, sync::atomic::{AtomicU8, Ordering}}; + +use ratatui::layout::Position; +use yazi_macro::writef; +use yazi_term::{CursorStyle, sequence::{BeginSyncUpdate, EndSyncUpdate, MoveTo, SetCursorStyle, ShowCursor}}; +use yazi_tty::TTY; + +static DEPTH: AtomicU8 = AtomicU8::new(0); + +pub(super) struct SyncGuard { + finished: bool, +} + +impl SyncGuard { + pub(super) fn enter() -> Self { + if DEPTH.fetch_add(1, Ordering::Relaxed) == 0 { + write!(TTY.writer(), "{BeginSyncUpdate}").ok(); + } + + Self { finished: false } + } + + pub(super) fn finish(mut self, cursor: Option<(Position, CursorStyle)>) { + self.finished = true; + if DEPTH.fetch_sub(1, Ordering::Relaxed) != 1 { + return; + } + + _ = if let Some((Position { x, y }, shape)) = cursor { + writef!( + TTY.writer(), + "{}{}{ShowCursor}{EndSyncUpdate}", + SetCursorStyle(shape as u8), + MoveTo(x, y), + ) + } else { + writef!(TTY.writer(), "{EndSyncUpdate}") + }; + } +} + +impl Drop for SyncGuard { + fn drop(&mut self) { + if self.finished { + return; + } + + if DEPTH.fetch_sub(1, Ordering::Relaxed) == 1 { + writef!(TTY.writer(), "{EndSyncUpdate}").ok(); + } + } +} diff --git a/yazi-fm/src/cmp/cmp.rs b/yazi-fm/src/cmp/cmp.rs index df231612..e4b4261d 100644 --- a/yazi-fm/src/cmp/cmp.rs +++ b/yazi-fm/src/cmp/cmp.rs @@ -37,7 +37,7 @@ impl Widget for Cmp<'_> { }) .collect(); - let input_area = self.core.mgr.area(self.core.input.position); + let input_area = self.core.mgr.area(self.core.input.main_position); let mut area = Position::sticky(TERM.dimension(), input_area, Offset { x: 1, y: 0, diff --git a/yazi-fm/src/dispatcher.rs b/yazi-fm/src/dispatcher.rs index b25fd32e..6e24e65b 100644 --- a/yazi-fm/src/dispatcher.rs +++ b/yazi-fm/src/dispatcher.rs @@ -88,12 +88,11 @@ impl<'a> Dispatcher<'a> { } 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)?; + if let Some(mut guard) = self.app.core.input.lock_mut() { + if guard.mode() == InputMode::Insert { + guard.type_str(&str)?; + } else if guard.mode() == InputMode::Replace { + guard.replace_str(&str)?; } } Ok(()) diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 7f7c6c5c..671119cd 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -249,40 +249,39 @@ impl<'a> Executor<'a> { } fn input(&mut self, action: ActionCow) -> Result { - let mode = self.app.core.input.mode(); - let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?; + let Some(mut guard) = self.app.core.input.lock_mut() else { succ!() }; macro_rules! on { ($name:ident) => { if action.name == stringify!($name) { - return act!(input:$name, cx, action); + on!(input:$name, action); } }; + ($layer:ident : $name:ident, $opt:expr) => {{ + drop(guard); + let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?; + return act!($layer:$name, cx, $opt); + }}; } on!(escape); on!(show); on!(close); - match mode { - InputMode::Normal => { - match action.name.as_ref() { - // Help - "help" => return act!(help:toggle, cx, Layer::Input), - // Plugin - "plugin" => return act!(app:plugin, cx, action), - // Lua - "lua" => return act!(app:lua, cx, action), - _ => {} - } - } + match guard.mode() { + InputMode::Normal => match action.name.as_ref() { + "help" => on!(help:toggle, Layer::Input), + "plugin" => on!(app:plugin, action), + "lua" => on!(app:lua, action), + _ => {} + }, InputMode::Insert => { on!(complete); } InputMode::Replace => {} }; - self.app.core.input.execute(action) + guard.execute(action) } fn confirm(&mut self, action: ActionCow) -> Result { diff --git a/yazi-fm/src/input/input.rs b/yazi-fm/src/input/input.rs index fa5bbc49..c396a9b1 100644 --- a/yazi-fm/src/input/input.rs +++ b/yazi-fm/src/input/input.rs @@ -13,16 +13,16 @@ impl<'a> Input<'a> { impl Widget for Input<'_> { fn render(self, _: Rect, buf: &mut Buffer) { let input = &self.core.input; - let area = self.core.mgr.area(input.position); - yazi_widgets::Clear.render(area, buf); + let outer = self.core.mgr.area(input.main_position); + yazi_widgets::Clear.render(outer, buf); Block::bordered() .border_type(BorderType::Rounded) .border_style(THEME.input.border.get()) - .title(Line::styled(&input.title, THEME.input.title.get())) - .render(area, buf); + .title(Line::styled(&input.main_title, THEME.input.title.get())) + .render(outer, buf); - input.render(area.inner(Margin::new(1, 1)), buf); + input.main.render(outer.inner(Margin::new(1, 1)), buf); } } diff --git a/yazi-fm/src/mgr/preview.rs b/yazi-fm/src/mgr/preview.rs index 7227edc3..bb3a8ef6 100644 --- a/yazi-fm/src/mgr/preview.rs +++ b/yazi-fm/src/mgr/preview.rs @@ -1,4 +1,5 @@ use ratatui::{buffer::Buffer, widgets::Widget}; +use yazi_binding::elements::Spatial; use yazi_config::LAYOUT; use yazi_core::Core; diff --git a/yazi-fm/src/renderer.rs b/yazi-fm/src/renderer.rs index 7c53af05..87c2bf89 100644 --- a/yazi-fm/src/renderer.rs +++ b/yazi-fm/src/renderer.rs @@ -1,6 +1,6 @@ use mlua::{ObjectLike, Table}; use ratatui::{buffer::Buffer, layout::Rect}; -use yazi_binding::elements::Renderables; +use yazi_binding::elements::{Renderable, Renderables}; use yazi_core::Core; use yazi_plugin::LUA; @@ -33,9 +33,17 @@ impl<'a> Renderer<'a> { .globals() .raw_get::
(self.component)? .call_method::
(self.constructor, area)? - .call_method("redraw", ())?; + .call_method(self.redrawer, ())?; + self.core.input.alt = None; Renderables::reduce(value, |element| { + match &element { + Renderable::Input(input) if input.focus => { + self.core.input.alt = Some(input.into()); + } + _ => {} + } + element.render_with(buf, |p| self.core.mgr.area(p)); }); diff --git a/yazi-fm/src/root.rs b/yazi-fm/src/root.rs index 67d4daf6..1b5f3962 100644 --- a/yazi-fm/src/root.rs +++ b/yazi-fm/src/root.rs @@ -42,7 +42,7 @@ impl Widget for Root<'_> { pick::Pick::new(self.core).render(area, buf); } - if self.core.input.visible { + if self.core.input.main_visible { input::Input::new(self.core).render(area, buf); } diff --git a/yazi-fm/src/router.rs b/yazi-fm/src/router.rs index 151fa709..b2479c61 100644 --- a/yazi-fm/src/router.rs +++ b/yazi-fm/src/router.rs @@ -14,17 +14,20 @@ impl<'a> Router<'a> { pub(super) fn new(app: &'a mut App) -> Self { Self { app } } pub(super) fn route(&mut self, key: Key) -> Result { - let core = &mut self.app.core; - let layer = core.layer(); + use Layer as L; + let core = &mut self.app.core; if core.help.visible && core.help.r#type(&key)? { return Ok(true); } - if core.input.visible && core.input.r#type(&key)? { + + if let Some(mut guard) = core.input.lock_mut() + && guard.r#type(&key)? + { return Ok(true); } - use Layer as L; + let layer = core.layer(); Ok(match layer { L::Null | L::App | L::Notify => unreachable!(), L::Mgr | L::Tasks | L::Spot | L::Pick | L::Input | L::Confirm | L::Help => { diff --git a/yazi-fm/src/spot/spot.rs b/yazi-fm/src/spot/spot.rs index 7dd5cc6f..9da17fe2 100644 --- a/yazi-fm/src/spot/spot.rs +++ b/yazi-fm/src/spot/spot.rs @@ -1,4 +1,5 @@ use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; +use yazi_binding::elements::Spatial; use yazi_core::Core; pub(crate) struct Spot<'a> { diff --git a/yazi-macro/src/input.rs b/yazi-macro/src/input.rs index 25506e8e..e240c1b6 100644 --- a/yazi-macro/src/input.rs +++ b/yazi-macro/src/input.rs @@ -2,7 +2,7 @@ macro_rules! input { ($cx:ident, $cfg:expr) => {{ let (tx, rx) = ::tokio::sync::mpsc::unbounded_channel(); - match $crate::act!(input:show, $cx, yazi_widgets::input::InputOpt { cfg: $cfg, tx }) { + match $crate::act!(input:show, $cx, yazi_widgets::input::InputOpt { cfg: $cfg, tx: Some(tx) }) { Ok(_) => Ok(rx), Err(e) => Err(e) } diff --git a/yazi-parser/src/input/close.rs b/yazi-parser/src/input/close.rs index fc2065eb..32e7dd9e 100644 --- a/yazi-parser/src/input/close.rs +++ b/yazi-parser/src/input/close.rs @@ -1,7 +1,9 @@ -use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value}; +use serde::{Deserialize, Serialize}; +use yazi_binding::SER_OPT; use yazi_shared::event::ActionCow; -#[derive(Debug, Default)] +#[derive(Debug, Default, Deserialize, Serialize)] pub struct CloseForm { pub submit: bool, } @@ -15,9 +17,9 @@ impl From for CloseForm { } impl FromLua for CloseForm { - fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } + fn from_lua(value: Value, lua: &Lua) -> mlua::Result { lua.from_value(value) } } impl IntoLua for CloseForm { - fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } + fn into_lua(self, lua: &Lua) -> mlua::Result { lua.to_value_with(&self, SER_OPT) } } diff --git a/yazi-parser/src/spark/kind.rs b/yazi-parser/src/spark/kind.rs index 382e00ea..4eeb31f6 100644 --- a/yazi-parser/src/spark/kind.rs +++ b/yazi-parser/src/spark/kind.rs @@ -20,6 +20,10 @@ pub enum SparkKind { // mgr:quit KeyQuit, + // input:close + KeyInputClose, + IndInputClose, + // which:activate IndWhichActivate, diff --git a/yazi-parser/src/spark/spark.rs b/yazi-parser/src/spark/spark.rs index 48f2c080..97cfd306 100644 --- a/yazi-parser/src/spark/spark.rs +++ b/yazi-parser/src/spark/spark.rs @@ -181,6 +181,10 @@ impl<'a> Spark<'a> { // mgr:quit KeyQuit => Self::Quit(<_>::from_lua(value, lua)?), + // input:close + KeyInputClose => Self::InputClose(<_>::from_lua(value, lua)?), + IndInputClose => Self::InputClose(<_>::from_lua(value, lua)?), + // which:activate IndWhichActivate => Self::WhichActivate(<_>::from_lua(value, lua)?), diff --git a/yazi-plugin/preset/components/root.lua b/yazi-plugin/preset/components/root.lua index 15d2557f..466994a1 100644 --- a/yazi-plugin/preset/components/root.lua +++ b/yazi-plugin/preset/components/root.lua @@ -56,7 +56,7 @@ function Root:click(event, up) Root._dragging = not up and c or nil if tostring(cx.layer) == "mgr" then - return c and c:click(event, up) + return c and c.click and c:click(event, up) end end @@ -65,7 +65,7 @@ function Root:scroll(event, step) return end local c = ya.child_at(ui.Rect { x = event.x, y = event.y }, self:reflow()) - return c and c:scroll(event, step) + return c and c.scroll and c:scroll(event, step) end function Root:touch(event, step) @@ -73,7 +73,7 @@ function Root:touch(event, step) return end local c = ya.child_at(ui.Rect { x = event.x, y = event.y }, self:reflow()) - return c and c:touch(event, step) + return c and c.touch and c:touch(event, step) end function Root:move(event) end diff --git a/yazi-plugin/src/utils/preview.rs b/yazi-plugin/src/utils/preview.rs index 5c996f74..63570798 100644 --- a/yazi-plugin/src/utils/preview.rs +++ b/yazi-plugin/src/utils/preview.rs @@ -1,5 +1,5 @@ use mlua::{ExternalError, Function, IntoLuaMulti, Lua, Table, Value}; -use yazi_binding::{Error, elements::{Area, Renderable, Text}}; +use yazi_binding::{Error, elements::{Area, Renderable}}; use yazi_core::{Highlighter, MgrProxy, tab::PreviewLock}; use yazi_fs::FsUrl; use yazi_runner::previewer::PeekError; @@ -25,7 +25,7 @@ impl Utils { } }; - lock.data = vec![Renderable::Text(Text { area, inner, ..Default::default() })]; + lock.data = vec![Renderable::Text(inner.into()).with_area(area)]; MgrProxy::update_peeked(lock); ().into_lua_multi(&lua) @@ -43,7 +43,7 @@ impl Utils { Err(e) => { if let Ok(err) = ud.take::() { vec![ - Renderable::Clear(yazi_binding::elements::Clear { area: lock.area.into() }), + Renderable::Clear(Default::default()).with_area(lock.area), Renderable::from(err).with_area(lock.area), ] } else { diff --git a/yazi-plugin/src/utils/spot.rs b/yazi-plugin/src/utils/spot.rs index e0b4b709..687f2133 100644 --- a/yazi-plugin/src/utils/spot.rs +++ b/yazi-plugin/src/utils/spot.rs @@ -1,5 +1,5 @@ use mlua::{AnyUserData, Function, Lua, Table}; -use yazi_binding::elements::{Edge, Renderable}; +use yazi_binding::elements::{Edge, Renderable, Spatial}; use yazi_config::THEME; use yazi_core::spot::SpotLock; use yazi_proxy::MgrProxy; @@ -10,13 +10,13 @@ impl Utils { pub(super) fn spot_table(lua: &Lua) -> mlua::Result { lua.create_function(|_, (t, table): (mlua::Table, AnyUserData)| { let mut lock = SpotLock::try_from(t)?; - let mut table = yazi_binding::elements::Table::try_from(table)?; + let mut table = yazi_binding::elements::Table::try_from(&table)?; - let area = table.area; - table.area = area.inner(ratatui::widgets::Padding::uniform(1)); + let area = table.area(); + table.set_area(area.inner(ratatui::widgets::Padding::uniform(1))); lock.data = vec![ - Renderable::Clear(yazi_binding::elements::Clear { area }), + Renderable::Clear(Default::default()).with_area(area), Renderable::Border(yazi_binding::elements::Border { area, edge: Edge(ratatui::widgets::Borders::ALL), diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index e398cf5b..a96bb065 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -24,7 +24,7 @@ foldhash = { workspace = true } futures = { workspace = true } hashbrown = { workspace = true } inventory = { workspace = true } -memchr = "2.8.1" +memchr = "2.8.2" mlua = { workspace = true } ordered-float = { workspace = true } parking_lot = { workspace = true } diff --git a/yazi-shared/src/id.rs b/yazi-shared/src/id.rs index 3394c554..8d3af867 100644 --- a/yazi-shared/src/id.rs +++ b/yazi-shared/src/id.rs @@ -45,6 +45,7 @@ impl PartialEq for Id { } // --- Ids +#[derive(Debug)] pub struct Ids { next: AtomicU64, } diff --git a/yazi-shim/src/cell/ro_cell.rs b/yazi-shim/src/cell/ro_cell.rs index fcfa8b87..b5d48f99 100644 --- a/yazi-shim/src/cell/ro_cell.rs +++ b/yazi-shim/src/cell/ro_cell.rs @@ -55,7 +55,6 @@ impl RoCell { self.inner.get().replace(MaybeUninit::uninit()).assume_init() } } - } impl Default for RoCell { diff --git a/yazi-version/Cargo.toml b/yazi-version/Cargo.toml index 93f04a85..44dfdecc 100644 --- a/yazi-version/Cargo.toml +++ b/yazi-version/Cargo.toml @@ -13,4 +13,4 @@ rust-version.workspace = true workspace = true [build-dependencies] -vergen-gitcl = { version = "9.1.0", features = [ "build", "rustc" ] } +vergen-gitcl = { version = "10.0.0", features = [ "build", "rustc" ] } diff --git a/yazi-version/build.rs b/yazi-version/build.rs index 2be047c1..3f9a46af 100644 --- a/yazi-version/build.rs +++ b/yazi-version/build.rs @@ -1,22 +1,17 @@ use std::{env, error::Error}; -use vergen_gitcl::{BuildBuilder, Emitter, GitclBuilder, RustcBuilder}; +use vergen_gitcl::{Build, Emitter, Gitcl, Rustc}; fn main() -> Result<(), Box> { Emitter::default() - .add_instructions(&BuildBuilder::default().build_date(true).build()?)? + .add_instructions(&Build::builder().build_date(true).build())? .add_instructions( - &RustcBuilder::default() - .commit_date(true) - .commit_hash(true) - .host_triple(true) - .semver(true) - .build()?, + &Rustc::builder().commit_date(true).commit_hash(true).host_triple(true).semver(true).build(), )? .emit()?; if env::var_os("YAZI_NO_GITCL").is_none() { - Emitter::default().add_instructions(&GitclBuilder::default().sha(true).build()?)?.emit()?; + Emitter::default().add_instructions(&Gitcl::builder().sha(true).build())?.emit()?; } else { println!("cargo:rustc-env=VERGEN_GIT_SHA=no-gitcl"); } diff --git a/yazi-widgets/src/input/actor/casefy.rs b/yazi-widgets/src/input/actor/casefy.rs index 317162f6..72494c71 100644 --- a/yazi-widgets/src/input/actor/casefy.rs +++ b/yazi-widgets/src/input/actor/casefy.rs @@ -22,7 +22,7 @@ impl Input { snap.value.replace_range(start..end, &casefied); snap.op = InputOp::None; snap.cursor = range.start; - self.snaps.tag(self.limit).then(|| self.flush_type()); + self.snaps.tag(self.pos.width as usize).then(|| self.flush_type()); act!(r#move, self)?; succ!(render!()); diff --git a/yazi-widgets/src/input/actor/escape.rs b/yazi-widgets/src/input/actor/escape.rs index b2e90a04..d9e45d77 100644 --- a/yazi-widgets/src/input/actor/escape.rs +++ b/yazi-widgets/src/input/actor/escape.rs @@ -19,7 +19,7 @@ impl Input { snap.mode = InputMode::Normal; } } - self.snaps.tag(self.limit); + self.snaps.tag(self.pos.width as usize); succ!(); } } diff --git a/yazi-widgets/src/input/actor/move.rs b/yazi-widgets/src/input/actor/move.rs index 3b817006..d3958f42 100644 --- a/yazi-widgets/src/input/actor/move.rs +++ b/yazi-widgets/src/input/actor/move.rs @@ -15,7 +15,7 @@ impl Input { 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()); + let (limit, snap) = (self.pos.width as usize, self.snap_mut()); if snap.value.is_empty() { succ!(snap.offset = 0); } diff --git a/yazi-widgets/src/input/actor/replace.rs b/yazi-widgets/src/input/actor/replace.rs index 652b7563..84cad48f 100644 --- a/yazi-widgets/src/input/actor/replace.rs +++ b/yazi-widgets/src/input/actor/replace.rs @@ -29,7 +29,7 @@ impl Input { (Some(_), Some((len, _))) => snap.value.replace_range(start..start + len, &s), } - self.snaps.tag(self.limit).then(|| self.flush_type()); + self.snaps.tag(self.pos.width as usize).then(|| self.flush_type()); succ!(render!()); } } diff --git a/yazi-widgets/src/input/input.rs b/yazi-widgets/src/input/input.rs index 22269482..a9fcf6da 100644 --- a/yazi-widgets/src/input/input.rs +++ b/yazi-widgets/src/input/input.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, ops::Range}; use anyhow::Result; use tokio::sync::mpsc; -use yazi_config::YAZI; +use yazi_config::{YAZI, popup::Position}; use yazi_macro::act; use yazi_shared::Ids; use yazi_shim::path::CROSS_SEPARATOR; @@ -11,10 +11,10 @@ use yazi_term::CursorStyle; use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp}; use crate::{CLIPBOARD, input::{InputEvent, InputOpt}}; -#[derive(Default)] +#[derive(Debug, Default)] pub struct Input { + pub pos: Position, pub snaps: InputSnaps, - pub limit: usize, pub obscure: bool, pub realtime: bool, pub completion: bool, @@ -25,15 +25,15 @@ pub struct Input { impl Input { pub fn new(opt: InputOpt) -> Result { - let limit = opt.cfg.position.offset.width.saturating_sub(YAZI.input.border()) as usize; + let limit = opt.cfg.position.width as usize; let mut input = Self { + pos: opt.cfg.position, snaps: InputSnaps::new(opt.cfg.value, opt.cfg.obscure, limit), - limit, obscure: opt.cfg.obscure, realtime: opt.cfg.realtime, completion: opt.cfg.completion, - tx: Some(opt.tx), + tx: opt.tx, ..Default::default() }; @@ -45,6 +45,11 @@ impl Input { Ok(input) } + pub fn repos(&mut self, pos: Position) { + self.pos = pos; + self.snap_mut().resize(pos.width as usize); + } + pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool { let old = self.snap().clone(); let snap = self.snap_mut(); @@ -81,7 +86,7 @@ impl Input { return false; } if !matches!(old.op, InputOp::None | InputOp::Select(_)) { - self.snaps.tag(self.limit).then(|| self.flush_type()); + self.snaps.tag(self.pos.width as usize).then(|| self.flush_type()); } true } @@ -111,9 +116,9 @@ impl Input { pub fn display(&self) -> Cow<'_, str> { if self.obscure { - "•".repeat(self.snap().window(self.limit).len()).into() + "•".repeat(self.snap().window(self.pos.width as usize).len()).into() } else { - self.snap().slice(self.snap().window(self.limit)).into() + self.snap().slice(self.snap().window(self.pos.width as usize)).into() } } @@ -142,7 +147,7 @@ impl Input { let (start, end) = if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) }; - let win = snap.window(self.limit); + let win = snap.window(self.pos.width as usize); let Range { start, end } = start.max(win.start)..end.min(win.end); let s = snap.width(snap.offset..start); diff --git a/yazi-widgets/src/input/option.rs b/yazi-widgets/src/input/option.rs index 4e6f1dd7..5acfa986 100644 --- a/yazi-widgets/src/input/option.rs +++ b/yazi-widgets/src/input/option.rs @@ -9,7 +9,7 @@ use crate::input::InputEvent; #[derive(Debug)] pub struct InputOpt { pub cfg: InputCfg, - pub tx: mpsc::UnboundedSender, + pub tx: Option>, } impl TryFrom for InputOpt { @@ -17,14 +17,10 @@ impl TryFrom for InputOpt { fn try_from(mut a: ActionCow) -> Result { let Some(cfg) = a.take_any("cfg") else { - bail!("Invalid 'cfg' in InputOpt"); + bail!("invalid 'cfg' in InputOpt"); }; - let Some(tx) = a.take_any("tx") else { - bail!("Invalid 'tx' in InputOpt"); - }; - - Ok(Self { cfg, tx }) + Ok(Self { cfg, tx: a.take_any("tx") }) } } diff --git a/yazi-widgets/src/input/snap.rs b/yazi-widgets/src/input/snap.rs index 0ae7228f..112988b5 100644 --- a/yazi-widgets/src/input/snap.rs +++ b/yazi-widgets/src/input/snap.rs @@ -34,7 +34,6 @@ impl InputSnap { snap } - #[inline] pub(super) fn resize(&mut self, limit: usize) { let count = self.count(); let limit = if self.obscure { diff --git a/yazi-widgets/src/input/snaps.rs b/yazi-widgets/src/input/snaps.rs index 0bfbd539..780ee383 100644 --- a/yazi-widgets/src/input/snaps.rs +++ b/yazi-widgets/src/input/snaps.rs @@ -2,7 +2,7 @@ use std::mem; use super::InputSnap; -#[derive(PartialEq, Eq)] +#[derive(Debug, Eq, PartialEq)] pub struct InputSnaps { idx: usize, versions: Vec, diff --git a/yazi-widgets/src/input/widget.rs b/yazi-widgets/src/input/widget.rs index b05741f9..ad4381cd 100644 --- a/yazi-widgets/src/input/widget.rs +++ b/yazi-widgets/src/input/widget.rs @@ -10,8 +10,6 @@ impl Widget for &Input { where Self: Sized, { - crate::Clear.render(area, buf); - Line::styled(self.display(), THEME.input.value.get()).render(area, buf); if let Some(Range { start, end }) = self.selected() {