From 77214ff15cabd0c4170bdf013cc8ce4c10268bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20D=C3=A9saulniers?= Date: Tue, 5 May 2026 08:28:17 -0400 Subject: [PATCH] feat(vi): support deleting chars backward with X --- src/edit_mode/vi/command.rs | 15 +++++++++++++++ src/edit_mode/vi/parser.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/edit_mode/vi/command.rs b/src/edit_mode/vi/command.rs index 18999e5c..f7bcf724 100644 --- a/src/edit_mode/vi/command.rs +++ b/src/edit_mode/vi/command.rs @@ -109,6 +109,10 @@ where let _ = input.next(); Some(Command::DeleteChar) } + Some('X') => { + let _ = input.next(); + Some(Command::DeleteCharBackward) + } Some('r') => { let _ = input.next(); input @@ -177,6 +181,7 @@ pub enum Command { Incomplete, Delete, DeleteChar, + DeleteCharBackward, ReplaceChar(char), SubstituteCharWithInsert, NewlineAbove, @@ -242,6 +247,16 @@ impl Command { select: false, })], Self::RewriteCurrentLine => vec![ReedlineOption::Edit(EditCommand::CutCurrentLine)], + Self::DeleteCharBackward => { + if vi_state.mode == ViMode::Visual { + vec![ReedlineOption::Edit(EditCommand::CutSelection)] + } else { + vec![ + ReedlineOption::Edit(EditCommand::MoveLeft { select: false }), + ReedlineOption::Edit(EditCommand::CutChar), + ] + } + } Self::DeleteChar => { if vi_state.mode == ViMode::Visual { vec![ReedlineOption::Edit(EditCommand::CutSelection)] diff --git a/src/edit_mode/vi/parser.rs b/src/edit_mode/vi/parser.rs index da0839cd..5ba40db5 100644 --- a/src/edit_mode/vi/parser.rs +++ b/src/edit_mode/vi/parser.rs @@ -210,6 +210,36 @@ mod tests { parse(ViMode::Normal, &mut input.iter().peekable()) } + #[test] + fn test_delete_char_backward() { + let input = ['X']; + let output = vi_parse(&input); + assert_eq!( + output, + ParsedViSequence { + multiplier: None, + command: Some(Command::DeleteCharBackward), + count: None, + motion: ParseResult::Incomplete, + } + ); + } + + #[test] + fn test_two_delete_char_backward() { + let input = ['2', 'X']; + let output = vi_parse(&input); + assert_eq!( + output, + ParsedViSequence { + multiplier: Some(2), + command: Some(Command::DeleteCharBackward), + count: None, + motion: ParseResult::Incomplete, + } + ); + } + #[test] fn test_delete_without_motion() { let input = ['d'];