diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 1f2742f982bc2165181a797e577b350f5630def9..66693ab0a153a73af1dccb101e0ed36259b774fa 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -427,6 +427,7 @@ "escape": "vim::SwitchToHelixNormalMode", "i": "vim::HelixInsert", "a": "vim::HelixAppend", + "shift-a": "vim::HelixInsertEndOfLine", "ctrl-[": "editor::Cancel", }, }, diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index 126683f0b419ae9a44d17d90d760f06b106fad8a..06630d18edfe0d1f3e643f02a1f50e5a1f4a0682 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -36,6 +36,8 @@ actions!( HelixInsert, /// Appends at the end of the selection. HelixAppend, + /// Inserts at the end of the current Helix cursor line. + HelixInsertEndOfLine, /// Goes to the location of the last modification. HelixGotoLastModification, /// Select entire line or multiple lines, extending downwards. @@ -64,6 +66,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, Vim::helix_select_lines); Vim::action(editor, cx, Vim::helix_insert); Vim::action(editor, cx, Vim::helix_append); + Vim::action(editor, cx, Vim::helix_insert_end_of_line); Vim::action(editor, cx, Vim::helix_yank); Vim::action(editor, cx, Vim::helix_goto_last_modification); Vim::action(editor, cx, Vim::helix_paste); @@ -600,6 +603,34 @@ impl Vim { }); } + /// Helix-specific implementation of `shift-a` that accounts for Helix's + /// selection model, where selecting a line with `x` creates a selection + /// from column 0 of the current row to column 0 of the next row, so the + /// default [`vim::normal::InsertEndOfLine`] would move the cursor to the + /// end of the wrong line. + fn helix_insert_end_of_line( + &mut self, + _: &HelixInsertEndOfLine, + window: &mut Window, + cx: &mut Context, + ) { + self.start_recording(cx); + self.switch_mode(Mode::Insert, false, window, cx); + self.update_editor(cx, |_, editor, cx| { + editor.change_selections(Default::default(), window, cx, |s| { + s.move_with(&mut |map, selection| { + let cursor = if !selection.is_empty() && !selection.reversed { + movement::left(map, selection.head()) + } else { + selection.head() + }; + selection + .collapse_to(motion::next_line_end(map, cursor, 1), SelectionGoal::None); + }); + }); + }); + } + pub fn helix_replace(&mut self, text: &str, window: &mut Window, cx: &mut Context) { self.update_editor(cx, |_, editor, cx| { editor.transact(window, cx, |editor, window, cx| { @@ -1447,6 +1478,47 @@ mod test { ˇ»line five"}, Mode::HelixNormal, ); + + // Test selecting with an empty line below the current line + cx.set_state( + indoc! {" + line one + line twoˇ + + line four + line five"}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("x"); + cx.assert_state( + indoc! {" + line one + «line two + ˇ» + line four + line five"}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("x"); + cx.assert_state( + indoc! {" + line one + «line two + + ˇ»line four + line five"}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("x"); + cx.assert_state( + indoc! {" + line one + «line two + + line four + ˇ»line five"}, + Mode::HelixNormal, + ); } #[gpui::test] @@ -1848,4 +1920,51 @@ mod test { Mode::HelixSelect, ); } + + #[gpui::test] + async fn test_helix_insert_end_of_line(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + cx.enable_helix(); + + // Ensure that, when lines are selected using `x`, pressing `shift-a` + // actually puts the cursor at the end of the selected lines and not at + // the end of the line below. + cx.set_state( + indoc! {" + line oˇne + line two"}, + Mode::HelixNormal, + ); + + cx.simulate_keystrokes("x"); + cx.assert_state( + indoc! {" + «line one + ˇ»line two"}, + Mode::HelixNormal, + ); + + cx.simulate_keystrokes("shift-a"); + cx.assert_state( + indoc! {" + line oneˇ + line two"}, + Mode::Insert, + ); + + cx.set_state( + indoc! {" + line «one + lineˇ» two"}, + Mode::HelixNormal, + ); + + cx.simulate_keystrokes("shift-a"); + cx.assert_state( + indoc! {" + line one + line twoˇ"}, + Mode::Insert, + ); + } }