Flesh out helix bindings (#28103)

jneem created

This brings in a bunch of helix bindings (many of them from
infogulch/zed-helix-keymap) and implements helix-style delete.

Release Notes:

- vim: Expanded default helix-style keybindings in HelixNormal mode

Change summary

assets/keymaps/vim.json  | 99 +++++++++++++++++++++++++++++++++++++----
crates/vim/src/normal.rs | 16 ++++++
crates/vim/src/vim.rs    |  8 ++
3 files changed, 111 insertions(+), 12 deletions(-)

Detailed changes

assets/keymaps/vim.json 🔗

@@ -335,22 +335,101 @@
     }
   },
   {
-    "context": "vim_mode == helix_normal",
+    "context": "vim_mode == helix_normal && !menu",
     "bindings": {
+      "escape": "editor::Cancel",
+      "ctrl-[": "editor::Cancel",
+      ":": "command_palette::Toggle",
+      "shift-d": "vim::DeleteToEndOfLine",
+      "shift-j": "vim::JoinLines",
+      "y": "editor::Copy",
+      "shift-y": "vim::YankLine",
       "i": "vim::InsertBefore",
+      "shift-i": "vim::InsertFirstNonWhitespace",
       "a": "vim::InsertAfter",
-      "d": "vim::HelixDelete",
-      "w": "vim::NextWordStart",
-      "e": "vim::NextWordEnd",
-      "b": "vim::PreviousWordStart",
+      "shift-a": "vim::InsertEndOfLine",
+      "o": "vim::InsertLineBelow",
+      "shift-o": "vim::InsertLineAbove",
+      "~": "vim::ChangeCase",
+      "ctrl-a": "vim::Increment",
+      "ctrl-x": "vim::Decrement",
+      "p": "vim::Paste",
+      "shift-p": ["vim::Paste", { "before": true }],
+      "u": "vim::Undo",
+      "ctrl-r": "vim::Redo",
+      "r": "vim::PushReplace",
+      "s": "vim::Substitute",
+      "shift-s": "vim::SubstituteLine",
+      ">": "vim::Indent",
+      "<": "vim::Outdent",
+      "=": "vim::AutoIndent",
+      "g u": "vim::PushLowercase",
+      "g shift-u": "vim::PushUppercase",
+      "g ~": "vim::PushOppositeCase",
+      "\"": "vim::PushRegister",
+      "g q": "vim::PushRewrap",
+      "g w": "vim::PushRewrap",
+      "ctrl-pagedown": "pane::ActivateNextItem",
+      "ctrl-pageup": "pane::ActivatePreviousItem",
+      "insert": "vim::InsertBefore",
+      // tree-sitter related commands
+      "[ x": "editor::SelectLargerSyntaxNode",
+      "] x": "editor::SelectSmallerSyntaxNode",
+      "] d": "editor::GoToDiagnostic",
+      "[ d": "editor::GoToPreviousDiagnostic",
+      "] c": "editor::GoToHunk",
+      "[ c": "editor::GoToPreviousHunk",
+      // Goto mode
+      "g n": "pane::ActivateNextItem",
+      "g p": "pane::ActivatePreviousItem",
+      // "tab": "pane::ActivateNextItem",
+      // "shift-tab": "pane::ActivatePrevItem",
+      "shift-h": "pane::ActivatePreviousItem",
+      "shift-l": "pane::ActivateNextItem",
+      "g l": "vim::EndOfLine",
+      "g h": "vim::StartOfLine",
+      "g s": "vim::FirstNonWhitespace", // "g s" default behavior is "space s"
+      "g e": "vim::EndOfDocument",
+      "g y": "editor::GoToTypeDefinition",
+      "g r": "editor::FindAllReferences", // zed specific
+      "g t": "vim::WindowTop",
+      "g c": "vim::WindowMiddle",
+      "g b": "vim::WindowBottom",
 
-      "h": "vim::Left",
-      "j": "vim::Down",
-      "k": "vim::Up",
-      "l": "vim::Right"
+      "x": "editor::SelectLine",
+      "shift-x": "editor::SelectLine",
+      // Window mode
+      "space w h": "workspace::ActivatePaneLeft",
+      "space w l": "workspace::ActivatePaneRight",
+      "space w k": "workspace::ActivatePaneUp",
+      "space w j": "workspace::ActivatePaneDown",
+      "space w q": "pane::CloseActiveItem",
+      "space w s": "pane::SplitRight",
+      "space w r": "pane::SplitRight",
+      "space w v": "pane::SplitDown",
+      "space w d": "pane::SplitDown",
+      // Space mode
+      "space f": "file_finder::Toggle",
+      "space k": "editor::Hover",
+      "space s": "outline::Toggle",
+      "space shift-s": "project_symbols::Toggle",
+      "space d": "editor::GoToDiagnostic",
+      "space r": "editor::Rename",
+      "space a": "editor::ToggleCodeActions",
+      "space h": "editor::SelectAllMatches",
+      "space c": "editor::ToggleComments",
+      "space y": "editor::Copy",
+      "space p": "editor::Paste",
+      // Match mode
+      "m m": "vim::Matching",
+      "m i w": ["workspace::SendKeystrokes", "v i w"],
+      "shift-u": "editor::Redo",
+      "ctrl-c": "editor::ToggleComments",
+      "d": "vim::HelixDelete",
+      "c": "vim::Substitute",
+      "shift-c": "editor::AddSelectionBelow"
     }
   },
