feat: new ui.Input element (#4040)

This commit is contained in:
三咲雅 misaki masa 2026-06-13 19:37:29 +08:00 committed by GitHub
parent 9b920ed2e3
commit b37be0529a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
71 changed files with 802 additions and 411 deletions

View file

@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Drag and drop ([#4005]) - Drag and drop ([#4005])
- Bulk create ([#3793]) - Bulk create ([#3793])
- Dynamic keymap Lua API ([#4031]) - Dynamic keymap Lua API ([#4031])
- New `ui.Input` element ([#4040])
- Image preview with Überzug++ on Niri ([#3990]) - Image preview with Überzug++ on Niri ([#3990])
- New gait for input `backward` and `forward` actions ([#4012]) - 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 [#4012]: https://github.com/sxyazi/yazi/pull/4012
[#4022]: https://github.com/sxyazi/yazi/pull/4022 [#4022]: https://github.com/sxyazi/yazi/pull/4022
[#4031]: https://github.com/sxyazi/yazi/pull/4031 [#4031]: https://github.com/sxyazi/yazi/pull/4031
[#4040]: https://github.com/sxyazi/yazi/pull/4040

132
Cargo.lock generated
View file

@ -462,6 +462,31 @@ dependencies = [
"cipher", "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]] [[package]]
name = "bs58" name = "bs58"
version = "0.5.1" version = "0.5.1"
@ -1000,38 +1025,14 @@ dependencies = [
"syn 2.0.117", "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]] [[package]]
name = "darling" name = "darling"
version = "0.23.0" version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
dependencies = [ dependencies = [
"darling_core 0.23.0", "darling_core",
"darling_macro 0.23.0", "darling_macro",
]
[[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",
] ]
[[package]] [[package]]
@ -1047,24 +1048,13 @@ dependencies = [
"syn 2.0.117", "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]] [[package]]
name = "darling_macro" name = "darling_macro"
version = "0.23.0" version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
dependencies = [ dependencies = [
"darling_core 0.23.0", "darling_core",
"quote", "quote",
"syn 2.0.117", "syn 2.0.117",
] ]
@ -1133,37 +1123,6 @@ dependencies = [
"serde_core", "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]] [[package]]
name = "derive_more" name = "derive_more"
version = "2.1.1" version = "2.1.1"
@ -2007,7 +1966,7 @@ version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971"
dependencies = [ dependencies = [
"darling 0.23.0", "darling",
"indoc", "indoc",
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2310,9 +2269,9 @@ checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.1" version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]] [[package]]
name = "memmem" name = "memmem"
@ -3532,9 +3491,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.12.3" version = "1.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -3555,9 +3514,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.10" version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]] [[package]]
name = "rfc6979" name = "rfc6979"
@ -3940,7 +3899,7 @@ version = "3.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660"
dependencies = [ dependencies = [
"darling 0.23.0", "darling",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.117", "syn 2.0.117",
@ -4847,12 +4806,12 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]] [[package]]
name = "vergen" name = "vergen"
version = "9.1.0" version = "10.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b849a1f6d8639e8de261e81ee0fc881e3e3620db1af9f2e0da015d4382ceaf75" checksum = "7bdf18a54cf91b4d98a8e8b67f6321606539fbcdcac02536286ad1de37b53fd2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"derive_builder", "bon",
"rustc_version", "rustc_version",
"rustversion", "rustversion",
"time", "time",
@ -4861,12 +4820,12 @@ dependencies = [
[[package]] [[package]]
name = "vergen-gitcl" name = "vergen-gitcl"
version = "9.1.0" version = "10.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ff3b5300a085d6bcd8fc96a507f706a28ae3814693236c9b409db71a1d15b9" checksum = "4961429ed12888cb3c6dd20f7dc9508c821091a3ba5fec0156ed5a654c1c4572"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"derive_builder", "bon",
"rustversion", "rustversion",
"time", "time",
"vergen", "vergen",
@ -4875,12 +4834,12 @@ dependencies = [
[[package]] [[package]]
name = "vergen-lib" name = "vergen-lib"
version = "9.1.0" version = "10.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b34a29ba7e9c59e62f229ae1932fb1b8fb8a6fdcc99215a641913f5f5a59a569" checksum = "910e8471e27130bbc019e9bfa6bda16dfc4c6dd7c5d0793da70a9256caeae984"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"derive_builder", "bon",
"rustversion", "rustversion",
] ]
@ -5672,6 +5631,7 @@ dependencies = [
"libc", "libc",
"mlua", "mlua",
"ordered-float 5.3.0", "ordered-float 5.3.0",
"parking_lot",
"paste", "paste",
"ratatui", "ratatui",
"serde", "serde",

View file

@ -62,7 +62,7 @@ paste = "1.0.15"
percent-encoding = "2.3.2" percent-encoding = "2.3.2"
rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] } 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" ] } 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" ] } russh = { version = "0.61.2", default-features = false, features = [ "ring", "rsa" ] }
scopeguard = "1.2.0" scopeguard = "1.2.0"
serde = { version = "1.0.228", features = [ "derive" ] } serde = { version = "1.0.228", features = [ "derive" ] }

View file

@ -22,7 +22,7 @@ impl Actor for Trigger {
if form.ticket.is_some_and(|t| t != cx.cmp.ticket) { if form.ticket.is_some_and(|t| t != cx.cmp.ticket) {
succ!(); succ!();
} else if form.ticket.is_none() { } 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()); cx.cmp.handle.take().map(|h| h.abort());

View file

@ -1,7 +1,7 @@
use anyhow::Result; use anyhow::Result;
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_parser::input::CloseForm; use yazi_parser::{input::CloseForm, spark::SparkKind};
use yazi_shared::data::Data; use yazi_shared::{Source, data::Data};
use yazi_widgets::input::InputEvent; use yazi_widgets::input::InputEvent;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -14,16 +14,28 @@ impl Actor for Close {
const NAME: &str = "close"; const NAME: &str = "close";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let input = &mut cx.input; let Some(mut guard) = cx.input.lock_mut() else {
input.visible = false; succ!();
input.ticket.next(); };
if let Some(tx) = input.tx.take() { guard.ticket.next();
let value = input.snap().value.clone(); 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) }); _ = tx.send(if form.submit { InputEvent::Submit(value) } else { InputEvent::Cancel(value) });
} }
drop(guard);
cx.input.main_visible = false;
act!(cmp:close, cx)?; act!(cmp:close, cx)?;
succ!(render!()); succ!(render!());
} }
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
match cx.source() {
Source::Key => Some(SparkKind::KeyInputClose),
Source::Ind => Some(SparkKind::IndInputClose),
_ => None,
}
}
} }

View file

@ -13,11 +13,14 @@ impl Actor for Complete {
const NAME: &str = "complete"; const NAME: &str = "complete";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let input = &mut cx.input; let Some(mut guard) = cx.input.lock_mut() else {
if !input.visible || input.ticket.current() != form.ticket { succ!();
};
if guard.ticket.current() != form.ticket {
succ!(); succ!();
} }
act!(complete, input, form) act!(complete, guard, form)
} }
} }

View file

@ -15,16 +15,20 @@ impl Actor for Escape {
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
use yazi_widgets::input::InputMode as M; 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 { 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::Insert => act!(cmp:close, cx),
M::Normal | M::Replace => Ok(().into()), M::Normal | M::Replace => Ok(().into()),
}?; }?;
act!(escape, cx.input)?;
succ!(render!()); succ!(render!());
} }
} }

View file

@ -1,6 +1,5 @@
use std::ops::DerefMut;
use anyhow::Result; use anyhow::Result;
use ratatui::widgets::Padding;
use yazi_macro::{act, render, succ}; use yazi_macro::{act, render, succ};
use yazi_shared::data::Data; use yazi_shared::data::Data;
use yazi_widgets::input::InputOpt; use yazi_widgets::input::InputOpt;
@ -14,14 +13,16 @@ impl Actor for Show {
const NAME: &str = "show"; const NAME: &str = "show";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, mut form: Self::Form) -> Result<Data> {
act!(input:close, cx)?; act!(input:close, cx)?;
let input = &mut cx.input; let input = &mut cx.input;
input.visible = true; input.main_visible = true;
input.title = form.cfg.title.clone(); input.main_title = form.cfg.title.clone();
input.position = form.cfg.position; input.main_position = form.cfg.position;
*input.deref_mut() = yazi_widgets::input::Input::new(form)?;
form.cfg.position = form.cfg.position.padding(Padding::uniform(1));
input.main = yazi_widgets::input::Input::new(form)?;
succ!(render!()); succ!(render!());
} }

View file

@ -37,6 +37,7 @@ hashbrown = { workspace = true }
inventory = { workspace = true } inventory = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
ordered-float = { workspace = true } ordered-float = { workspace = true }
parking_lot = { workspace = true }
paste = { workspace = true } paste = { workspace = true }
ratatui = { workspace = true } ratatui = { workspace = true }
serde = { workspace = true } serde = { workspace = true }

View file

@ -8,31 +8,38 @@ const EXPECTED: &str = "expected a Pos or Rect";
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Area { pub enum Area {
Pos(Pos),
Rect(Rect), Rect(Rect),
Pos(Pos),
} }
impl Default for Area { impl Default for Area {
fn default() -> Self { Self::Rect(Default::default()) } 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 { impl Area {
pub fn size(self) -> ratatui::layout::Size { pub fn size(self) -> ratatui::layout::Size {
match self { match self {
Self::Pos(pos) => {
ratatui::layout::Size { width: pos.offset.width, height: pos.offset.height }
}
Self::Rect(rect) => (*rect).into(), 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 { pub fn inner(self, padding: ratatui::widgets::Padding) -> Self {
match self { match self {
Self::Rect(rect) => Self::Rect(rect.pad(padding.into())),
Self::Pos(mut pos) => { Self::Pos(mut pos) => {
pos.pad += padding; pos.pad += padding;
Self::Pos(pos) 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, f: impl FnOnce(yazi_config::popup::Position) -> ratatui::layout::Rect,
) -> ratatui::layout::Rect { ) -> ratatui::layout::Rect {
match self { match self {
Self::Pos(pos) => *Rect(f(*pos)).pad(pos.pad),
Self::Rect(rect) => *rect, Self::Rect(rect) => *rect,
Self::Pos(pos) => *Rect(f(*pos)).pad(pos.pad),
} }
} }
} }
@ -55,10 +62,14 @@ impl From<ratatui::layout::Rect> for Area {
fn from(rect: ratatui::layout::Rect) -> Self { Self::Rect(rect.into()) } fn from(rect: ratatui::layout::Rect) -> Self { Self::Rect(rect.into()) }
} }
impl TryFrom<AnyUserData> for Area { impl From<yazi_config::popup::Position> for Area {
fn from(pos: yazi_config::popup::Position) -> Self { Self::Pos(pos.into()) }
}
impl TryFrom<&AnyUserData> for Area {
type Error = mlua::Error; type Error = mlua::Error;
fn try_from(value: AnyUserData) -> Result<Self, Self::Error> { fn try_from(value: &AnyUserData) -> Result<Self, Self::Error> {
Ok(if let Ok(rect) = value.borrow::<Rect>() { Ok(if let Ok(rect) = value.borrow::<Rect>() {
Self::Rect(*rect) Self::Rect(*rect)
} else if let Ok(pos) = value.borrow::<Pos>() { } else if let Ok(pos) = value.borrow::<Pos>() {
@ -72,7 +83,7 @@ impl TryFrom<AnyUserData> for Area {
impl FromLua for Area { impl FromLua for Area {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
match value { match value {
Value::UserData(ud) => Self::try_from(ud), Value::UserData(ud) => Self::try_from(&ud),
_ => Err(EXPECTED.into_lua_err()), _ => Err(EXPECTED.into_lua_err()),
} }
} }
@ -81,17 +92,8 @@ impl FromLua for Area {
impl IntoLua for Area { impl IntoLua for Area {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
match self { match self {
Self::Pos(pos) => pos.into_lua(lua),
Self::Rect(rect) => rect.into_lua(lua), Self::Rect(rect) => rect.into_lua(lua),
} Self::Pos(pos) => pos.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),
} }
} }
} }

View file

@ -2,10 +2,11 @@ use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMetho
use ratatui::widgets::{Borders, Widget}; use ratatui::widgets::{Borders, Widget};
use super::{Area, Edge}; use super::{Area, Edge};
use crate::elements::Spatial;
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Bar { pub struct Bar {
pub(super) area: Area, area: Area,
edge: Edge, edge: Edge,
symbol: String, symbol: String,
@ -23,6 +24,18 @@ impl Bar {
} }
} }
impl TryFrom<&AnyUserData> for Bar {
type Error = mlua::Error;
fn try_from(value: &AnyUserData) -> Result<Self, Self::Error> { 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 { impl Widget for Bar {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where where

View file

@ -2,7 +2,7 @@ use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMetho
use ratatui::widgets::{Borders, Widget}; use ratatui::widgets::{Borders, Widget};
use super::{Area, Edge}; use super::{Area, Edge};
use crate::elements::Line; use crate::elements::{Line, Spatial};
// Type // Type
const PLAIN: u8 = 0; 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<Self, Self::Error> { 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 { impl Widget for Border {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where where

View file

@ -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 ratatui::widgets::Widget;
use super::Area; use super::Area;
use crate::elements::Spatial;
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default)]
pub struct Clear { pub struct Clear {
pub area: Area, area: Area,
} }
impl Clear { impl Clear {
@ -19,6 +20,18 @@ impl Clear {
} }
} }
impl TryFrom<&AnyUserData> for Clear {
type Error = mlua::Error;
fn try_from(value: &AnyUserData) -> Result<Self, Self::Error> { 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 { impl Widget for Clear {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where where

View file

@ -1,6 +1,6 @@
use std::str::FromStr; 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)] #[derive(Clone, Copy, Default)]
pub struct Color(pub ratatui::style::Color); 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<Self, Self::Error> { Ok(*ud.borrow()?) }
}
impl FromLua for Color { impl FromLua for Color {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
Ok(Self(match value { Ok(Self(match value {
Value::Nil => ratatui::style::Color::Reset, Value::Nil => ratatui::style::Color::Reset,
Value::String(s) => ratatui::style::Color::from_str(&s.to_str()?).into_lua_err()?, Value::String(s) => ratatui::style::Color::from_str(&s.to_str()?).into_lua_err()?,
Value::UserData(ud) => ud.borrow::<Self>()?.0, Value::UserData(ud) => return Self::try_from(&ud),
_ => Err("expected a Color".into_lua_err())?, _ => Err("expected a Color".into_lua_err())?,
})) }))
} }

View file

@ -14,6 +14,7 @@ pub fn compose(p_get: ComposerGet, p_set: ComposerSet) -> Composer<ComposerGet,
b"Edge" => super::Edge::compose(lua)?, b"Edge" => super::Edge::compose(lua)?,
b"Fill" => super::Fill::compose(lua)?, b"Fill" => super::Fill::compose(lua)?,
b"Gauge" => super::Gauge::compose(lua)?, b"Gauge" => super::Gauge::compose(lua)?,
b"Input" => super::Input::compose(lua)?,
b"Layout" => super::Layout::compose(lua)?, b"Layout" => super::Layout::compose(lua)?,
b"Line" => super::Line::compose(lua)?, b"Line" => super::Line::compose(lua)?,
b"List" => super::List::compose(lua)?, b"List" => super::List::compose(lua)?,

View file

@ -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 ratatui::widgets::Widget;
use super::Area; use super::Area;
use crate::elements::Spatial;
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default)]
pub struct Fill { pub struct Fill {
pub area: Area, area: Area,
style: ratatui::style::Style, 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<Self, Self::Error> { 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 { impl Widget for Fill {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where where

View file

@ -2,11 +2,11 @@ use mlua::{AnyUserData, ExternalError, IntoLua, Lua, MetaMethod, Table, UserData
use ratatui::widgets::Widget; use ratatui::widgets::Widget;
use super::{Area, Span}; use super::{Area, Span};
use crate::Style; use crate::{Style, elements::Spatial};
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Gauge { pub struct Gauge {
pub(super) area: Area, area: Area,
ratio: f64, ratio: f64,
label: Option<ratatui::text::Span<'static>>, label: Option<ratatui::text::Span<'static>>,
@ -25,6 +25,18 @@ impl Gauge {
} }
} }
impl TryFrom<&AnyUserData> for Gauge {
type Error = mlua::Error;
fn try_from(value: &AnyUserData) -> Result<Self, Self::Error> { 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 { impl Widget for Gauge {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where where

View file

@ -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<Mutex<yazi_widgets::input::Input>>,
}
impl Input {
pub fn compose(lua: &Lua) -> mlua::Result<Value> {
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<Mutex<yazi_widgets::input::Input>> {
fn from(input: &Input) -> Self { input.inner.clone() }
}
impl TryFrom<&AnyUserData> for Input {
type Error = mlua::Error;
fn try_from(value: &AnyUserData) -> Result<Self, Self::Error> {
Ok(value.borrow::<Self>()?.clone())
}
}
impl TryFrom<Table> for Input {
type Error = mlua::Error;
fn try_from(tb: Table) -> Result<Self, Self::Error> {
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<Self> {
match value {
Value::Table(tb) => Self::try_from(tb),
_ => Err(EXPECTED.into_lua_err()),
}
}
}
impl UserData for Input {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
impl_area_method!(methods);
methods.add_function("focus", |lua, (ud, focus): (AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.focus = focus;
ud.into_lua(lua)
});
}
}

View file

@ -6,13 +6,13 @@ use ratatui::widgets::Widget;
use unicode_width::UnicodeWidthChar; use unicode_width::UnicodeWidthChar;
use super::{Area, Span}; 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"; const EXPECTED: &str = "expected a string, Span, Line, or a table of them";
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Line { pub struct Line {
pub(super) area: Area, area: Area,
pub(super) inner: ratatui::text::Line<'static>, pub(super) inner: ratatui::text::Line<'static>,
} }
@ -57,6 +57,20 @@ impl From<ratatui::text::Line<'static>> for Line {
} }
} }
impl TryFrom<&AnyUserData> for Line {
type Error = mlua::Error;
fn try_from(value: &AnyUserData) -> Result<Self, Self::Error> {
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<Table> for Line { impl TryFrom<Table> for Line {
type Error = mlua::Error; type Error = mlua::Error;
@ -86,6 +100,12 @@ impl From<Line> for ratatui::text::Line<'static> {
fn from(value: Line) -> Self { value.inner } 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 { impl Widget for Line {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where where
@ -106,22 +126,11 @@ impl Widget for &Line {
impl FromLua for Line { impl FromLua for Line {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
Ok(Self { Ok(match value {
inner: match value { Value::Table(tb) => Self::try_from(tb)?,
Value::Table(tb) => return Self::try_from(tb), Value::String(s) => Self { inner: s.to_string_lossy().into(), ..Default::default() },
Value::String(s) => s.to_string_lossy().into(), Value::UserData(ud) => Self::try_from(&ud)?,
Value::UserData(ud) => { _ => Err(EXPECTED.into_lua_err())?,
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()
}) })
} }
} }

View file

@ -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 ratatui::widgets::Widget;
use super::{Area, Text}; use super::{Area, Text};
use crate::elements::Spatial;
// --- List // --- List
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct List { pub struct List {
pub(super) area: Area, area: Area,
inner: ratatui::widgets::List<'static>, 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<Self, Self::Error> { 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 { impl Widget for List {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where where

View file

@ -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);

View file

@ -30,6 +30,14 @@ impl From<Pos> for yazi_config::popup::Position {
fn from(value: Pos) -> Self { value.inner } fn from(value: Pos) -> Self { value.inner }
} }
impl TryFrom<&AnyUserData> for Pos {
type Error = mlua::Error;
fn try_from(ud: &AnyUserData) -> Result<Self, Self::Error> {
if let Ok(pos) = ud.borrow() { Ok(*pos) } else { Err(EXPECTED.into_lua_err()) }
}
}
impl TryFrom<Table> for Pos { impl TryFrom<Table> for Pos {
type Error = mlua::Error; type Error = mlua::Error;
@ -50,17 +58,11 @@ impl TryFrom<Table> for Pos {
impl FromLua for Pos { impl FromLua for Pos {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
Ok(match value { match value {
Value::Table(tbl) => Self::try_from(tbl)?, Value::Table(tbl) => Self::try_from(tbl),
Value::UserData(ud) => { Value::UserData(ud) => Self::try_from(&ud),
if let Ok(pos) = ud.borrow() { _ => Err(EXPECTED.into_lua_err()),
*pos }
} else {
Err(EXPECTED.into_lua_err())?
}
}
_ => Err(EXPECTED.into_lua_err())?,
})
} }
} }
@ -75,7 +77,7 @@ impl Pos {
} }
pub fn with_height(mut self, height: u16) -> Self { pub fn with_height(mut self, height: u16) -> Self {
self.inner.offset.height = height; self.inner.height = height;
self self
} }
} }
@ -84,10 +86,10 @@ impl UserData for Pos {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
// TODO: cache // TODO: cache
fields.add_field_method_get("1", |_, me| Ok(me.origin.into_str())); 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("x", |_, me| Ok(me.x));
fields.add_field_method_get("y", |_, me| Ok(me.offset.y)); fields.add_field_method_get("y", |_, me| Ok(me.y));
fields.add_field_method_get("w", |_, me| Ok(me.offset.width)); fields.add_field_method_get("w", |_, me| Ok(me.width));
fields.add_field_method_get("h", |_, me| Ok(me.offset.height)); fields.add_field_method_get("h", |_, me| Ok(me.height));
} }
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {

View file

@ -3,8 +3,8 @@ use std::any::TypeId;
use mlua::{AnyUserData, ExternalError, FromLua, Lua, Value}; use mlua::{AnyUserData, ExternalError, FromLua, Lua, Value};
use ratatui::widgets::Widget; use ratatui::widgets::Widget;
use super::{Area, Bar, Border, Clear, Fill, Gauge, Line, List, Table, Text}; use super::{Area, Bar, Border, Clear, Fill, Gauge, Input, Line, List, Table, Text};
use crate::Error; use crate::{Error, elements::Spatial};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Renderable { pub enum Renderable {
@ -15,38 +15,14 @@ pub enum Renderable {
Clear(Clear), Clear(Clear),
Fill(Fill), Fill(Fill),
Border(Border), Border(Border),
Input(Input),
Gauge(Box<Gauge>), Gauge(Box<Gauge>),
Table(Box<Table>), Table(Box<Table>),
} }
impl Renderable { 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<Area>) -> Self { pub fn with_area(mut self, area: impl Into<Area>) -> Self {
let area = area.into(); self.set_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 self
} }
@ -64,15 +40,16 @@ impl TryFrom<&AnyUserData> for Renderable {
fn try_from(ud: &AnyUserData) -> Result<Self, Self::Error> { fn try_from(ud: &AnyUserData) -> Result<Self, Self::Error> {
Ok(match ud.type_id() { Ok(match ud.type_id() {
Some(t) if t == TypeId::of::<Line>() => Self::Line(ud.take()?), Some(t) if t == TypeId::of::<Line>() => Self::Line(ud.try_into()?),
Some(t) if t == TypeId::of::<Text>() => Self::Text(ud.take()?), Some(t) if t == TypeId::of::<Text>() => Self::Text(ud.try_into()?),
Some(t) if t == TypeId::of::<List>() => Self::List(Box::new(ud.take()?)), Some(t) if t == TypeId::of::<List>() => Self::List(Box::new(ud.try_into()?)),
Some(t) if t == TypeId::of::<Bar>() => Self::Bar(ud.take()?), Some(t) if t == TypeId::of::<Bar>() => Self::Bar(ud.try_into()?),
Some(t) if t == TypeId::of::<Clear>() => Self::Clear(ud.take()?), Some(t) if t == TypeId::of::<Clear>() => Self::Clear(ud.try_into()?),
Some(t) if t == TypeId::of::<Fill>() => Self::Fill(ud.take()?), Some(t) if t == TypeId::of::<Fill>() => Self::Fill(ud.try_into()?),
Some(t) if t == TypeId::of::<Border>() => Self::Border(ud.take()?), Some(t) if t == TypeId::of::<Border>() => Self::Border(ud.try_into()?),
Some(t) if t == TypeId::of::<Gauge>() => Self::Gauge(Box::new(ud.take()?)), Some(t) if t == TypeId::of::<Input>() => Self::Input(ud.try_into()?),
Some(t) if t == TypeId::of::<Table>() => Self::Table(Box::new(ud.take()?)), Some(t) if t == TypeId::of::<Gauge>() => Self::Gauge(Box::new(ud.try_into()?)),
Some(t) if t == TypeId::of::<Table>() => Self::Table(Box::new(ud.try_into()?)),
_ => Err(format!("expected a renderable userdata, not: {ud:#?}").into_lua_err())?, _ => Err(format!("expected a renderable userdata, not: {ud:#?}").into_lua_err())?,
}) })
} }
@ -80,11 +57,39 @@ impl TryFrom<&AnyUserData> for Renderable {
impl From<Error> for Renderable { impl From<Error> for Renderable {
fn from(error: Error) -> Self { fn from(error: Error) -> Self {
Self::Text(Text { Self::Text(Text::from(error.into_string()).wrap(ratatui::widgets::Wrap { trim: false }))
inner: error.into_string().into(), }
wrap: ratatui::widgets::Wrap { trim: false }.into(), }
..Default::default()
}) 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::Clear(clear) => clear.render(rect, buf),
Self::Fill(fill) => fill.render(rect, buf), Self::Fill(fill) => fill.render(rect, buf),
Self::Border(border) => border.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::Gauge(gauge) => gauge.render(rect, buf),
Self::Table(table) => table.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::Clear(clear) => clear.render(rect, buf),
Renderable::Fill(fill) => fill.render(rect, buf), Renderable::Fill(fill) => fill.render(rect, buf),
Renderable::Border(border) => border.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::Gauge(gauge) => (&**gauge).render(rect, buf),
Renderable::Table(table) => (&**table).render(rect, buf), Renderable::Table(table) => (&**table).render(rect, buf),
} }

View file

@ -36,18 +36,20 @@ impl From<Row> for ratatui::widgets::Row<'static> {
} }
} }
impl TryFrom<&AnyUserData> for Row {
type Error = mlua::Error;
fn try_from(value: &AnyUserData) -> Result<Self, Self::Error> {
if let Ok(row) = value.take() { Ok(row) } else { Err(EXPECTED.into_lua_err()) }
}
}
impl FromLua for Row { impl FromLua for Row {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
Ok(match value { match value {
Value::UserData(ud) => { Value::UserData(ud) => Self::try_from(&ud),
if let Ok(row) = ud.take() { _ => Err(EXPECTED.into_lua_err()),
row }
} else {
Err(EXPECTED.into_lua_err())?
}
}
_ => Err(EXPECTED.into_lua_err())?,
})
} }
} }

View file

@ -58,19 +58,21 @@ impl Span {
} }
} }
impl TryFrom<&AnyUserData> for Span {
type Error = mlua::Error;
fn try_from(value: &AnyUserData) -> Result<Self, Self::Error> {
if let Ok(span) = value.take() { Ok(span) } else { Err(EXPECTED.into_lua_err()) }
}
}
impl FromLua for Span { impl FromLua for Span {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
Ok(Self(match value { Ok(match value {
Value::String(s) => s.to_string_lossy().into(), Value::String(s) => Self(s.to_string_lossy().into()),
Value::UserData(ud) => { Value::UserData(ud) => Self::try_from(&ud)?,
if let Ok(Self(span)) = ud.take() {
span
} else {
Err(EXPECTED.into_lua_err())?
}
}
_ => Err(EXPECTED.into_lua_err())?, _ => Err(EXPECTED.into_lua_err())?,
})) })
} }
} }

View file

@ -0,0 +1,7 @@
use crate::elements::Area;
pub trait Spatial {
fn area(&self) -> Area;
fn set_area(&mut self, area: Area);
}

View file

@ -2,12 +2,12 @@ use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, UserData, UserDataMethods, Val
use ratatui::widgets::{StatefulWidget, Widget}; use ratatui::widgets::{StatefulWidget, Widget};
use super::{Area, Row}; use super::{Area, Row};
use crate::{Style, elements::Constraint}; use crate::{Style, elements::{Constraint, Spatial}};
// --- Table // --- Table
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Table { pub struct Table {
pub area: Area, area: Area,
rows: Vec<Row>, rows: Vec<Row>,
header: Option<ratatui::widgets::Row<'static>>, header: Option<ratatui::widgets::Row<'static>>,
@ -60,10 +60,16 @@ impl Table {
} }
} }
impl TryFrom<AnyUserData> for Table { impl TryFrom<&AnyUserData> for Table {
type Error = mlua::Error; type Error = mlua::Error;
fn try_from(value: AnyUserData) -> Result<Self, Self::Error> { value.take() } fn try_from(value: &AnyUserData) -> Result<Self, Self::Error> { 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 { impl Widget for Table {

View file

@ -3,15 +3,16 @@ use std::{any::TypeId, mem};
use ansi_to_tui::IntoText; use ansi_to_tui::IntoText;
use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
use ratatui::widgets::Widget; use ratatui::widgets::Widget;
use yazi_shared::SStr;
use super::{Area, Line, Span, Wrap}; 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"; const EXPECTED: &str = "expected a string, Line, Span, or a table of them";
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Text { pub struct Text {
pub area: Area, area: Area,
// TODO: block // TODO: block
pub inner: ratatui::text::Text<'static>, 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.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;
text.into_lua(lua) text.into_lua(lua)
} }
pub fn wrap(mut self, wrap: impl Into<Wrap>) -> Self {
self.wrap = wrap.into();
self
}
}
impl From<ratatui::text::Text<'static>> for Text {
fn from(inner: ratatui::text::Text<'static>) -> Self { Self { inner, ..Default::default() } }
}
impl From<SStr> for Text {
fn from(value: SStr) -> Self { Self { inner: value.into(), ..Default::default() } }
} }
impl TryFrom<Table> for Text { impl TryFrom<Table> for Text {
@ -74,6 +88,12 @@ impl From<Text> 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 { impl Widget for Text {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where where
@ -100,24 +120,33 @@ impl Widget for &Text {
} }
} }
impl FromLua for Text { impl TryFrom<&AnyUserData> for Text {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { type Error = mlua::Error;
let inner = match value {
Value::Table(tb) => return Self::try_from(tb), fn try_from(value: &AnyUserData) -> Result<Self, Self::Error> {
Value::String(s) => s.to_string_lossy().into(), let inner = match value.type_id() {
Value::UserData(ud) => match ud.type_id() { Some(t) if t == TypeId::of::<Self>() => return value.take(),
Some(t) if t == TypeId::of::<Line>() => ud.take::<Line>()?.inner.into(), Some(t) if t == TypeId::of::<Line>() => value.take::<Line>()?.inner.into(),
Some(t) if t == TypeId::of::<Span>() => ud.take::<Span>()?.0.into(), Some(t) if t == TypeId::of::<Span>() => value.take::<Span>()?.0.into(),
Some(t) if t == TypeId::of::<Self>() => return ud.take(), Some(t) if t == TypeId::of::<Error>() => value.take::<Error>()?.into_string().into(),
Some(t) if t == TypeId::of::<Error>() => ud.take::<Error>()?.into_string().into(),
_ => Err(EXPECTED.into_lua_err())?,
},
_ => Err(EXPECTED.into_lua_err())?, _ => Err(EXPECTED.into_lua_err())?,
}; };
Ok(Self { inner, ..Default::default() }) Ok(Self { inner, ..Default::default() })
} }
} }
impl FromLua for Text {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
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 { impl UserData for Text {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
crate::impl_area_method!(methods); crate::impl_area_method!(methods);

View file

@ -50,11 +50,14 @@ macro_rules! impl_area_method {
"area", "area",
|lua, (ud, area): (mlua::AnyUserData, Option<mlua::AnyUserData>)| { |lua, (ud, area): (mlua::AnyUserData, Option<mlua::AnyUserData>)| {
use mlua::IntoLua; use mlua::IntoLua;
use crate::elements::Spatial;
if let Some(v) = area { if let Some(v) = area {
ud.borrow_mut::<Self>()?.area = $crate::elements::Area::try_from(v)?; ud.borrow_mut::<Self>()?.set_area((&v).try_into()?);
ud.into_lua(lua) ud.into_lua(lua)
} else { } else {
ud.borrow::<Self>()?.area.into_lua(lua) ud.borrow::<Self>()?.area().into_lua(lua)
} }
}, },
); );

View file

@ -42,7 +42,3 @@ pub struct Input {
pub shell_origin: Origin, pub shell_origin: Origin,
pub shell_offset: Offset, pub shell_offset: Offset,
} }
impl Input {
pub const fn border(&self) -> u16 { 2 }
}

View file

@ -1,4 +1,6 @@
use ratatui::layout::Rect; use std::ops::{Deref, DerefMut};
use ratatui::{layout::Rect, widgets::Padding};
use yazi_term::Dimension; use yazi_term::Dimension;
use super::{Offset, Origin}; use super::{Offset, Origin};
@ -9,6 +11,16 @@ pub struct Position {
pub offset: Offset, 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 { impl Position {
pub const fn new(origin: Origin, offset: Offset) -> Self { Self { origin, offset } } 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)), 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
}
} }

View file

@ -1,8 +1,8 @@
use ratatui::layout::{Position, Rect}; use ratatui::layout::{Margin, Position, Rect};
use yazi_shared::Layer; use yazi_shared::Layer;
use yazi_term::CursorStyle; 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 struct Core {
pub mgr: Mgr, pub mgr: Mgr,
@ -32,16 +32,18 @@ impl Core {
} }
pub fn cursor(&self) -> Option<(Position, CursorStyle)> { pub fn cursor(&self) -> Option<(Position, CursorStyle)> {
if self.input.visible { if let Some(guard) = self.input.lock() {
let Rect { x, y, .. } = self.mgr.area(self.input.position); let Rect { x, y, .. } = match &guard {
return Some(( InputGuard::Main(_) => self.mgr.area(self.input.main_position).inner(Margin::new(1, 1)),
Position { x: x + 1 + self.input.cursor(), y: y + 1 }, InputGuard::Alt(alt) => self.mgr.area(alt.pos),
self.input.cursor_shape(), };
)); return Some((Position { x: x + guard.cursor(), y }, guard.cursor_shape()));
} }
if let Some((x, y)) = self.help.cursor() { if let Some((x, y)) = self.help.cursor() {
return Some((Position { x, y }, self.help.cursor_shape())); return Some((Position { x, y }, self.help.cursor_shape()));
} }
None None
} }
@ -54,7 +56,7 @@ impl Core {
Layer::Help Layer::Help
} else if self.confirm.visible { } else if self.confirm.visible {
Layer::Confirm Layer::Confirm
} else if self.input.visible { } else if self.input.focus() {
Layer::Input Layer::Input
} else if self.pick.visible { } else if self.pick.visible {
Layer::Pick Layer::Pick

View file

@ -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,
}
}
}

View file

@ -1,22 +1,40 @@
use std::ops::{Deref, DerefMut}; use std::sync::Arc;
use parking_lot::Mutex;
use yazi_config::popup::Position; use yazi_config::popup::Position;
use crate::input::{InputGuard, InputMutGuard};
#[derive(Default)] #[derive(Default)]
pub struct Input { pub struct Input {
pub(super) inner: yazi_widgets::input::Input, pub main: yazi_widgets::input::Input,
pub alt: Option<Arc<Mutex<yazi_widgets::input::Input>>>,
pub visible: bool, pub main_visible: bool,
pub title: String, pub main_title: String,
pub position: Position, pub main_position: Position,
} }
impl Deref for Input { impl Input {
type Target = yazi_widgets::input::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<InputGuard<'_>> {
} 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 { pub fn lock_mut(&mut self) -> Option<InputMutGuard<'_>> {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } if self.main_visible {
Some(InputMutGuard::Main(&mut self.main))
} else if let Some(alt) = &self.alt {
Some(InputMutGuard::Alt(alt.lock()))
} else {
None
}
}
} }

View file

@ -1 +1 @@
yazi_macro::mod_flat!(input); yazi_macro::mod_flat!(guard input);

View file

@ -26,9 +26,7 @@ impl Pick {
impl Scrollable for Pick { impl Scrollable for Pick {
fn total(&self) -> usize { self.items.len() } fn total(&self) -> usize { self.items.len() }
fn limit(&self) -> usize { fn limit(&self) -> usize { self.position.height.saturating_sub(YAZI.pick.border()) as usize }
self.position.offset.height.saturating_sub(YAZI.pick.border()) as usize
}
fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor } fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor }

View file

@ -50,7 +50,7 @@ impl MgrProxy {
skip: job.skip, skip: job.skip,
area: area.into(), area: area.into(),
data: vec![ 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), Renderable::from(Error::custom(error)).with_area(area),
], ],
}); });

View file

@ -155,7 +155,8 @@ impl Folder {
len.saturating_sub(limit).min(self.offset) len.saturating_sub(limit).min(self.offset)
} else { } else {
len.saturating_sub(limit).min(self.cursor.saturating_sub(limit) + 1 + scrolloff) len.saturating_sub(limit).min(self.cursor.saturating_sub(limit) + 1 + scrolloff)
}; }
.min(self.cursor);
old != self.offset old != self.offset
} }

View file

@ -1 +1 @@
yazi_macro::mod_flat!(app render); yazi_macro::mod_flat!(app render sync_guard);

View file

@ -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 anyhow::Result;
use ratatui::layout::Position;
use yazi_actor::{Ctx, lives::Lives}; use yazi_actor::{Ctx, lives::Lives};
use yazi_binding::runtime_scope; use yazi_binding::runtime_scope;
use yazi_config::LAYOUT; use yazi_config::LAYOUT;
use yazi_macro::{act, succ, writef}; use yazi_macro::{act, succ};
use yazi_plugin::LUA; use yazi_plugin::LUA;
use yazi_shared::{data::Data, event::NEED_RENDER}; 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 yazi_widgets::COLLISION;
use super::SyncGuard;
use crate::{app::App, root::Root}; use crate::{app::App, root::Root};
impl App { impl App {
@ -24,9 +22,7 @@ impl App {
return self.render_partially(); return self.render_partially();
} }
Self::routine(true, None); let guard = SyncGuard::enter();
let _guard = scopeguard::guard(self.core.cursor(), |c| Self::routine(false, c));
let collision = COLLISION.swap(false, Ordering::Relaxed); let collision = COLLISION.swap(false, Ordering::Relaxed);
let preview_rect = LAYOUT.get().preview; let preview_rect = LAYOUT.get().preview;
term.draw(|f| { term.draw(|f| {
@ -45,6 +41,8 @@ impl App {
} else if preview_rect != LAYOUT.get().preview { } else if preview_rect != LAYOUT.get().preview {
act!(mgr:peek, cx)?; // Reload preview if layout changed act!(mgr:peek, cx)?; // Reload preview if layout changed
} }
guard.finish(self.core.cursor());
succ!(); succ!();
} }
@ -54,9 +52,7 @@ impl App {
return self.render(false); return self.render(false);
} }
Self::routine(true, None); let guard = SyncGuard::enter();
let _guard = scopeguard::guard(self.core.cursor(), |c| Self::routine(false, c));
term.draw_partial(|f| { term.draw_partial(|f| {
_ = Lives::scope(&mut self.core, |core| { _ = Lives::scope(&mut self.core, |core| {
runtime_scope!(LUA, "root", { runtime_scope!(LUA, "root", {
@ -67,29 +63,7 @@ impl App {
}); });
})?; })?;
guard.finish(self.core.cursor());
succ!(); 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();
};
}
} }

View file

@ -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();
}
}
}

View file

@ -37,7 +37,7 @@ impl Widget for Cmp<'_> {
}) })
.collect(); .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 { let mut area = Position::sticky(TERM.dimension(), input_area, Offset {
x: 1, x: 1,
y: 0, y: 0,

View file

@ -88,12 +88,11 @@ impl<'a> Dispatcher<'a> {
} }
fn dispatch_paste(&mut self, str: String) -> Result<()> { fn dispatch_paste(&mut self, str: String) -> Result<()> {
if self.app.core.input.visible { if let Some(mut guard) = self.app.core.input.lock_mut() {
let input = &mut self.app.core.input; if guard.mode() == InputMode::Insert {
if input.mode() == InputMode::Insert { guard.type_str(&str)?;
input.type_str(&str)?; } else if guard.mode() == InputMode::Replace {
} else if input.mode() == InputMode::Replace { guard.replace_str(&str)?;
input.replace_str(&str)?;
} }
} }
Ok(()) Ok(())

View file

@ -249,40 +249,39 @@ impl<'a> Executor<'a> {
} }
fn input(&mut self, action: ActionCow) -> Result<Data> { fn input(&mut self, action: ActionCow) -> Result<Data> {
let mode = self.app.core.input.mode(); let Some(mut guard) = self.app.core.input.lock_mut() else { succ!() };
let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;
macro_rules! on { macro_rules! on {
($name:ident) => { ($name:ident) => {
if action.name == stringify!($name) { 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!(escape);
on!(show); on!(show);
on!(close); on!(close);
match mode { match guard.mode() {
InputMode::Normal => { InputMode::Normal => match action.name.as_ref() {
match action.name.as_ref() { "help" => on!(help:toggle, Layer::Input),
// Help "plugin" => on!(app:plugin, action),
"help" => return act!(help:toggle, cx, Layer::Input), "lua" => on!(app:lua, action),
// Plugin _ => {}
"plugin" => return act!(app:plugin, cx, action), },
// Lua
"lua" => return act!(app:lua, cx, action),
_ => {}
}
}
InputMode::Insert => { InputMode::Insert => {
on!(complete); on!(complete);
} }
InputMode::Replace => {} InputMode::Replace => {}
}; };
self.app.core.input.execute(action) guard.execute(action)
} }
fn confirm(&mut self, action: ActionCow) -> Result<Data> { fn confirm(&mut self, action: ActionCow) -> Result<Data> {

View file

@ -13,16 +13,16 @@ impl<'a> Input<'a> {
impl Widget for Input<'_> { impl Widget for Input<'_> {
fn render(self, _: Rect, buf: &mut Buffer) { fn render(self, _: Rect, buf: &mut Buffer) {
let input = &self.core.input; 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() Block::bordered()
.border_type(BorderType::Rounded) .border_type(BorderType::Rounded)
.border_style(THEME.input.border.get()) .border_style(THEME.input.border.get())
.title(Line::styled(&input.title, THEME.input.title.get())) .title(Line::styled(&input.main_title, THEME.input.title.get()))
.render(area, buf); .render(outer, buf);
input.render(area.inner(Margin::new(1, 1)), buf); input.main.render(outer.inner(Margin::new(1, 1)), buf);
} }
} }

View file

@ -1,4 +1,5 @@
use ratatui::{buffer::Buffer, widgets::Widget}; use ratatui::{buffer::Buffer, widgets::Widget};
use yazi_binding::elements::Spatial;
use yazi_config::LAYOUT; use yazi_config::LAYOUT;
use yazi_core::Core; use yazi_core::Core;

View file

@ -1,6 +1,6 @@
use mlua::{ObjectLike, Table}; use mlua::{ObjectLike, Table};
use ratatui::{buffer::Buffer, layout::Rect}; use ratatui::{buffer::Buffer, layout::Rect};
use yazi_binding::elements::Renderables; use yazi_binding::elements::{Renderable, Renderables};
use yazi_core::Core; use yazi_core::Core;
use yazi_plugin::LUA; use yazi_plugin::LUA;
@ -33,9 +33,17 @@ impl<'a> Renderer<'a> {
.globals() .globals()
.raw_get::<Table>(self.component)? .raw_get::<Table>(self.component)?
.call_method::<Table>(self.constructor, area)? .call_method::<Table>(self.constructor, area)?
.call_method("redraw", ())?; .call_method(self.redrawer, ())?;
self.core.input.alt = None;
Renderables::reduce(value, |element| { 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)); element.render_with(buf, |p| self.core.mgr.area(p));
}); });

View file

@ -42,7 +42,7 @@ impl Widget for Root<'_> {
pick::Pick::new(self.core).render(area, buf); 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); input::Input::new(self.core).render(area, buf);
} }

View file

@ -14,17 +14,20 @@ impl<'a> Router<'a> {
pub(super) fn new(app: &'a mut App) -> Self { Self { app } } pub(super) fn new(app: &'a mut App) -> Self { Self { app } }
pub(super) fn route(&mut self, key: Key) -> Result<bool> { pub(super) fn route(&mut self, key: Key) -> Result<bool> {
let core = &mut self.app.core; use Layer as L;
let layer = core.layer();
let core = &mut self.app.core;
if core.help.visible && core.help.r#type(&key)? { if core.help.visible && core.help.r#type(&key)? {
return Ok(true); 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); return Ok(true);
} }
use Layer as L; let layer = core.layer();
Ok(match layer { Ok(match layer {
L::Null | L::App | L::Notify => unreachable!(), L::Null | L::App | L::Notify => unreachable!(),
L::Mgr | L::Tasks | L::Spot | L::Pick | L::Input | L::Confirm | L::Help => { L::Mgr | L::Tasks | L::Spot | L::Pick | L::Input | L::Confirm | L::Help => {

View file

@ -1,4 +1,5 @@
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
use yazi_binding::elements::Spatial;
use yazi_core::Core; use yazi_core::Core;
pub(crate) struct Spot<'a> { pub(crate) struct Spot<'a> {

View file

@ -2,7 +2,7 @@
macro_rules! input { macro_rules! input {
($cx:ident, $cfg:expr) => {{ ($cx:ident, $cfg:expr) => {{
let (tx, rx) = ::tokio::sync::mpsc::unbounded_channel(); 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), Ok(_) => Ok(rx),
Err(e) => Err(e) Err(e) => Err(e)
} }

View file

@ -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; use yazi_shared::event::ActionCow;
#[derive(Debug, Default)] #[derive(Debug, Default, Deserialize, Serialize)]
pub struct CloseForm { pub struct CloseForm {
pub submit: bool, pub submit: bool,
} }
@ -15,9 +17,9 @@ impl From<bool> for CloseForm {
} }
impl FromLua for CloseForm { impl FromLua for CloseForm {
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) } fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> { lua.from_value(value) }
} }
impl IntoLua for CloseForm { impl IntoLua for CloseForm {
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) } fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.to_value_with(&self, SER_OPT) }
} }

View file

@ -20,6 +20,10 @@ pub enum SparkKind {
// mgr:quit // mgr:quit
KeyQuit, KeyQuit,
// input:close
KeyInputClose,
IndInputClose,
// which:activate // which:activate
IndWhichActivate, IndWhichActivate,

View file

@ -181,6 +181,10 @@ impl<'a> Spark<'a> {
// mgr:quit // mgr:quit
KeyQuit => Self::Quit(<_>::from_lua(value, lua)?), 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 // which:activate
IndWhichActivate => Self::WhichActivate(<_>::from_lua(value, lua)?), IndWhichActivate => Self::WhichActivate(<_>::from_lua(value, lua)?),

View file

@ -56,7 +56,7 @@ function Root:click(event, up)
Root._dragging = not up and c or nil Root._dragging = not up and c or nil
if tostring(cx.layer) == "mgr" then 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
end end
@ -65,7 +65,7 @@ function Root:scroll(event, step)
return return
end end
local c = ya.child_at(ui.Rect { x = event.x, y = event.y }, self:reflow()) 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 end
function Root:touch(event, step) function Root:touch(event, step)
@ -73,7 +73,7 @@ function Root:touch(event, step)
return return
end end
local c = ya.child_at(ui.Rect { x = event.x, y = event.y }, self:reflow()) 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 end
function Root:move(event) end function Root:move(event) end

View file

@ -1,5 +1,5 @@
use mlua::{ExternalError, Function, IntoLuaMulti, Lua, Table, Value}; 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_core::{Highlighter, MgrProxy, tab::PreviewLock};
use yazi_fs::FsUrl; use yazi_fs::FsUrl;
use yazi_runner::previewer::PeekError; 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); MgrProxy::update_peeked(lock);
().into_lua_multi(&lua) ().into_lua_multi(&lua)
@ -43,7 +43,7 @@ impl Utils {
Err(e) => { Err(e) => {
if let Ok(err) = ud.take::<Error>() { if let Ok(err) = ud.take::<Error>() {
vec![ 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), Renderable::from(err).with_area(lock.area),
] ]
} else { } else {

View file

@ -1,5 +1,5 @@
use mlua::{AnyUserData, Function, Lua, Table}; 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_config::THEME;
use yazi_core::spot::SpotLock; use yazi_core::spot::SpotLock;
use yazi_proxy::MgrProxy; use yazi_proxy::MgrProxy;
@ -10,13 +10,13 @@ impl Utils {
pub(super) fn spot_table(lua: &Lua) -> mlua::Result<Function> { pub(super) fn spot_table(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, (t, table): (mlua::Table, AnyUserData)| { lua.create_function(|_, (t, table): (mlua::Table, AnyUserData)| {
let mut lock = SpotLock::try_from(t)?; 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; let area = table.area();
table.area = area.inner(ratatui::widgets::Padding::uniform(1)); table.set_area(area.inner(ratatui::widgets::Padding::uniform(1)));
lock.data = vec![ lock.data = vec![
Renderable::Clear(yazi_binding::elements::Clear { area }), Renderable::Clear(Default::default()).with_area(area),
Renderable::Border(yazi_binding::elements::Border { Renderable::Border(yazi_binding::elements::Border {
area, area,
edge: Edge(ratatui::widgets::Borders::ALL), edge: Edge(ratatui::widgets::Borders::ALL),

View file

@ -24,7 +24,7 @@ foldhash = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
inventory = { workspace = true } inventory = { workspace = true }
memchr = "2.8.1" memchr = "2.8.2"
mlua = { workspace = true } mlua = { workspace = true }
ordered-float = { workspace = true } ordered-float = { workspace = true }
parking_lot = { workspace = true } parking_lot = { workspace = true }

View file

@ -45,6 +45,7 @@ impl PartialEq<u64> for Id {
} }
// --- Ids // --- Ids
#[derive(Debug)]
pub struct Ids { pub struct Ids {
next: AtomicU64, next: AtomicU64,
} }

View file

@ -55,7 +55,6 @@ impl<T> RoCell<T> {
self.inner.get().replace(MaybeUninit::uninit()).assume_init() self.inner.get().replace(MaybeUninit::uninit()).assume_init()
} }
} }
} }
impl<T> Default for RoCell<T> { impl<T> Default for RoCell<T> {

View file

@ -13,4 +13,4 @@ rust-version.workspace = true
workspace = true workspace = true
[build-dependencies] [build-dependencies]
vergen-gitcl = { version = "9.1.0", features = [ "build", "rustc" ] } vergen-gitcl = { version = "10.0.0", features = [ "build", "rustc" ] }

View file

@ -1,22 +1,17 @@
use std::{env, error::Error}; use std::{env, error::Error};
use vergen_gitcl::{BuildBuilder, Emitter, GitclBuilder, RustcBuilder}; use vergen_gitcl::{Build, Emitter, Gitcl, Rustc};
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
Emitter::default() Emitter::default()
.add_instructions(&BuildBuilder::default().build_date(true).build()?)? .add_instructions(&Build::builder().build_date(true).build())?
.add_instructions( .add_instructions(
&RustcBuilder::default() &Rustc::builder().commit_date(true).commit_hash(true).host_triple(true).semver(true).build(),
.commit_date(true)
.commit_hash(true)
.host_triple(true)
.semver(true)
.build()?,
)? )?
.emit()?; .emit()?;
if env::var_os("YAZI_NO_GITCL").is_none() { 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 { } else {
println!("cargo:rustc-env=VERGEN_GIT_SHA=no-gitcl"); println!("cargo:rustc-env=VERGEN_GIT_SHA=no-gitcl");
} }

View file

@ -22,7 +22,7 @@ impl Input {
snap.value.replace_range(start..end, &casefied); snap.value.replace_range(start..end, &casefied);
snap.op = InputOp::None; snap.op = InputOp::None;
snap.cursor = range.start; 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)?; act!(r#move, self)?;
succ!(render!()); succ!(render!());

View file

@ -19,7 +19,7 @@ impl Input {
snap.mode = InputMode::Normal; snap.mode = InputMode::Normal;
} }
} }
self.snaps.tag(self.limit); self.snaps.tag(self.pos.width as usize);
succ!(); succ!();
} }
} }

View file

@ -15,7 +15,7 @@ impl Input {
render!(self.handle_op(opt.step.add(&snap.value, snap.cursor), false)); render!(self.handle_op(opt.step.add(&snap.value, snap.cursor), false));
let n_cur = self.snap().cursor; 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() { if snap.value.is_empty() {
succ!(snap.offset = 0); succ!(snap.offset = 0);
} }

View file

@ -29,7 +29,7 @@ impl Input {
(Some(_), Some((len, _))) => snap.value.replace_range(start..start + len, &s), (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!()); succ!(render!());
} }
} }

View file

@ -2,7 +2,7 @@ use std::{borrow::Cow, ops::Range};
use anyhow::Result; use anyhow::Result;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use yazi_config::YAZI; use yazi_config::{YAZI, popup::Position};
use yazi_macro::act; use yazi_macro::act;
use yazi_shared::Ids; use yazi_shared::Ids;
use yazi_shim::path::CROSS_SEPARATOR; use yazi_shim::path::CROSS_SEPARATOR;
@ -11,10 +11,10 @@ use yazi_term::CursorStyle;
use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp}; use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp};
use crate::{CLIPBOARD, input::{InputEvent, InputOpt}}; use crate::{CLIPBOARD, input::{InputEvent, InputOpt}};
#[derive(Default)] #[derive(Debug, Default)]
pub struct Input { pub struct Input {
pub pos: Position,
pub snaps: InputSnaps, pub snaps: InputSnaps,
pub limit: usize,
pub obscure: bool, pub obscure: bool,
pub realtime: bool, pub realtime: bool,
pub completion: bool, pub completion: bool,
@ -25,15 +25,15 @@ pub struct Input {
impl Input { impl Input {
pub fn new(opt: InputOpt) -> Result<Self> { pub fn new(opt: InputOpt) -> Result<Self> {
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 { let mut input = Self {
pos: opt.cfg.position,
snaps: InputSnaps::new(opt.cfg.value, opt.cfg.obscure, limit), snaps: InputSnaps::new(opt.cfg.value, opt.cfg.obscure, limit),
limit,
obscure: opt.cfg.obscure, obscure: opt.cfg.obscure,
realtime: opt.cfg.realtime, realtime: opt.cfg.realtime,
completion: opt.cfg.completion, completion: opt.cfg.completion,
tx: Some(opt.tx), tx: opt.tx,
..Default::default() ..Default::default()
}; };
@ -45,6 +45,11 @@ impl Input {
Ok(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 { pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
let old = self.snap().clone(); let old = self.snap().clone();
let snap = self.snap_mut(); let snap = self.snap_mut();
@ -81,7 +86,7 @@ impl Input {
return false; return false;
} }
if !matches!(old.op, InputOp::None | InputOp::Select(_)) { 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 true
} }
@ -111,9 +116,9 @@ impl Input {
pub fn display(&self) -> Cow<'_, str> { pub fn display(&self) -> Cow<'_, str> {
if self.obscure { if self.obscure {
"".repeat(self.snap().window(self.limit).len()).into() "".repeat(self.snap().window(self.pos.width as usize).len()).into()
} else { } 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) = let (start, end) =
if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) }; 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 Range { start, end } = start.max(win.start)..end.min(win.end);
let s = snap.width(snap.offset..start); let s = snap.width(snap.offset..start);

View file

@ -9,7 +9,7 @@ use crate::input::InputEvent;
#[derive(Debug)] #[derive(Debug)]
pub struct InputOpt { pub struct InputOpt {
pub cfg: InputCfg, pub cfg: InputCfg,
pub tx: mpsc::UnboundedSender<InputEvent>, pub tx: Option<mpsc::UnboundedSender<InputEvent>>,
} }
impl TryFrom<ActionCow> for InputOpt { impl TryFrom<ActionCow> for InputOpt {
@ -17,14 +17,10 @@ impl TryFrom<ActionCow> for InputOpt {
fn try_from(mut a: ActionCow) -> Result<Self, Self::Error> { fn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {
let Some(cfg) = a.take_any("cfg") else { 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 { Ok(Self { cfg, tx: a.take_any("tx") })
bail!("Invalid 'tx' in InputOpt");
};
Ok(Self { cfg, tx })
} }
} }

View file

@ -34,7 +34,6 @@ impl InputSnap {
snap snap
} }
#[inline]
pub(super) fn resize(&mut self, limit: usize) { pub(super) fn resize(&mut self, limit: usize) {
let count = self.count(); let count = self.count();
let limit = if self.obscure { let limit = if self.obscure {

View file

@ -2,7 +2,7 @@ use std::mem;
use super::InputSnap; use super::InputSnap;
#[derive(PartialEq, Eq)] #[derive(Debug, Eq, PartialEq)]
pub struct InputSnaps { pub struct InputSnaps {
idx: usize, idx: usize,
versions: Vec<InputSnap>, versions: Vec<InputSnap>,

View file

@ -10,8 +10,6 @@ impl Widget for &Input {
where where
Self: Sized, Self: Sized,
{ {
crate::Clear.render(area, buf);
Line::styled(self.display(), THEME.input.value.get()).render(area, buf); Line::styled(self.display(), THEME.input.value.get()).render(area, buf);
if let Some(Range { start, end }) = self.selected() { if let Some(Range { start, end }) = self.selected() {