From c72ea628bd8fc11a9da9474a2afc7ac2643e12fb Mon Sep 17 00:00:00 2001 From: Krystof Gartner Date: Thu, 9 Oct 2025 20:54:11 +0200 Subject: [PATCH] feat: add change case vim input actions (#3233) --- yazi-config/preset/keymap-default.toml | 5 +++-- yazi-widgets/src/input/commands/case.rs | 24 +++++++++++++++++++++ yazi-widgets/src/input/commands/commands.rs | 3 +++ yazi-widgets/src/input/commands/mod.rs | 2 +- yazi-widgets/src/input/commands/undo.rs | 6 +++++- yazi-widgets/src/input/input.rs | 14 ++++++++++++ yazi-widgets/src/input/op.rs | 2 ++ 7 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 yazi-widgets/src/input/commands/case.rs diff --git a/yazi-config/preset/keymap-default.toml b/yazi-config/preset/keymap-default.toml index dee02f00..dcf991e0 100644 --- a/yazi-config/preset/keymap-default.toml +++ b/yazi-config/preset/keymap-default.toml @@ -301,9 +301,10 @@ keymap = [ { on = "p", run = "paste", desc = "Paste copied characters after the cursor" }, { on = "P", run = "paste --before", desc = "Paste copied characters before the cursor" }, - # Undo/Redo - { on = "u", run = "undo", desc = "Undo the last operation" }, + # Undo/Redo/Change case + { on = "u", run = [ "undo", "lowercase"], desc = "Undo the last operation or Lowercase selected characters" }, { on = "", run = "redo", desc = "Redo the last operation" }, + { on = "U", run = "uppercase", desc = "Uppercase selected characters" }, # Help { on = "~", run = "help", desc = "Open help" }, diff --git a/yazi-widgets/src/input/commands/case.rs b/yazi-widgets/src/input/commands/case.rs new file mode 100644 index 00000000..85798b0c --- /dev/null +++ b/yazi-widgets/src/input/commands/case.rs @@ -0,0 +1,24 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_parser::VoidOpt; +use yazi_shared::data::Data; + +use crate::input::{Input, op::InputOp}; + +impl Input { + pub fn uppercase(&mut self, _: VoidOpt) -> Result { self.apply_case(true) } + + pub fn lowercase(&mut self, _: VoidOpt) -> Result { self.apply_case(false) } + + fn apply_case(&mut self, uppercase: bool) -> Result { + match self.snap().op { + InputOp::Select(start) => { + self.snap_mut().op = InputOp::Case(uppercase, start); + render!(self.handle_op(self.snap().cursor, true)); + act!(r#move, self)?; + } + _ => {} + } + succ!(); + } +} diff --git a/yazi-widgets/src/input/commands/commands.rs b/yazi-widgets/src/input/commands/commands.rs index ba6ed220..5bd15dbc 100644 --- a/yazi-widgets/src/input/commands/commands.rs +++ b/yazi-widgets/src/input/commands/commands.rs @@ -35,6 +35,9 @@ impl Input { on!(undo); on!(redo); + + on!(uppercase); + on!(lowercase); } InputMode::Insert => { on!(visual); diff --git a/yazi-widgets/src/input/commands/mod.rs b/yazi-widgets/src/input/commands/mod.rs index 9eafa31b..ce716f4b 100644 --- a/yazi-widgets/src/input/commands/mod.rs +++ b/yazi-widgets/src/input/commands/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(backspace backward commands complete delete escape forward insert kill paste r#move r#type redo replace undo visual yank); +yazi_macro::mod_flat!(backspace backward commands complete delete escape forward insert kill paste r#move r#type redo replace undo visual yank case); diff --git a/yazi-widgets/src/input/commands/undo.rs b/yazi-widgets/src/input/commands/undo.rs index 1f0c1a6f..119871a1 100644 --- a/yazi-widgets/src/input/commands/undo.rs +++ b/yazi-widgets/src/input/commands/undo.rs @@ -3,10 +3,14 @@ use yazi_macro::{act, render, succ}; use yazi_parser::VoidOpt; use yazi_shared::data::Data; -use crate::input::{Input, InputMode}; +use crate::input::{Input, InputMode, InputOp}; impl Input { pub fn undo(&mut self, _: VoidOpt) -> Result { + if let InputOp::Select(_) = self.snap().op { + succ!(); + } + if !self.snaps.undo() { succ!(); } diff --git a/yazi-widgets/src/input/input.rs b/yazi-widgets/src/input/input.rs index 8c636592..742366b5 100644 --- a/yazi-widgets/src/input/input.rs +++ b/yazi-widgets/src/input/input.rs @@ -50,6 +50,20 @@ impl Input { snap.op = InputOp::None; futures::executor::block_on(CLIPBOARD.set(yanked)); } + InputOp::Case(upper, _) => { + let range = snap.op.range(cursor, include).unwrap(); + let Range { start, end } = snap.idx(range.start)..snap.idx(range.end); + + if let (Some(start), Some(end)) = (start, end) { + let selected = &snap.value[start..end]; + let replaced = if upper { selected.to_uppercase() } else { selected.to_lowercase() }; + snap.value.replace_range(start..end, &replaced); + } + + snap.op = InputOp::None; + snap.mode = InputMode::Normal; + snap.cursor = range.start; + } }; snap.cursor = snap.count().saturating_sub(snap.mode.delta()).min(snap.cursor); diff --git a/yazi-widgets/src/input/op.rs b/yazi-widgets/src/input/op.rs index 036ca44a..b68a8513 100644 --- a/yazi-widgets/src/input/op.rs +++ b/yazi-widgets/src/input/op.rs @@ -7,6 +7,7 @@ pub enum InputOp { Select(usize), Delete(bool, bool, usize), // cut, insert, start Yank(usize), + Case(bool, usize), // upper, start } impl InputOp { @@ -17,6 +18,7 @@ impl InputOp { Self::Select(s) => Some(*s), Self::Delete(.., s) => Some(*s), Self::Yank(s) => Some(*s), + Self::Case(.., s) => Some(*s), } }