-
   {
     "context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
     "bindings": {

crates/vim/src/normal.rs 🔗

@@ -48,6 +48,7 @@ actions!(
         JoinLinesNoWhitespace,
         DeleteLeft,
         DeleteRight,
+        HelixDelete,
         ChangeToEndOfLine,
         DeleteToEndOfLine,
         Yank,
@@ -92,6 +93,21 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
         let times = Vim::take_count(cx);
         vim.delete_motion(Motion::Right, times, window, cx);
     });
+
+    Vim::action(editor, cx, |vim, _: &HelixDelete, window, cx| {
+        vim.record_current_action(cx);
+        vim.update_editor(window, cx, |_, editor, window, cx| {
+            editor.change_selections(None, window, cx, |s| {
+                s.move_with(|map, selection| {
+                    if selection.is_empty() {
+                        selection.end = movement::right(map, selection.end)
+                    }
+                })
+            })
+        });
+        vim.visual_delete(false, window, cx);
+    });
+
     Vim::action(editor, cx, |vim, _: &ChangeToEndOfLine, window, cx| {
         vim.start_recording(cx);
         let times = Vim::take_count(cx);

crates/vim/src/vim.rs 🔗

@@ -438,7 +438,7 @@ impl Vim {
 
         vim.update(cx, |_, cx| {
             Vim::action(editor, cx, |vim, _: &SwitchToNormalMode, window, cx| {
-                vim.switch_mode(Mode::Normal, false, window, cx)
+                vim.switch_mode(vim.default_mode(cx), false, window, cx)
             });
 
             Vim::action(editor, cx, |vim, _: &SwitchToInsertMode, window, cx| {
@@ -739,6 +739,10 @@ impl Vim {
         cx.on_release(|_, _| drop(subscription)).detach();
     }
 
+    pub fn default_mode(&self, cx: &App) -> Mode {
+        VimSettings::get_global(cx).default_mode
+    }
+
     pub fn editor(&self) -> Option<Entity<Editor>> {
         self.editor.upgrade()
     }
@@ -1105,7 +1109,7 @@ impl Vim {
             }
         }
 
-        if mode == "normal" || mode == "visual" || mode == "operator" {
+        if mode == "normal" || mode == "visual" || mode == "operator" || mode == "helix_normal" {
             context.add("VimControl");
         }
         context.set("vim_mode", mode);