diff --git a/Cargo.lock b/Cargo.lock
index 99986894..7ae987c9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -620,9 +620,9 @@ dependencies = [
[[package]]
name = "deranged"
-version = "0.3.11"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
dependencies = [
"powerfmt",
]
@@ -2589,9 +2589,9 @@ dependencies = [
[[package]]
name = "time"
-version = "0.3.39"
+version = "0.3.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
+checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618"
dependencies = [
"deranged",
"itoa",
@@ -2606,15 +2606,15 @@ dependencies = [
[[package]]
name = "time-core"
-version = "0.1.3"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
+checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]]
name = "time-macros"
-version = "0.2.20"
+version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
+checksum = "29aa485584182073ed57fd5004aa09c371f021325014694e432313345865fd04"
dependencies = [
"num-conv",
"time-core",
@@ -3191,9 +3191,9 @@ dependencies = [
[[package]]
name = "windows-link"
-version = "0.1.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
+checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-result"
@@ -3433,6 +3433,7 @@ dependencies = [
"yazi-proxy",
"yazi-scheduler",
"yazi-shared",
+ "yazi-widgets",
]
[[package]]
@@ -3482,7 +3483,6 @@ dependencies = [
"ratatui",
"scopeguard",
"signal-hook-tokio",
- "syntect",
"textwrap",
"tikv-jemallocator",
"tokio",
@@ -3501,6 +3501,7 @@ dependencies = [
"yazi-plugin",
"yazi-proxy",
"yazi-shared",
+ "yazi-widgets",
]
[[package]]
@@ -3630,6 +3631,20 @@ dependencies = [
"yazi-macro",
]
+[[package]]
+name = "yazi-widgets"
+version = "25.3.7"
+dependencies = [
+ "futures",
+ "ratatui",
+ "unicode-width 0.2.0",
+ "yazi-codegen",
+ "yazi-config",
+ "yazi-macro",
+ "yazi-plugin",
+ "yazi-shared",
+]
+
[[package]]
name = "yoke"
version = "0.7.5"
diff --git a/Cargo.toml b/Cargo.toml
index 5d82d069..80ebeda0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -37,6 +37,7 @@ regex = "1.11.1"
scopeguard = "1.2.0"
serde = { version = "1.0.219", features = [ "derive" ] }
serde_json = "1.0.140"
+syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "1.44.1", features = [ "full" ] }
tokio-stream = "0.1.17"
tokio-util = "0.7.14"
diff --git a/yazi-config/src/popup/input.rs b/yazi-config/src/popup/input.rs
index f2c05c37..71c87ba0 100644
--- a/yazi-config/src/popup/input.rs
+++ b/yazi-config/src/popup/input.rs
@@ -46,7 +46,7 @@ pub struct Input {
}
impl Input {
- pub const fn border(&self) -> u16 { 2 }
+ pub const fn border(&self) -> u16 { 1 }
}
impl FromStr for Input {
diff --git a/yazi-core/Cargo.toml b/yazi-core/Cargo.toml
index d7ad6db4..2ad16e76 100644
--- a/yazi-core/Cargo.toml
+++ b/yazi-core/Cargo.toml
@@ -20,6 +20,7 @@ yazi-plugin = { path = "../yazi-plugin", version = "25.3.7" }
yazi-proxy = { path = "../yazi-proxy", version = "25.3.7" }
yazi-scheduler = { path = "../yazi-scheduler", version = "25.3.7" }
yazi-shared = { path = "../yazi-shared", version = "25.3.7" }
+yazi-widgets = { path = "../yazi-widgets", version = "25.3.7" }
# External dependencies
anyhow = { workspace = true }
diff --git a/yazi-core/src/cmp/commands/close.rs b/yazi-core/src/cmp/commands/close.rs
index 81e80634..6e1d3c46 100644
--- a/yazi-core/src/cmp/commands/close.rs
+++ b/yazi-core/src/cmp/commands/close.rs
@@ -1,3 +1,5 @@
+use std::mem;
+
use yazi_macro::render;
use yazi_proxy::InputProxy;
use yazi_shared::event::CmdCow;
@@ -23,7 +25,6 @@ impl Cmp {
}
self.caches.clear();
- self.visible = false;
- render!();
+ render!(mem::replace(&mut self.visible, false));
}
}
diff --git a/yazi-core/src/help/commands/filter.rs b/yazi-core/src/help/commands/filter.rs
index 29fcc540..0bfb54af 100644
--- a/yazi-core/src/help/commands/filter.rs
+++ b/yazi-core/src/help/commands/filter.rs
@@ -1,15 +1,11 @@
-use yazi_config::popup::{Offset, Origin, Position};
use yazi_macro::render;
use yazi_shared::event::CmdCow;
-use crate::{help::Help, input::Input};
+use crate::help::Help;
impl Help {
pub fn filter(&mut self, _: CmdCow) {
- let mut input = Input::default();
- input.position = Position::new(Origin::BottomLeft, Offset::line());
-
- self.in_filter = Some(input);
+ self.in_filter = Some(Default::default());
self.filter_apply();
render!();
}
diff --git a/yazi-core/src/help/help.rs b/yazi-core/src/help/help.rs
index 00670f8a..a9c12fc6 100644
--- a/yazi-core/src/help/help.rs
+++ b/yazi-core/src/help/help.rs
@@ -1,12 +1,11 @@
-use crossterm::event::KeyCode;
+use crossterm::{cursor::SetCursorStyle, event::KeyCode};
use unicode_width::UnicodeWidthStr;
use yazi_adapter::Dimension;
-use yazi_config::{KEYMAP, keymap::{Chord, Key}};
+use yazi_config::{INPUT, KEYMAP, keymap::{Chord, Key}};
use yazi_macro::{render, render_and};
use yazi_shared::Layer;
use super::HELP_MARGIN;
-use crate::input::Input;
#[derive(Default)]
pub struct Help {
@@ -16,7 +15,7 @@ pub struct Help {
// Filter
pub(super) keyword: String,
- pub(super) in_filter: Option,
+ pub(super) in_filter: Option,
pub(super) offset: usize,
pub(super) cursor: usize,
@@ -89,7 +88,7 @@ impl Help {
.as_ref()
.map(|i| i.value())
.or(Some(self.keyword.as_str()).filter(|&s| !s.is_empty()))
- .map(|s| format!("Filter: {}", s))
+ .map(|s| format!("Filter: {s}"))
}
// --- Bindings
@@ -113,4 +112,9 @@ impl Help {
#[inline]
pub fn rel_cursor(&self) -> usize { self.cursor - self.offset }
+
+ #[inline]
+ pub fn cursor_shape(&self) -> SetCursorStyle {
+ if INPUT.cursor_blink { SetCursorStyle::BlinkingBlock } else { SetCursorStyle::SteadyBlock }
+ }
}
diff --git a/yazi-core/src/input/commands/close.rs b/yazi-core/src/input/commands/close.rs
index a3a5bc26..816c012f 100644
--- a/yazi-core/src/input/commands/close.rs
+++ b/yazi-core/src/input/commands/close.rs
@@ -18,17 +18,15 @@ impl From for Opt {
impl Input {
#[yazi_codegen::command]
pub fn close(&mut self, opt: Opt) {
- if self.completion {
- CmpProxy::close();
- }
-
- if let Some(cb) = self.callback.take() {
- let value = self.snap_mut().value.clone();
- _ = cb.send(if opt.submit { Ok(value) } else { Err(InputError::Canceled(value)) });
- }
-
- self.ticket = self.ticket.wrapping_add(1);
self.visible = false;
+ self.ticket.next();
+
+ if let Some(tx) = self.tx.take() {
+ let value = self.snap().value.clone();
+ _ = tx.send(if opt.submit { Ok(value) } else { Err(InputError::Canceled(value)) });
+ }
+
+ CmpProxy::close();
render!();
}
}
diff --git a/yazi-core/src/input/commands/escape.rs b/yazi-core/src/input/commands/escape.rs
index f376a807..03811ddb 100644
--- a/yazi-core/src/input/commands/escape.rs
+++ b/yazi-core/src/input/commands/escape.rs
@@ -1,8 +1,9 @@
use yazi_macro::render;
use yazi_proxy::CmpProxy;
use yazi_shared::event::CmdCow;
+use yazi_widgets::input::InputOp;
-use crate::input::{Input, InputMode, op::InputOp};
+use crate::input::Input;
struct Opt;
@@ -16,28 +17,16 @@ impl From<()> for Opt {
impl Input {
#[yazi_codegen::command]
pub fn escape(&mut self, _: Opt) {
- let snap = self.snap_mut();
- match snap.mode {
- InputMode::Normal if snap.op == InputOp::None => {
- self.close(false);
- }
- InputMode::Normal => {
- snap.op = InputOp::None;
- }
- InputMode::Insert => {
- snap.mode = InputMode::Normal;
- self.move_(-1);
+ use yazi_widgets::input::InputMode as M;
- if self.completion {
- CmpProxy::close();
- }
- }
- InputMode::Replace => {
- snap.mode = InputMode::Normal;
- }
+ let mode = self.snap().mode;
+ match mode {
+ M::Normal if self.snap_mut().op == InputOp::None => self.close(false),
+ M::Insert => CmpProxy::close(),
+ M::Normal | M::Replace => {}
}
- self.snaps.tag(self.limit());
+ self.inner.escape(());
render!();
}
}
diff --git a/yazi-core/src/input/commands/mod.rs b/yazi-core/src/input/commands/mod.rs
index 87017d3f..d23f3447 100644
--- a/yazi-core/src/input/commands/mod.rs
+++ b/yazi-core/src/input/commands/mod.rs
@@ -1 +1 @@
-yazi_macro::mod_flat!(backspace backward close complete delete escape forward insert kill move_ paste redo replace show type_ undo visual yank);
+yazi_macro::mod_flat!(close escape show);
diff --git a/yazi-core/src/input/commands/show.rs b/yazi-core/src/input/commands/show.rs
index ac4e3879..0c3ea1f2 100644
--- a/yazi-core/src/input/commands/show.rs
+++ b/yazi-core/src/input/commands/show.rs
@@ -1,7 +1,8 @@
use tokio::sync::mpsc;
-use yazi_config::popup::InputCfg;
+use yazi_config::{INPUT, popup::InputCfg};
use yazi_macro::render;
use yazi_shared::{errors::InputError, event::CmdCow};
+use yazi_widgets::input::InputCallback;
use crate::input::Input;
@@ -20,7 +21,7 @@ impl TryFrom for Opt {
impl Input {
pub fn show(&mut self, opt: impl TryInto) {
- let Ok(opt) = opt.try_into() else { return };
+ let Ok(opt): Result = opt.try_into() else { return };
self.close(false);
self.visible = true;
@@ -28,17 +29,28 @@ impl Input {
self.position = opt.cfg.position;
// Typing
- self.callback = Some(opt.tx);
- self.realtime = opt.cfg.realtime;
- self.completion = opt.cfg.completion;
+ self.tx = Some(opt.tx.clone());
+ let ticket = self.ticket.clone();
// Shell
self.highlight = opt.cfg.highlight;
- // Reset snaps
- self.snaps.reset(opt.cfg.value, self.limit());
+ // Reset input
+ let cb: InputCallback = Box::new(move |before, after| {
+ if opt.cfg.realtime {
+ opt.tx.send(Err(InputError::Typed(format!("{before}{after}")))).ok();
+ } else if opt.cfg.completion {
+ opt.tx.send(Err(InputError::Completed(before.to_owned(), ticket.current()))).ok();
+ }
+ });
+ self.inner = yazi_widgets::input::Input::new(
+ opt.cfg.value,
+ opt.cfg.position.offset.width.saturating_sub(INPUT.border()) as usize,
+ cb,
+ );
// Set cursor after reset
+ // TODO: remove this
if let Some(cursor) = opt.cfg.cursor {
self.snap_mut().cursor = cursor;
self.move_(0);
diff --git a/yazi-core/src/input/input.rs b/yazi-core/src/input/input.rs
index 27dff313..879a0463 100644
--- a/yazi-core/src/input/input.rs
+++ b/yazi-core/src/input/input.rs
@@ -1,131 +1,31 @@
-use std::ops::Range;
+use std::{ops::{Deref, DerefMut}, rc::Rc};
use tokio::sync::mpsc::UnboundedSender;
-use unicode_width::UnicodeWidthStr;
-use yazi_config::{INPUT, popup::Position};
-use yazi_plugin::CLIPBOARD;
-use yazi_shared::errors::InputError;
-
-use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp};
+use yazi_config::popup::Position;
+use yazi_shared::{Ids, errors::InputError};
#[derive(Default)]
pub struct Input {
- pub(super) snaps: InputSnaps,
- pub ticket: usize,
- pub visible: bool,
+ pub(super) inner: yazi_widgets::input::Input,
+ pub visible: bool,
pub title: String,
pub position: Position,
// Typing
- pub(super) callback: Option>>,
- pub(super) realtime: bool,
- pub(super) completion: bool,
+ pub(super) tx: Option>>,
+ pub(super) ticket: Rc,
// Shell
pub highlight: bool,
}
-impl Input {
- #[inline]
- pub(super) fn limit(&self) -> usize {
- self.position.offset.width.saturating_sub(INPUT.border()) as usize
- }
+impl Deref for Input {
+ type Target = yazi_widgets::input::Input;
- pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
- let old = self.snap().clone();
- let snap = self.snap_mut();
-
- match snap.op {
- InputOp::None | InputOp::Select(_) => {
- snap.cursor = cursor;
- }
- InputOp::Delete(cut, insert, _) => {
- let range = snap.op.range(cursor, include).unwrap();
- let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
-
- let drain = snap.value.drain(start.unwrap()..end.unwrap()).collect::();
- if cut {
- futures::executor::block_on(CLIPBOARD.set(&drain));
- }
-
- snap.op = InputOp::None;
- snap.mode = if insert { InputMode::Insert } else { InputMode::Normal };
- snap.cursor = range.start;
- }
- InputOp::Yank(_) => {
- let range = snap.op.range(cursor, include).unwrap();
- let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
- let yanked = &snap.value[start.unwrap()..end.unwrap()];
-
- snap.op = InputOp::None;
- futures::executor::block_on(CLIPBOARD.set(yanked));
- }
- };
-
- snap.cursor = snap.count().saturating_sub(snap.mode.delta()).min(snap.cursor);
- if snap == &old {
- return false;
- }
- if !matches!(old.op, InputOp::None | InputOp::Select(_)) {
- self.snaps.tag(self.limit()).then(|| self.flush_value());
- }
- true
- }
-
- pub(super) fn flush_value(&mut self) {
- let Some(tx) = &self.callback else { return };
- self.ticket = self.ticket.wrapping_add(1);
-
- if self.realtime {
- let value = self.snap().value.clone();
- tx.send(Err(InputError::Typed(value))).ok();
- }
-
- if self.completion {
- let before = self.partition()[0].to_owned();
- tx.send(Err(InputError::Completed(before, self.ticket))).ok();
- }
- }
+ fn deref(&self) -> &Self::Target { &self.inner }
}
-impl Input {
- #[inline]
- pub fn value(&self) -> &str { self.snap().slice(self.snap().window(self.limit())) }
-
- #[inline]
- pub fn mode(&self) -> InputMode { self.snap().mode }
-
- #[inline]
- pub fn cursor(&self) -> u16 {
- let snap = self.snap();
- snap.slice(snap.offset..snap.cursor).width() as u16
- }
-
- pub fn selected(&self) -> Option> {
- let snap = self.snap();
- let start = snap.op.start()?;
-
- let (start, end) =
- if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) };
-
- let win = snap.window(self.limit());
- let Range { start, end } = start.max(win.start)..end.min(win.end);
-
- let s = snap.slice(snap.offset..start).width() as u16;
- Some(s..s + snap.slice(start..end).width() as u16)
- }
-
- #[inline]
- pub fn partition(&self) -> [&str; 2] {
- let snap = self.snap();
- let idx = snap.idx(snap.cursor).unwrap();
- [&snap.value[..idx], &snap.value[idx..]]
- }
-
- #[inline]
- pub(super) fn snap(&self) -> &InputSnap { self.snaps.current() }
-
- #[inline]
- pub(super) fn snap_mut(&mut self) -> &mut InputSnap { self.snaps.current_mut() }
+impl DerefMut for Input {
+ fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
}
diff --git a/yazi-core/src/input/mod.rs b/yazi-core/src/input/mod.rs
index bc6b58ab..e77b2fe8 100644
--- a/yazi-core/src/input/mod.rs
+++ b/yazi-core/src/input/mod.rs
@@ -1,3 +1,3 @@
yazi_macro::mod_pub!(commands);
-yazi_macro::mod_flat!(input mode op snap snaps);
+yazi_macro::mod_flat!(input);
diff --git a/yazi-fm/Cargo.toml b/yazi-fm/Cargo.toml
index 4dba97a9..360e1e7b 100644
--- a/yazi-fm/Cargo.toml
+++ b/yazi-fm/Cargo.toml
@@ -24,6 +24,7 @@ yazi-macro = { path = "../yazi-macro", version = "25.3.7" }
yazi-plugin = { path = "../yazi-plugin", version = "25.3.7" }
yazi-proxy = { path = "../yazi-proxy", version = "25.3.7" }
yazi-shared = { path = "../yazi-shared", version = "25.3.7" }
+yazi-widgets = { path = "../yazi-widgets", version = "25.3.7" }
# External dependencies
anyhow = { workspace = true }
@@ -35,7 +36,6 @@ indexmap = { workspace = true }
mlua = { workspace = true }
ratatui = { workspace = true }
scopeguard = { workspace = true }
-syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { workspace = true }
tokio-stream = { workspace = true }
diff --git a/yazi-fm/src/app/app.rs b/yazi-fm/src/app/app.rs
index 5d0253ea..2a16e14c 100644
--- a/yazi-fm/src/app/app.rs
+++ b/yazi-fm/src/app/app.rs
@@ -3,9 +3,9 @@ use std::sync::atomic::Ordering;
use anyhow::Result;
use crossterm::event::KeyEvent;
use yazi_config::keymap::Key;
-use yazi_core::input::InputMode;
use yazi_macro::emit;
use yazi_shared::event::{CmdCow, Event, NEED_RENDER};
+use yazi_widgets::input::InputMode;
use crate::{Ctx, Executor, Router, Signals, Term, lives::Lives};
diff --git a/yazi-fm/src/app/commands/render.rs b/yazi-fm/src/app/commands/render.rs
index 97a80c69..a7e2fadd 100644
--- a/yazi-fm/src/app/commands/render.rs
+++ b/yazi-fm/src/app/commands/render.rs
@@ -1,8 +1,7 @@
-use std::sync::atomic::Ordering;
+use std::sync::atomic::{AtomicU8, Ordering};
-use crossterm::{execute, queue, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}};
-use ratatui::{CompletedFrame, backend::{Backend, CrosstermBackend}, buffer::Buffer};
-use scopeguard::defer;
+use crossterm::{cursor::{MoveTo, SetCursorStyle, Show}, execute, queue, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}};
+use ratatui::{CompletedFrame, backend::{Backend, CrosstermBackend}, buffer::Buffer, layout::Position};
use yazi_plugin::elements::COLLISION;
use yazi_shared::{event::NEED_RENDER, tty::TTY};
@@ -13,22 +12,18 @@ impl App {
NEED_RENDER.store(false, Ordering::Relaxed);
let Some(term) = &mut self.term else { return };
- queue!(TTY.writer(), BeginSynchronizedUpdate).ok();
- defer! { execute!(TTY.writer(), EndSynchronizedUpdate).ok(); }
+ Self::routine(true, None);
+ let _guard = scopeguard::guard(self.cx.cursor(), |c| Self::routine(false, c));
let collision = COLLISION.swap(false, Ordering::Relaxed);
let frame = term
.draw(|f| {
_ = Lives::scope(&self.cx, || Ok(f.render_widget(Root::new(&self.cx), f.area())));
-
- if let Some(pos) = self.cx.cursor() {
- f.set_cursor_position(pos);
- }
})
.unwrap();
if COLLISION.load(Ordering::Relaxed) {
- Self::patch(frame, self.cx.cursor());
+ Self::patch(frame);
}
if !self.cx.notify.messages.is_empty() {
self.render_partially();
@@ -46,6 +41,9 @@ impl App {
return self.render();
}
+ Self::routine(true, None);
+ let _guard = scopeguard::guard(self.cx.cursor(), |c| Self::routine(false, c));
+
let frame = term
.draw_partial(|f| {
_ = Lives::scope(&self.cx, || {
@@ -53,20 +51,16 @@ impl App {
f.render_widget(crate::notify::Notify::new(&self.cx), f.area());
Ok(())
});
-
- if let Some(pos) = self.cx.cursor() {
- f.set_cursor_position(pos);
- }
})
.unwrap();
if COLLISION.load(Ordering::Relaxed) {
- Self::patch(frame, self.cx.cursor());
+ Self::patch(frame);
}
}
#[inline]
- fn patch(frame: CompletedFrame, cursor: Option<(u16, u16)>) {
+ fn patch(frame: CompletedFrame) {
let mut new = Buffer::empty(frame.area);
for y in new.area.top()..new.area.bottom() {
for x in new.area.left()..new.area.right() {
@@ -79,14 +73,23 @@ impl App {
}
let patches = frame.buffer.diff(&new);
- let stdout = &mut *TTY.lockout();
+ CrosstermBackend::new(&mut *TTY.lockout()).draw(patches.into_iter()).ok();
+ }
- let mut backend = CrosstermBackend::new(stdout);
- backend.draw(patches.into_iter()).ok();
- if let Some(pos) = cursor {
- backend.show_cursor().ok();
- backend.set_cursor_position(pos).ok();
+ fn routine(push: bool, cursor: Option<(Position, SetCursorStyle)>) {
+ 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;
}
- backend.flush().ok();
+
+ _ = if push {
+ queue!(TTY.writer(), BeginSynchronizedUpdate)
+ } else if let Some((Position { x, y }, shape)) = cursor {
+ execute!(TTY.writer(), shape, MoveTo(x, y), Show, EndSynchronizedUpdate)
+ } else {
+ execute!(TTY.writer(), EndSynchronizedUpdate)
+ };
}
}
diff --git a/yazi-fm/src/context.rs b/yazi-fm/src/context.rs
index f8d5b1b7..cc1f012b 100644
--- a/yazi-fm/src/context.rs
+++ b/yazi-fm/src/context.rs
@@ -1,4 +1,5 @@
-use ratatui::layout::Rect;
+use crossterm::cursor::SetCursorStyle;
+use ratatui::layout::{Position, Rect};
use yazi_core::{cmp::Cmp, confirm::Confirm, help::Help, input::Input, mgr::Mgr, notify::Notify, pick::Pick, tab::{Folder, Tab}, tasks::Tasks, which::Which};
use yazi_shared::Layer;
@@ -30,13 +31,16 @@ impl Ctx {
}
#[inline]
- pub fn cursor(&self) -> Option<(u16, u16)> {
+ pub fn cursor(&self) -> Option<(Position, SetCursorStyle)> {
if self.input.visible {
let Rect { x, y, .. } = self.mgr.area(self.input.position);
- return Some((x + 1 + self.input.cursor(), y + 1));
+ return Some((
+ Position { x: x + 1 + self.input.cursor(), y: y + 1 },
+ self.input.cursor_shape(),
+ ));
}
if let Some((x, y)) = self.help.cursor() {
- return Some((x, y));
+ return Some((Position { x, y }, self.help.cursor_shape()));
}
None
}
diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs
index a5a21700..282c1ad6 100644
--- a/yazi-fm/src/executor.rs
+++ b/yazi-fm/src/executor.rs
@@ -1,5 +1,5 @@
-use yazi_core::input::InputMode;
use yazi_shared::{Layer, event::CmdCow};
+use yazi_widgets::input::InputMode;
use crate::app::App;
@@ -233,57 +233,30 @@ impl<'a> Executor<'a> {
return self.app.cx.input.$name(cmd);
}
};
- ($name:ident, $alias:literal) => {
- if cmd.name == $alias {
- return self.app.cx.input.$name(cmd);
- }
- };
}
+ on!(escape);
on!(show);
on!(close);
- on!(escape);
- on!(move_, "move");
- on!(backward);
- on!(forward);
-
- if cmd.name.as_str() == "complete" {
- return if cmd.bool("trigger") {
- self.app.cx.cmp.trigger(cmd)
- } else {
- self.app.cx.input.complete(cmd)
- };
- }
match self.app.cx.input.mode() {
InputMode::Normal => {
- on!(insert);
- on!(visual);
- on!(replace);
-
- on!(delete);
- on!(yank);
- on!(paste);
-
- on!(undo);
- on!(redo);
-
match cmd.name.as_str() {
// Help
- "help" => self.app.cx.help.toggle(Layer::Input),
+ "help" => return self.app.cx.help.toggle(Layer::Input),
// Plugin
- "plugin" => self.app.plugin(cmd),
+ "plugin" => return self.app.plugin(cmd),
_ => {}
}
}
- InputMode::Insert => {
- on!(visual);
-
- on!(backspace);
- on!(kill);
- }
+ InputMode::Insert => match cmd.name.as_str() {
+ "complete" if cmd.bool("trigger") => return self.app.cx.cmp.trigger(cmd),
+ _ => {}
+ },
InputMode::Replace => {}
- }
+ };
+
+ self.app.cx.input.execute(cmd)
}
fn confirm(&mut self, cmd: CmdCow) {
diff --git a/yazi-fm/src/input/input.rs b/yazi-fm/src/input/input.rs
index cb27e8f3..4c85dcb4 100644
--- a/yazi-fm/src/input/input.rs
+++ b/yazi-fm/src/input/input.rs
@@ -1,13 +1,7 @@
-use std::ops::Range;
+use ratatui::{buffer::Buffer, layout::{Margin, Rect}, text::Line, widgets::{Block, BorderType, Widget}};
+use yazi_config::THEME;
-use anyhow::{Result, bail};
-use ratatui::{buffer::Buffer, layout::Rect, text::Line, widgets::{Block, BorderType, Paragraph, Widget}};
-use syntect::easy::HighlightLines;
-use yazi_config::{PREVIEW, THEME};
-use yazi_core::input::InputMode;
-use yazi_plugin::external::Highlighter;
-
-use crate::{Ctx, Term};
+use crate::Ctx;
pub(crate) struct Input<'a> {
cx: &'a Ctx,
@@ -15,52 +9,21 @@ pub(crate) struct Input<'a> {
impl<'a> Input<'a> {
pub(crate) fn new(cx: &'a Ctx) -> Self { Self { cx } }
-
- fn highlighted_value(&self) -> Result> {
- if !self.cx.input.highlight {
- bail!("Highlighting is disabled");
- }
-
- let (theme, syntaxes) = Highlighter::init();
- if let Some(syntax) = syntaxes.find_syntax_by_name("Bourne Again Shell (bash)") {
- let mut h = HighlightLines::new(syntax, theme);
- let regions = h.highlight_line(self.cx.input.value(), syntaxes)?;
- return Ok(Highlighter::to_line_widget(regions, &PREVIEW.indent()));
- }
- bail!("Failed to find syntax")
- }
}
impl Widget for Input<'_> {
- fn render(self, win: Rect, buf: &mut Buffer) {
+ fn render(self, _: Rect, buf: &mut Buffer) {
let input = &self.cx.input;
let area = self.cx.mgr.area(input.position);
yazi_plugin::elements::Clear::default().render(area, buf);
- Paragraph::new(self.highlighted_value().unwrap_or_else(|_| Line::from(input.value())))
- .block(
- Block::bordered()
- .border_type(BorderType::Rounded)
- .border_style(THEME.input.border)
- .title(Line::styled(&input.title, THEME.input.title)),
- )
- .style(THEME.input.value)
+
+ Block::bordered()
+ .border_type(BorderType::Rounded)
+ .border_style(THEME.input.border)
+ .title(Line::styled(&input.title, THEME.input.title))
.render(area, buf);
- if let Some(Range { start, end }) = input.selected() {
- let x = win.width.min(area.x + 1 + start);
- let y = win.height.min(area.y + 1);
-
- buf.set_style(
- Rect { x, y, width: (end - start).min(win.width - x), height: 1.min(win.height - y) },
- THEME.input.selected,
- )
- }
-
- _ = match input.mode() {
- InputMode::Insert => Term::set_cursor_bar(),
- InputMode::Replace => Term::set_cursor_underscore(),
- _ => Term::set_cursor_block(),
- };
+ input.render(area.inner(Margin::new(1, 1)), buf);
}
}
diff --git a/yazi-fm/src/term.rs b/yazi-fm/src/term.rs
index 182ee1af..e0d22bf2 100644
--- a/yazi-fm/src/term.rs
+++ b/yazi-fm/src/term.rs
@@ -1,11 +1,11 @@
use std::{io, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, AtomicU8, Ordering}};
use anyhow::Result;
-use crossterm::{Command, event::{DisableBracketedPaste, EnableBracketedPaste, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode}};
+use crossterm::{Command, event::{DisableBracketedPaste, EnableBracketedPaste, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, style::Print, terminal::{LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode}};
use cursor::RestoreCursor;
use ratatui::{CompletedFrame, Frame, Terminal, backend::CrosstermBackend, buffer::Buffer, layout::Rect};
use yazi_adapter::{Emulator, Mux};
-use yazi_config::{INPUT, MGR};
+use yazi_config::MGR;
use yazi_shared::{SyncCell, tty::{TTY, TtyWriter}};
static CSI_U: AtomicBool = AtomicBool::new(false);
@@ -141,36 +141,6 @@ impl Term {
pub(super) fn can_partial(&mut self) -> bool {
self.inner.autoresize().is_ok() && self.last_area == self.inner.get_frame().area()
}
-
- #[inline]
- pub(super) fn set_cursor_block() -> Result<()> {
- use crossterm::cursor::SetCursorStyle;
- Ok(if INPUT.cursor_blink {
- queue!(TTY.writer(), SetCursorStyle::BlinkingBlock)?
- } else {
- queue!(TTY.writer(), SetCursorStyle::SteadyBlock)?
- })
- }
-
- #[inline]
- pub(super) fn set_cursor_bar() -> Result<()> {
- use crossterm::cursor::SetCursorStyle;
- Ok(if INPUT.cursor_blink {
- queue!(TTY.writer(), SetCursorStyle::BlinkingBar)?
- } else {
- queue!(TTY.writer(), SetCursorStyle::SteadyBar)?
- })
- }
-
- #[inline]
- pub(super) fn set_cursor_underscore() -> Result<()> {
- use crossterm::cursor::SetCursorStyle;
- Ok(if INPUT.cursor_blink {
- queue!(TTY.writer(), SetCursorStyle::BlinkingUnderScore)?
- } else {
- queue!(TTY.writer(), SetCursorStyle::SteadyUnderScore)?
- })
- }
}
impl Drop for Term {
diff --git a/yazi-plugin/Cargo.toml b/yazi-plugin/Cargo.toml
index 135d97f2..25eee456 100644
--- a/yazi-plugin/Cargo.toml
+++ b/yazi-plugin/Cargo.toml
@@ -34,7 +34,7 @@ mlua = { workspace = true }
parking_lot = { workspace = true }
ratatui = { workspace = true }
serde_json = { workspace = true }
-syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
+syntect = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
tokio-util = { workspace = true }
diff --git a/yazi-proxy/src/cmp.rs b/yazi-proxy/src/cmp.rs
index cb02e11c..f33a9b53 100644
--- a/yazi-proxy/src/cmp.rs
+++ b/yazi-proxy/src/cmp.rs
@@ -1,5 +1,5 @@
use yazi_macro::emit;
-use yazi_shared::event::Cmd;
+use yazi_shared::{Id, event::Cmd};
pub struct CmpProxy;
@@ -10,7 +10,7 @@ impl CmpProxy {
}
#[inline]
- pub fn trigger(word: &str, ticket: usize) {
+ pub fn trigger(word: &str, ticket: Id) {
emit!(Call(Cmd::args("cmp:trigger", &[word]).with("ticket", ticket)));
}
}
diff --git a/yazi-shared/src/errors/input.rs b/yazi-shared/src/errors/input.rs
index 9c0c1c6f..93b53fe0 100644
--- a/yazi-shared/src/errors/input.rs
+++ b/yazi-shared/src/errors/input.rs
@@ -1,9 +1,11 @@
use std::{error::Error, fmt::{self, Display}};
+use crate::Id;
+
#[derive(Debug)]
pub enum InputError {
Typed(String),
- Completed(String, usize),
+ Completed(String, Id),
Canceled(String),
}
diff --git a/yazi-widgets/Cargo.toml b/yazi-widgets/Cargo.toml
new file mode 100644
index 00000000..806ad867
--- /dev/null
+++ b/yazi-widgets/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "yazi-widgets"
+version = "25.3.7"
+edition = "2021"
+license = "MIT"
+authors = [ "sxyazi " ]
+description = "Yazi user interface widgets"
+homepage = "https://yazi-rs.github.io"
+repository = "https://github.com/sxyazi/yazi"
+rust-version = "1.83.0"
+
+[dependencies]
+yazi-codegen = { path = "../yazi-codegen", version = "25.3.7" }
+yazi-config = { path = "../yazi-config", version = "25.3.7" }
+yazi-macro = { path = "../yazi-macro", version = "25.3.7" }
+yazi-plugin = { path = "../yazi-plugin", version = "25.3.7" }
+yazi-shared = { path = "../yazi-shared", version = "25.3.7" }
+
+# External dependencies
+futures = { workspace = true }
+ratatui = { workspace = true }
+unicode-width = { workspace = true }
diff --git a/yazi-core/src/input/commands/backspace.rs b/yazi-widgets/src/input/commands/backspace.rs
similarity index 100%
rename from yazi-core/src/input/commands/backspace.rs
rename to yazi-widgets/src/input/commands/backspace.rs
diff --git a/yazi-core/src/input/commands/backward.rs b/yazi-widgets/src/input/commands/backward.rs
similarity index 100%
rename from yazi-core/src/input/commands/backward.rs
rename to yazi-widgets/src/input/commands/backward.rs
diff --git a/yazi-widgets/src/input/commands/commands.rs b/yazi-widgets/src/input/commands/commands.rs
new file mode 100644
index 00000000..7ab08e12
--- /dev/null
+++ b/yazi-widgets/src/input/commands/commands.rs
@@ -0,0 +1,48 @@
+use yazi_shared::event::CmdCow;
+
+use crate::input::{Input, InputMode};
+
+impl Input {
+ pub fn execute(&mut self, cmd: CmdCow) {
+ macro_rules! on {
+ ($name:ident) => {
+ if cmd.name == stringify!($name) {
+ return self.$name(cmd);
+ }
+ };
+ ($name:ident, $alias:literal) => {
+ if cmd.name == $alias {
+ return self.$name(cmd);
+ }
+ };
+ }
+
+ on!(move_, "move");
+ on!(backward);
+ on!(forward);
+
+ match self.mode() {
+ InputMode::Normal => {
+ on!(insert);
+ on!(visual);
+ on!(replace);
+
+ on!(delete);
+ on!(yank);
+ on!(paste);
+
+ on!(undo);
+ on!(redo);
+ }
+ InputMode::Insert => {
+ on!(visual);
+
+ on!(backspace);
+ on!(kill);
+
+ on!(complete);
+ }
+ InputMode::Replace => {}
+ }
+ }
+}
diff --git a/yazi-core/src/input/commands/complete.rs b/yazi-widgets/src/input/commands/complete.rs
similarity index 78%
rename from yazi-core/src/input/commands/complete.rs
rename to yazi-widgets/src/input/commands/complete.rs
index 065557ab..241a3e03 100644
--- a/yazi-core/src/input/commands/complete.rs
+++ b/yazi-widgets/src/input/commands/complete.rs
@@ -12,15 +12,15 @@ const SEPARATOR: [char; 2] = ['/', '\\'];
const SEPARATOR: char = std::path::MAIN_SEPARATOR;
struct Opt {
- word: Cow<'static, str>,
- ticket: usize,
+ word: Cow<'static, str>,
+ _ticket: usize, // FIXME: not used
}
impl From for Opt {
fn from(mut c: CmdCow) -> Self {
Self {
- word: c.take_first_str().unwrap_or_default(),
- ticket: c.get("ticket").and_then(Data::as_usize).unwrap_or(0),
+ word: c.take_first_str().unwrap_or_default(),
+ _ticket: c.get("ticket").and_then(Data::as_usize).unwrap_or(0),
}
}
}
@@ -28,11 +28,7 @@ impl From for Opt {
impl Input {
#[yazi_codegen::command]
pub fn complete(&mut self, opt: Opt) {
- if self.ticket != opt.ticket {
- return;
- }
-
- let [before, after] = self.partition();
+ let (before, after) = self.partition();
let new = if let Some((prefix, _)) = before.rsplit_once(SEPARATOR) {
format!("{prefix}/{}{after}", opt.word).replace(SEPARATOR, MAIN_SEPARATOR_STR)
} else {
diff --git a/yazi-core/src/input/commands/delete.rs b/yazi-widgets/src/input/commands/delete.rs
similarity index 100%
rename from yazi-core/src/input/commands/delete.rs
rename to yazi-widgets/src/input/commands/delete.rs
diff --git a/yazi-widgets/src/input/commands/escape.rs b/yazi-widgets/src/input/commands/escape.rs
new file mode 100644
index 00000000..9074b8b1
--- /dev/null
+++ b/yazi-widgets/src/input/commands/escape.rs
@@ -0,0 +1,27 @@
+use crate::input::{Input, InputMode, op::InputOp};
+
+struct Opt;
+
+impl From<()> for Opt {
+ fn from(_: ()) -> Self { Self }
+}
+
+impl Input {
+ #[yazi_codegen::command]
+ pub fn escape(&mut self, _: Opt) {
+ let snap = self.snap_mut();
+ match snap.mode {
+ InputMode::Normal => {
+ snap.op = InputOp::None;
+ }
+ InputMode::Insert => {
+ snap.mode = InputMode::Normal;
+ self.move_(-1);
+ }
+ InputMode::Replace => {
+ snap.mode = InputMode::Normal;
+ }
+ }
+ self.snaps.tag(self.limit);
+ }
+}
diff --git a/yazi-core/src/input/commands/forward.rs b/yazi-widgets/src/input/commands/forward.rs
similarity index 100%
rename from yazi-core/src/input/commands/forward.rs
rename to yazi-widgets/src/input/commands/forward.rs
diff --git a/yazi-core/src/input/commands/insert.rs b/yazi-widgets/src/input/commands/insert.rs
similarity index 100%
rename from yazi-core/src/input/commands/insert.rs
rename to yazi-widgets/src/input/commands/insert.rs
diff --git a/yazi-core/src/input/commands/kill.rs b/yazi-widgets/src/input/commands/kill.rs
similarity index 100%
rename from yazi-core/src/input/commands/kill.rs
rename to yazi-widgets/src/input/commands/kill.rs
diff --git a/yazi-widgets/src/input/commands/mod.rs b/yazi-widgets/src/input/commands/mod.rs
new file mode 100644
index 00000000..3fb65236
--- /dev/null
+++ b/yazi-widgets/src/input/commands/mod.rs
@@ -0,0 +1 @@
+yazi_macro::mod_flat!(backspace backward commands complete delete escape forward insert kill move_ paste redo replace type_ undo visual yank);
diff --git a/yazi-core/src/input/commands/move_.rs b/yazi-widgets/src/input/commands/move_.rs
similarity index 97%
rename from yazi-core/src/input/commands/move_.rs
rename to yazi-widgets/src/input/commands/move_.rs
index 9edc5384..64ff0db2 100644
--- a/yazi-core/src/input/commands/move_.rs
+++ b/yazi-widgets/src/input/commands/move_.rs
@@ -33,7 +33,7 @@ impl Input {
render!(self.handle_op(opt.step.cursor(snap), false));
- let (limit, snap) = (self.limit(), self.snap_mut());
+ let (limit, snap) = (self.limit, self.snap_mut());
if snap.offset > snap.cursor {
snap.offset = snap.cursor;
} else if snap.value.is_empty() {
diff --git a/yazi-core/src/input/commands/paste.rs b/yazi-widgets/src/input/commands/paste.rs
similarity index 100%
rename from yazi-core/src/input/commands/paste.rs
rename to yazi-widgets/src/input/commands/paste.rs
diff --git a/yazi-core/src/input/commands/redo.rs b/yazi-widgets/src/input/commands/redo.rs
similarity index 100%
rename from yazi-core/src/input/commands/redo.rs
rename to yazi-widgets/src/input/commands/redo.rs
diff --git a/yazi-core/src/input/commands/replace.rs b/yazi-widgets/src/input/commands/replace.rs
similarity index 92%
rename from yazi-core/src/input/commands/replace.rs
rename to yazi-widgets/src/input/commands/replace.rs
index f028ebb2..ab7046f3 100644
--- a/yazi-core/src/input/commands/replace.rs
+++ b/yazi-widgets/src/input/commands/replace.rs
@@ -27,6 +27,6 @@ impl Input {
}
render!();
- self.snaps.tag(self.limit()).then(|| self.flush_value());
+ self.snaps.tag(self.limit).then(|| self.flush_value());
}
}
diff --git a/yazi-core/src/input/commands/type_.rs b/yazi-widgets/src/input/commands/type_.rs
similarity index 100%
rename from yazi-core/src/input/commands/type_.rs
rename to yazi-widgets/src/input/commands/type_.rs
diff --git a/yazi-core/src/input/commands/undo.rs b/yazi-widgets/src/input/commands/undo.rs
similarity index 100%
rename from yazi-core/src/input/commands/undo.rs
rename to yazi-widgets/src/input/commands/undo.rs
diff --git a/yazi-core/src/input/commands/visual.rs b/yazi-widgets/src/input/commands/visual.rs
similarity index 100%
rename from yazi-core/src/input/commands/visual.rs
rename to yazi-widgets/src/input/commands/visual.rs
diff --git a/yazi-core/src/input/commands/yank.rs b/yazi-widgets/src/input/commands/yank.rs
similarity index 100%
rename from yazi-core/src/input/commands/yank.rs
rename to yazi-widgets/src/input/commands/yank.rs
diff --git a/yazi-widgets/src/input/input.rs b/yazi-widgets/src/input/input.rs
new file mode 100644
index 00000000..0fb0834b
--- /dev/null
+++ b/yazi-widgets/src/input/input.rs
@@ -0,0 +1,129 @@
+use std::ops::Range;
+
+use ratatui::crossterm::cursor::SetCursorStyle;
+use unicode_width::UnicodeWidthStr;
+use yazi_config::INPUT;
+use yazi_plugin::CLIPBOARD;
+
+use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp};
+
+pub type InputCallback = Box;
+
+#[derive(Default)]
+pub struct Input {
+ pub snaps: InputSnaps,
+ pub limit: usize,
+ pub callback: Option,
+}
+
+impl Input {
+ pub fn new(value: String, limit: usize, callback: InputCallback) -> Self {
+ Self { snaps: InputSnaps::new(value, limit), limit, callback: Some(callback) }
+ }
+
+ pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
+ let old = self.snap().clone();
+ let snap = self.snap_mut();
+
+ match snap.op {
+ InputOp::None | InputOp::Select(_) => {
+ snap.cursor = cursor;
+ }
+ InputOp::Delete(cut, insert, _) => {
+ let range = snap.op.range(cursor, include).unwrap();
+ let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
+
+ let drain = snap.value.drain(start.unwrap()..end.unwrap()).collect::();
+ if cut {
+ futures::executor::block_on(CLIPBOARD.set(&drain));
+ }
+
+ snap.op = InputOp::None;
+ snap.mode = if insert { InputMode::Insert } else { InputMode::Normal };
+ snap.cursor = range.start;
+ }
+ InputOp::Yank(_) => {
+ let range = snap.op.range(cursor, include).unwrap();
+ let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
+ let yanked = &snap.value[start.unwrap()..end.unwrap()];
+
+ snap.op = InputOp::None;
+ futures::executor::block_on(CLIPBOARD.set(yanked));
+ }
+ };
+
+ snap.cursor = snap.count().saturating_sub(snap.mode.delta()).min(snap.cursor);
+ if snap == &old {
+ return false;
+ }
+ if !matches!(old.op, InputOp::None | InputOp::Select(_)) {
+ self.snaps.tag(self.limit).then(|| self.flush_value());
+ }
+ true
+ }
+
+ pub(super) fn flush_value(&mut self) {
+ if let Some(cb) = &self.callback {
+ let (before, after) = self.partition();
+ cb(before, after);
+ }
+ }
+}
+
+impl Input {
+ #[inline]
+ pub fn value(&self) -> &str { &self.snap().value }
+
+ #[inline]
+ pub fn visible_value(&self) -> &str { self.snap().slice(self.snap().window(self.limit)) }
+
+ #[inline]
+ pub fn mode(&self) -> InputMode { self.snap().mode }
+
+ #[inline]
+ pub fn cursor(&self) -> u16 {
+ let snap = self.snap();
+ snap.slice(snap.offset..snap.cursor).width() as u16
+ }
+
+ pub fn cursor_shape(&self) -> SetCursorStyle {
+ use InputMode as M;
+
+ match self.mode() {
+ M::Normal if INPUT.cursor_blink => SetCursorStyle::BlinkingBlock,
+ M::Normal if !INPUT.cursor_blink => SetCursorStyle::SteadyBlock,
+ M::Insert if INPUT.cursor_blink => SetCursorStyle::BlinkingBar,
+ M::Insert if !INPUT.cursor_blink => SetCursorStyle::SteadyBar,
+ M::Replace if INPUT.cursor_blink => SetCursorStyle::BlinkingUnderScore,
+ M::Replace if !INPUT.cursor_blink => SetCursorStyle::SteadyUnderScore,
+ M::Normal | M::Insert | M::Replace => unreachable!(),
+ }
+ }
+
+ pub fn selected(&self) -> Option> {
+ let snap = self.snap();
+ let start = snap.op.start()?;
+
+ let (start, end) =
+ if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) };
+
+ let win = snap.window(self.limit);
+ let Range { start, end } = start.max(win.start)..end.min(win.end);
+
+ let s = snap.slice(snap.offset..start).width() as u16;
+ Some(s..s + snap.slice(start..end).width() as u16)
+ }
+
+ #[inline]
+ pub fn partition(&self) -> (&str, &str) {
+ let snap = self.snap();
+ let idx = snap.idx(snap.cursor).unwrap();
+ (&snap.value[..idx], &snap.value[idx..])
+ }
+
+ #[inline]
+ pub fn snap(&self) -> &InputSnap { self.snaps.current() }
+
+ #[inline]
+ pub fn snap_mut(&mut self) -> &mut InputSnap { self.snaps.current_mut() }
+}
diff --git a/yazi-widgets/src/input/mod.rs b/yazi-widgets/src/input/mod.rs
new file mode 100644
index 00000000..1e6e60b0
--- /dev/null
+++ b/yazi-widgets/src/input/mod.rs
@@ -0,0 +1,3 @@
+yazi_macro::mod_pub!(commands);
+
+yazi_macro::mod_flat!(input mode op snap snaps widget);
diff --git a/yazi-core/src/input/mode.rs b/yazi-widgets/src/input/mode.rs
similarity index 100%
rename from yazi-core/src/input/mode.rs
rename to yazi-widgets/src/input/mode.rs
diff --git a/yazi-core/src/input/op.rs b/yazi-widgets/src/input/op.rs
similarity index 100%
rename from yazi-core/src/input/op.rs
rename to yazi-widgets/src/input/op.rs
diff --git a/yazi-core/src/input/snap.rs b/yazi-widgets/src/input/snap.rs
similarity index 90%
rename from yazi-core/src/input/snap.rs
rename to yazi-widgets/src/input/snap.rs
index e2931220..6b0d09e2 100644
--- a/yazi-core/src/input/snap.rs
+++ b/yazi-widgets/src/input/snap.rs
@@ -5,14 +5,14 @@ use unicode_width::UnicodeWidthChar;
use super::{InputMode, InputOp};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub(super) struct InputSnap {
- pub(super) value: String,
+pub struct InputSnap {
+ pub value: String,
- pub(super) op: InputOp,
+ pub op: InputOp,
- pub(super) mode: InputMode,
- pub(super) offset: usize,
- pub(super) cursor: usize,
+ pub mode: InputMode,
+ pub offset: usize,
+ pub cursor: usize,
}
impl InputSnap {
@@ -22,7 +22,7 @@ impl InputSnap {
op: Default::default(),
- mode: Default::default(),
+ mode: Default::default(),
offset: usize::MAX,
cursor: usize::MAX,
};
diff --git a/yazi-core/src/input/snaps.rs b/yazi-widgets/src/input/snaps.rs
similarity index 74%
rename from yazi-core/src/input/snaps.rs
rename to yazi-widgets/src/input/snaps.rs
index b1960bcf..c0d6b1de 100644
--- a/yazi-core/src/input/snaps.rs
+++ b/yazi-widgets/src/input/snaps.rs
@@ -2,20 +2,27 @@ use std::mem;
use super::InputSnap;
-#[derive(Default, PartialEq, Eq)]
-pub(super) struct InputSnaps {
+#[derive(PartialEq, Eq)]
+pub struct InputSnaps {
idx: usize,
versions: Vec,
current: InputSnap,
}
+impl Default for InputSnaps {
+ fn default() -> Self {
+ Self {
+ idx: 0,
+ versions: vec![InputSnap::new(String::new(), 0)],
+ current: InputSnap::new(String::new(), 0),
+ }
+ }
+}
+
impl InputSnaps {
- #[inline]
- pub(super) fn reset(&mut self, value: String, limit: usize) {
- self.idx = 0;
- self.versions.clear();
- self.versions.push(InputSnap::new(value, limit));
- self.current = self.versions[0].clone();
+ pub fn new(value: String, limit: usize) -> Self {
+ let current = InputSnap::new(value, limit);
+ Self { idx: 0, versions: vec![current.clone()], current }
}
pub(super) fn tag(&mut self, limit: usize) -> bool {
@@ -65,7 +72,7 @@ impl InputSnaps {
impl InputSnaps {
#[inline]
- pub(super) fn current(&self) -> &InputSnap { &self.current }
+ pub fn current(&self) -> &InputSnap { &self.current }
#[inline]
pub(super) fn current_mut(&mut self) -> &mut InputSnap { &mut self.current }
diff --git a/yazi-widgets/src/input/widget.rs b/yazi-widgets/src/input/widget.rs
new file mode 100644
index 00000000..10e5ba91
--- /dev/null
+++ b/yazi-widgets/src/input/widget.rs
@@ -0,0 +1,30 @@
+use std::ops::Range;
+
+use ratatui::{layout::Rect, text::Line, widgets::Widget};
+use yazi_config::THEME;
+
+use super::Input;
+
+impl Widget for &Input {
+ fn render(self, area: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer)
+ where
+ Self: Sized,
+ {
+ yazi_plugin::elements::Clear::default().render(area, buf);
+
+ Line::styled(self.visible_value(), THEME.input.value).render(area, buf);
+
+ if let Some(Range { start, end }) = self.selected() {
+ let s = start.min(area.width);
+ buf.set_style(
+ Rect {
+ x: area.x + s,
+ y: area.y,
+ width: (end - start).min(area.width - s),
+ height: area.height.min(1),
+ },
+ THEME.input.selected,
+ )
+ }
+ }
+}
diff --git a/yazi-widgets/src/lib.rs b/yazi-widgets/src/lib.rs
new file mode 100644
index 00000000..6ce2b0ad
--- /dev/null
+++ b/yazi-widgets/src/lib.rs
@@ -0,0 +1 @@
+yazi_macro::mod_pub!(input);