diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index d0cf4621a5..6458ac1510 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -220,6 +220,8 @@ { "context": "vim_mode == normal", "bindings": { + "i": "vim::InsertBefore", + "a": "vim::InsertAfter", "ctrl-[": "editor::Cancel", ":": "command_palette::Toggle", "c": "vim::PushChange", @@ -353,9 +355,7 @@ "shift-d": "vim::DeleteToEndOfLine", "shift-j": "vim::JoinLines", "shift-y": "vim::YankLine", - "i": "vim::InsertBefore", "shift-i": "vim::InsertFirstNonWhitespace", - "a": "vim::InsertAfter", "shift-a": "vim::InsertEndOfLine", "o": "vim::InsertLineBelow", "shift-o": "vim::InsertLineAbove", @@ -377,6 +377,8 @@ { "context": "vim_mode == helix_normal && !menu", "bindings": { + "i": "vim::HelixInsert", + "a": "vim::HelixAppend", "ctrl-[": "editor::Cancel", ";": "vim::HelixCollapseSelection", ":": "command_palette::Toggle", diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index ec9b959b12..798af3bff3 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -4,18 +4,28 @@ use gpui::{Context, Window}; use language::{CharClassifier, CharKind}; use text::SelectionGoal; -use crate::{Vim, motion::Motion, state::Mode}; +use crate::{ + Vim, + motion::{Motion, right}, + state::Mode, +}; actions!( vim, [ /// Switches to normal mode after the cursor (Helix-style). - HelixNormalAfter + HelixNormalAfter, + /// Inserts at the beginning of the selection. + HelixInsert, + /// Appends at the end of the selection. + HelixAppend, ] ); pub fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, Vim::helix_normal_after); + Vim::action(editor, cx, Vim::helix_insert); + Vim::action(editor, cx, Vim::helix_append); } impl Vim { @@ -299,6 +309,38 @@ impl Vim { _ => self.helix_move_and_collapse(motion, times, window, cx), } } + + fn helix_insert(&mut self, _: &HelixInsert, window: &mut Window, cx: &mut Context) { + self.start_recording(cx); + self.update_editor(window, cx, |_, editor, window, cx| { + editor.change_selections(Default::default(), window, cx, |s| { + s.move_with(|_map, selection| { + // In helix normal mode, move cursor to start of selection and collapse + if !selection.is_empty() { + selection.collapse_to(selection.start, SelectionGoal::None); + } + }); + }); + }); + self.switch_mode(Mode::Insert, false, window, cx); + } + + fn helix_append(&mut self, _: &HelixAppend, window: &mut Window, cx: &mut Context) { + self.start_recording(cx); + self.switch_mode(Mode::Insert, false, window, cx); + self.update_editor(window, cx, |_, editor, window, cx| { + editor.change_selections(Default::default(), window, cx, |s| { + s.move_with(|map, selection| { + let point = if selection.is_empty() { + right(map, selection.head(), 1) + } else { + selection.end + }; + selection.collapse_to(point, SelectionGoal::None); + }); + }); + }); + } } #[cfg(test)] @@ -497,4 +539,68 @@ mod test { cx.assert_state("«ˇaa»\n", Mode::HelixNormal); } + + #[gpui::test] + async fn test_insert_selected(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + cx.set_state( + indoc! {" + «The ˇ»quick brown + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + + cx.simulate_keystrokes("i"); + + cx.assert_state( + indoc! {" + ˇThe quick brown + fox jumps over + the lazy dog."}, + Mode::Insert, + ); + } + + #[gpui::test] + async fn test_append(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + // test from the end of the selection + cx.set_state( + indoc! {" + «Theˇ» quick brown + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + + cx.simulate_keystrokes("a"); + + cx.assert_state( + indoc! {" + Theˇ quick brown + fox jumps over + the lazy dog."}, + Mode::Insert, + ); + + // test from the beginning of the selection + cx.set_state( + indoc! {" + «ˇThe» quick brown + fox jumps over + the lazy dog."}, + Mode::HelixNormal, + ); + + cx.simulate_keystrokes("a"); + + cx.assert_state( + indoc! {" + Theˇ quick brown + fox jumps over + the lazy dog."}, + Mode::Insert, + ); + } }