From 7d1b896fb80f44678ef8cd922e1ea94b8df514e9 Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Mon, 15 Dec 2025 19:46:25 -0800 Subject: [PATCH] fix: move cursor left when exiting vi insert mode with Escape In vim, pressing Escape to exit insert mode moves the cursor left by one position. This is because insert mode positions the cursor between characters, while normal mode positions it on a character. Without this fix, repeatedly pressing "i i " would not move the cursor backward as expected in vim. --- src/edit_mode/vi/mod.rs | 47 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/edit_mode/vi/mod.rs b/src/edit_mode/vi/mod.rs index a3204adb..9e6a8855 100644 --- a/src/edit_mode/vi/mod.rs +++ b/src/edit_mode/vi/mod.rs @@ -156,8 +156,20 @@ impl EditMode for Vi { } (_, KeyModifiers::NONE, KeyCode::Esc) => { self.cache.clear(); + let was_insert = self.mode == ViMode::Insert; self.mode = ViMode::Normal; - ReedlineEvent::Multiple(vec![ReedlineEvent::Esc, ReedlineEvent::Repaint]) + // In vim, exiting insert mode moves cursor left by one position + // because insert mode cursor is between characters while normal mode + // cursor is on a character + if was_insert { + ReedlineEvent::Multiple(vec![ + ReedlineEvent::Edit(vec![EditCommand::MoveLeft { select: false }]), + ReedlineEvent::Esc, + ReedlineEvent::Repaint, + ]) + } else { + ReedlineEvent::Multiple(vec![ReedlineEvent::Esc, ReedlineEvent::Repaint]) + } } (ViMode::Normal | ViMode::Visual, _, _) => self .normal_keybindings @@ -221,13 +233,17 @@ mod test { use pretty_assertions::assert_eq; #[test] - fn esc_leads_to_normal_mode_test() { - let mut vi = Vi::default(); + fn esc_from_normal_mode_stays_in_normal_mode() { + let mut vi = Vi { + mode: ViMode::Normal, + ..Default::default() + }; let esc = ReedlineRawEvent::try_from(Event::Key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE))) .unwrap(); let result = vi.parse_event(esc); + // Esc from normal mode should NOT move cursor left assert_eq!( result, ReedlineEvent::Multiple(vec![ReedlineEvent::Esc, ReedlineEvent::Repaint]) @@ -332,4 +348,29 @@ mod test { assert_eq!(result, ReedlineEvent::None); } + + #[test] + fn esc_from_insert_mode_moves_cursor_left() { + // In vim, pressing Escape from insert mode should move cursor left by one + // This is because insert mode cursor is between characters, but normal mode + // cursor is on a character. Repeating "i i " should slowly move backward. + let mut vi = Vi::default(); + assert!(matches!(vi.mode, ViMode::Insert)); + + let esc = + ReedlineRawEvent::try_from(Event::Key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE))) + .unwrap(); + let result = vi.parse_event(esc); + + // Should include MoveLeft to match vim behavior + assert_eq!( + result, + ReedlineEvent::Multiple(vec![ + ReedlineEvent::Edit(vec![EditCommand::MoveLeft { select: false }]), + ReedlineEvent::Esc, + ReedlineEvent::Repaint + ]) + ); + assert!(matches!(vi.mode, ViMode::Normal)); + } }