Add `;` key binding for Helix mode (#34315)

Daniel Sauble created

Closes #34111

In Helix mode, the `;` key should collapse the current selection without
moving the cursor. I've added a new action `vim::HelixCollapseSelection`
to support this behavior.


https://github.com/user-attachments/assets/1a40821a-f56f-456e-9d37-532500bef17b

Release Notes:

- Added `;` key binding to collapse the current text selection in Helix
mode

Change summary

assets/keymaps/vim.json  |  1 +
crates/vim/src/normal.rs | 16 ++++++++++++++++
2 files changed, 17 insertions(+)

Detailed changes

assets/keymaps/vim.json 🔗

@@ -377,6 +377,7 @@
     "context": "vim_mode == helix_normal && !menu",
     "bindings": {
       "ctrl-[": "editor::Cancel",
+      ";": "vim::HelixCollapseSelection",
       ":": "command_palette::Toggle",
       "left": "vim::WrappingLeft",
       "right": "vim::WrappingRight",

crates/vim/src/normal.rs 🔗

@@ -64,6 +64,8 @@ actions!(
         DeleteRight,
         /// Deletes using Helix-style behavior.
         HelixDelete,
+        /// Collapse the current selection
+        HelixCollapseSelection,
         /// Changes from cursor to end of line.
         ChangeToEndOfLine,
         /// Deletes from cursor to end of line.
@@ -143,6 +145,20 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
         vim.switch_mode(Mode::HelixNormal, true, window, cx);
     });
 
+    Vim::action(editor, cx, |vim, _: &HelixCollapseSelection, window, cx| {
+        vim.update_editor(window, cx, |_, editor, window, cx| {
+            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
+                s.move_with(|map, selection| {
+                    let mut point = selection.head();
+                    if !selection.reversed && !selection.is_empty() {
+                        point = movement::left(map, selection.head());
+                    }
+                    selection.collapse_to(point, selection.goal)
+                });
+            });
+        });
+    });
+
     Vim::action(editor, cx, |vim, _: &ChangeToEndOfLine, window, cx| {
         vim.start_recording(cx);
         let times = Vim::take_count(cx);