@@ -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",
@@ -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>) {
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>) {
+ 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>) {
+ 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,
+ );
+ }
}