diff --git a/yazi-config/preset/keymap-default.toml b/yazi-config/preset/keymap-default.toml index bb4b6a47..7d827dba 100644 --- a/yazi-config/preset/keymap-default.toml +++ b/yazi-config/preset/keymap-default.toml @@ -238,6 +238,7 @@ keymap = [ { on = "A", run = [ "move 999", "insert --append" ], desc = "Move to the EOL, and enter append mode" }, { on = "v", run = "visual", desc = "Enter visual mode" }, { on = "V", run = [ "move -999", "visual", "move 999" ], desc = "Enter visual mode and select all" }, + { on = "r", run = "replace", desc = "Enter replace mode" }, # Character-wise movement { on = "h", run = "move -1", desc = "Move back a character" }, diff --git a/yazi-core/src/input/commands/escape.rs b/yazi-core/src/input/commands/escape.rs index 6c8cbadb..72bb062d 100644 --- a/yazi-core/src/input/commands/escape.rs +++ b/yazi-core/src/input/commands/escape.rs @@ -32,6 +32,9 @@ impl Input { CompletionProxy::close(); } } + InputMode::Replace => { + snap.mode = InputMode::Normal; + } } self.snaps.tag(self.limit()); diff --git a/yazi-core/src/input/commands/mod.rs b/yazi-core/src/input/commands/mod.rs index 6648217c..fbbaafc4 100644 --- a/yazi-core/src/input/commands/mod.rs +++ b/yazi-core/src/input/commands/mod.rs @@ -1,4 +1,4 @@ yazi_macro::mod_flat!( backspace backward close complete delete escape forward insert kill move_ paste redo - show type_ undo visual yank + replace show type_ undo visual yank ); diff --git a/yazi-core/src/input/commands/replace.rs b/yazi-core/src/input/commands/replace.rs new file mode 100644 index 00000000..5a5782d8 --- /dev/null +++ b/yazi-core/src/input/commands/replace.rs @@ -0,0 +1,20 @@ +use yazi_macro::render; +use yazi_shared::event::CmdCow; + +use crate::input::{Input, InputMode, op::InputOp}; + +impl Input { + #[yazi_codegen::command] + pub fn replace(&mut self, _: CmdCow) { + let snap = self.snap_mut(); + if snap.mode == InputMode::Normal { + snap.op = InputOp::None; + snap.mode = InputMode::Replace; + } else { + return; + } + + render!(); + } +} + diff --git a/yazi-core/src/input/commands/type_.rs b/yazi-core/src/input/commands/type_.rs index 72488998..4692f545 100644 --- a/yazi-core/src/input/commands/type_.rs +++ b/yazi-core/src/input/commands/type_.rs @@ -11,6 +11,18 @@ impl From for Opt { impl Input { pub fn type_(&mut self, key: &Key) -> bool { + if self.mode() == InputMode::Replace { + let Some(c) = key.plain() else { + return false; + }; + + let snap = self.snaps.current_mut(); + snap.mode = InputMode::Normal; + self.replace_str(c.encode_utf8(&mut [0; 4])); + + return true; + } + if self.mode() != InputMode::Insert { return false; } diff --git a/yazi-core/src/input/input.rs b/yazi-core/src/input/input.rs index 59486442..d34b6ca9 100644 --- a/yazi-core/src/input/input.rs +++ b/yazi-core/src/input/input.rs @@ -46,6 +46,17 @@ impl Input { render!(); } + pub fn replace_str(&mut self, s: &str) { + let snap = self.snaps.current_mut(); + + let start = snap.idx(snap.cursor).unwrap(); + let end = snap.idx(snap.cursor + 1).unwrap(); + snap.value.replace_range(start..end, s); + + self.flush_value(); + render!(); + } + pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool { let old = self.snap().clone(); let snap = self.snaps.current_mut(); diff --git a/yazi-core/src/input/mode.rs b/yazi-core/src/input/mode.rs index 4668143b..67fa7458 100644 --- a/yazi-core/src/input/mode.rs +++ b/yazi-core/src/input/mode.rs @@ -3,6 +3,7 @@ pub enum InputMode { Normal, #[default] Insert, + Replace, } impl InputMode { diff --git a/yazi-fm/src/app/app.rs b/yazi-fm/src/app/app.rs index 1d08e5aa..64a15cfd 100644 --- a/yazi-fm/src/app/app.rs +++ b/yazi-fm/src/app/app.rs @@ -93,6 +93,9 @@ impl App { if input.mode() == InputMode::Insert { input.type_str(&str); } + if input.mode() == InputMode::Replace { + input.replace_str(&str); + } } } } diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 41a34f6b..769a4409 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -259,6 +259,7 @@ impl<'a> Executor<'a> { InputMode::Normal => { on!(insert); on!(visual); + on!(replace); on!(delete); on!(yank); @@ -279,6 +280,7 @@ impl<'a> Executor<'a> { on!(backspace); on!(kill); } + InputMode::Replace => {} } } diff --git a/yazi-fm/src/input/input.rs b/yazi-fm/src/input/input.rs index eb17a3b1..1174e7ab 100644 --- a/yazi-fm/src/input/input.rs +++ b/yazi-fm/src/input/input.rs @@ -59,6 +59,7 @@ impl Widget for Input<'_> { _ = match input.mode() { InputMode::Insert => Term::set_cursor_bar(), + InputMode::Replace => Term::set_cursor_underscore(), _ => Term::set_cursor_block(), }; } diff --git a/yazi-fm/src/term.rs b/yazi-fm/src/term.rs index 8598ad61..b5050855 100644 --- a/yazi-fm/src/term.rs +++ b/yazi-fm/src/term.rs @@ -159,6 +159,16 @@ impl Term { queue!(stderr(), SetCursorStyle::SteadyBar)? }) } + + #[inline] + pub(super) fn set_cursor_underscore() -> Result<()> { + use crossterm::cursor::SetCursorStyle; + Ok(if INPUT.cursor_blink { + queue!(stderr(), SetCursorStyle::BlinkingUnderScore)? + } else { + queue!(stderr(), SetCursorStyle::SteadyUnderScore)? + }) + } } impl Drop for Term {