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])
- 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

132
Cargo.lock generated
View file

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

View file

@ -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" ] }

View file

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

View file

@ -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<Data> {
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<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";
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
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)
}
}

View file

@ -15,16 +15,20 @@ impl Actor for Escape {
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
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!());
}
}

View file

@ -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<Data> {
fn act(cx: &mut Ctx, mut form: Self::Form) -> Result<Data> {
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!());
}

View file

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

View file

@ -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<ratatui::layout::Rect> for Area {
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;
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>() {
Self::Rect(*rect)
} else if let Ok(pos) = value.borrow::<Pos>() {
@ -72,7 +83,7 @@ impl TryFrom<AnyUserData> for Area {
impl FromLua for Area {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
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<Value> {
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),
}
}
}

View file

@ -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<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 {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where

View file

@ -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<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 {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
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 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<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 {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where

View file

@ -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<Self, Self::Error> { Ok(*ud.borrow()?) }
}
impl FromLua for Color {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
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::<Self>()?.0,
Value::UserData(ud) => return Self::try_from(&ud),
_ => 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"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)?,

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 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<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 {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
where

View file

@ -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<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 {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
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 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<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 {
type Error = mlua::Error;
@ -86,6 +100,12 @@ impl From<Line> 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<Self> {
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())?
}
}
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())?,
},
..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 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<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 {
fn render(self, rect: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
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 }
}
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 {
type Error = mlua::Error;
@ -50,18 +58,12 @@ impl TryFrom<Table> for Pos {
impl FromLua for Pos {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
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())?
match value {
Value::Table(tbl) => Self::try_from(tbl),
Value::UserData(ud) => Self::try_from(&ud),
_ => Err(EXPECTED.into_lua_err()),
}
}
_ => Err(EXPECTED.into_lua_err())?,
})
}
}
impl Pos {
@ -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<F: UserDataFields<Self>>(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<M: UserDataMethods<Self>>(methods: &mut M) {

View file

@ -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<Gauge>),
Table(Box<Table>),
}
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 {
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<Self, Self::Error> {
Ok(match ud.type_id() {
Some(t) if t == TypeId::of::<Line>() => Self::Line(ud.take()?),
Some(t) if t == TypeId::of::<Text>() => Self::Text(ud.take()?),
Some(t) if t == TypeId::of::<List>() => Self::List(Box::new(ud.take()?)),
Some(t) if t == TypeId::of::<Bar>() => Self::Bar(ud.take()?),
Some(t) if t == TypeId::of::<Clear>() => Self::Clear(ud.take()?),
Some(t) if t == TypeId::of::<Fill>() => Self::Fill(ud.take()?),
Some(t) if t == TypeId::of::<Border>() => Self::Border(ud.take()?),
Some(t) if t == TypeId::of::<Gauge>() => Self::Gauge(Box::new(ud.take()?)),
Some(t) if t == TypeId::of::<Table>() => Self::Table(Box::new(ud.take()?)),
Some(t) if t == TypeId::of::<Line>() => Self::Line(ud.try_into()?),
Some(t) if t == TypeId::of::<Text>() => Self::Text(ud.try_into()?),
Some(t) if t == TypeId::of::<List>() => Self::List(Box::new(ud.try_into()?)),
Some(t) if t == TypeId::of::<Bar>() => Self::Bar(ud.try_into()?),
Some(t) if t == TypeId::of::<Clear>() => Self::Clear(ud.try_into()?),
Some(t) if t == TypeId::of::<Fill>() => Self::Fill(ud.try_into()?),
Some(t) if t == TypeId::of::<Border>() => Self::Border(ud.try_into()?),
Some(t) if t == TypeId::of::<Input>() => Self::Input(ud.try_into()?),
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())?,
})
}
@ -80,11 +57,39 @@ impl TryFrom<&AnyUserData> for Renderable {
impl From<Error> 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),
}

View file

@ -36,19 +36,21 @@ 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 {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
Ok(match value {
Value::UserData(ud) => {
if let Ok(row) = ud.take() {
row
} else {
Err(EXPECTED.into_lua_err())?
match value {
Value::UserData(ud) => Self::try_from(&ud),
_ => Err(EXPECTED.into_lua_err()),
}
}
_ => Err(EXPECTED.into_lua_err())?,
})
}
}
impl UserData for Row {

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 {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
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())?,
}))
})
}
}

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 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<Row>,
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;
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 {

View file

@ -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<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 {
@ -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 {
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<Self> {
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::<Line>() => ud.take::<Line>()?.inner.into(),
Some(t) if t == TypeId::of::<Span>() => ud.take::<Span>()?.0.into(),
Some(t) if t == TypeId::of::<Self>() => return ud.take(),
Some(t) if t == TypeId::of::<Error>() => ud.take::<Error>()?.into_string().into(),
_ => Err(EXPECTED.into_lua_err())?,
},
impl TryFrom<&AnyUserData> for Text {
type Error = mlua::Error;
fn try_from(value: &AnyUserData) -> Result<Self, Self::Error> {
let inner = match value.type_id() {
Some(t) if t == TypeId::of::<Self>() => return value.take(),
Some(t) if t == TypeId::of::<Line>() => value.take::<Line>()?.inner.into(),
Some(t) if t == TypeId::of::<Span>() => value.take::<Span>()?.0.into(),
Some(t) if t == TypeId::of::<Error>() => value.take::<Error>()?.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<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 {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
crate::impl_area_method!(methods);

View file

@ -50,11 +50,14 @@ macro_rules! impl_area_method {
"area",
|lua, (ud, area): (mlua::AnyUserData, Option<mlua::AnyUserData>)| {
use mlua::IntoLua;
use crate::elements::Spatial;
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)
} 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_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 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
}
}

View file

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

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 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<Arc<Mutex<yazi_widgets::input::Input>>>,
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<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 {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
pub fn lock_mut(&mut self) -> Option<InputMutGuard<'_>> {
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 {
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 }

View file

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

View file

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

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

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();
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,

View file

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

View file

@ -249,40 +249,39 @@ impl<'a> Executor<'a> {
}
fn input(&mut self, action: ActionCow) -> Result<Data> {
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<Data> {

View file

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

View file

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

View file

@ -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::<Table>(self.component)?
.call_method::<Table>(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));
});

View file

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

View file

@ -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<bool> {
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 => {

View file

@ -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> {

View file

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

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;
#[derive(Debug, Default)]
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct CloseForm {
pub submit: bool,
}
@ -15,9 +17,9 @@ impl From<bool> 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 {
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
KeyQuit,
// input:close
KeyInputClose,
IndInputClose,
// which:activate
IndWhichActivate,

View file

@ -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)?),

View file

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

View file

@ -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::<Error>() {
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 {

View file

@ -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<Function> {
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),

View file

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

View file

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

View file

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

View file

@ -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" ] }

View file

@ -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<dyn Error>> {
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");
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 {

View file

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

View file

@ -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() {