Improve helix keymap (#59638)

# Objective

The objective is to improve Helix's default keymap within Zed as a few
are still missing.
These are all [default
keymaps](https://docs.helix-editor.com/keymap.html) i was using within
Helix but dissapointed they weren't working in Zed
There's more info in [Are we Helix
yet?](https://github.com/zed-industries/zed/discussions/33580#top)

## Solution

- Add `] g` and `[ g` for hunk navigation in helix mode, in helix this
is go to next/previous change which maps nicely to Zed's go to
next/previous hunk. (it was set to `c` here but this is incorrect and
doesn't match Helix's keymap.
- Add `alt-b` and `alt-e` for larger syntax node navigation in helix
mode, i use this a few times to go to the top of a function within the
body and Zed doesn't have it mapped.
- Add `] space` and `[ space` for inserting empty lines in helix mode.
This one was incorrectly implemented previously, after the `space` Zed
is waiting for input. It needs to be a direct chord added rather than on
`helix_next` mode.
- This means the space binding from the helix_next operator context is
redundant, so ive removed it
- Add `*` to use selection for find in helix mode. Helix mode is
slightly different and doesn't "go to next" on `*`, instead that becomes
the `/` register. This is pretty fundamental to helix navigation so
should be ported to Zed also.
- Move `] d` and `[ d` diagnostics navigation into helix_normal context.
Vim was already using this one but it wasn't shared with Helix. I've
moved it to the shared Vim and Helix block.


## Testing

I've tested these changes with a local build and each one works as
expected


## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- Added `alt-b`/`alt-e` in Helix mode to move to the start/end of the
larger syntax node.
- Added `*` in Helix mode to set the current selection for search.
- Fixed Helix `[`/`]` navigation so `c` goes to the previous/next
comment and `g` to the previous/next hunk, and single-key follow-ups
like `g` no longer hang.

---------

Co-authored-by: dino <dinojoaocosta@gmail.com>
This commit is contained in:
Jason Williams 2026-06-30 10:51:07 +01:00 committed by GitHub
parent 6a1ced4a8e
commit ae0f4462ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 87 additions and 2 deletions

View file

@ -454,6 +454,8 @@
"shift-t": ["vim::PushFindBackward", { "after": true, "multiline": true }],
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": true }],
"alt-.": "vim::RepeatFind",
"alt-b": "editor::MoveToStartOfLargerSyntaxNode",
"alt-e": "editor::MoveToEndOfLargerSyntaxNode",
// Changes
"shift-r": "editor::Paste",
@ -483,6 +485,7 @@
"alt-shift-c": "vim::HelixDuplicateAbove",
"%": "editor::SelectAll",
"x": "vim::HelixSelectLine",
"*": "buffer_search::UseSelectionForFind",
"shift-x": "editor::SelectLine",
"ctrl-c": "editor::ToggleComments",
"alt-o": "editor::SelectLargerSyntaxNode",
@ -688,7 +691,8 @@
"shift-b": "pane::ActivateLastItem",
"x": "editor::SelectSmallerSyntaxNode",
"d": "editor::GoToDiagnostic",
"c": "editor::GoToHunk",
"c": "vim::NextComment",
"g": "editor::GoToHunk",
"space": "vim::InsertEmptyLineBelow",
},
},
@ -706,7 +710,8 @@
"shift-b": ["pane::ActivateItem", 0],
"x": "editor::SelectLargerSyntaxNode",
"d": "editor::GoToPreviousDiagnostic",
"c": "editor::GoToPreviousHunk",
"c": "vim::PreviousComment",
"g": "editor::GoToPreviousHunk",
"space": "vim::InsertEmptyLineAbove",
},
},

View file

@ -4240,4 +4240,68 @@ mod test {
Mode::HelixSelect,
);
}
#[gpui::test]
async fn test_helix_go_to_hunk(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.enable_helix();
cx.set_state(
indoc! {"
ˇone
two
three"},
Mode::HelixNormal,
);
cx.set_head_text(indoc! {"
one
CHANGED
three"});
cx.run_until_parked();
cx.simulate_keystrokes("]");
assert_eq!(
cx.active_operator(),
Some(Operator::HelixNext { around: true })
);
cx.simulate_keystrokes("g");
cx.assert_state(
indoc! {"
one
ˇtwo
three"},
Mode::HelixNormal,
);
assert_eq!(cx.active_operator(), None);
cx.set_state(
indoc! {"
one
two
ˇthree"},
Mode::HelixNormal,
);
cx.set_head_text(indoc! {"
one
CHANGED
three"});
cx.run_until_parked();
cx.simulate_keystrokes("[");
assert_eq!(
cx.active_operator(),
Some(Operator::HelixPrevious { around: true })
);
cx.simulate_keystrokes("g");
cx.assert_state(
indoc! {"
one
ˇtwo
three"},
Mode::HelixNormal,
);
assert_eq!(cx.active_operator(), None);
}
}

View file

@ -1469,6 +1469,22 @@ impl Vim {
} else {
mode = "waiting".to_string();
}
} else if matches!(
active_operator,
Operator::HelixNext { .. } | Operator::HelixPrevious { .. }
) {
// Helix `[`/`]` take a curated, keymap-dispatched selector key
// rather than a motion over a range, so they keep `operator_id`
// set (so `vim_operator == helix_next/previous` context must
// resolve) but must not use the `operator` mode, as that adds
// `VimControl` and the `vim_mode == operator` context, whose `g
// ...` bindings would make a single-key follow-up like `g` a
// multi-key prefix and leave `] g` waiting for more input.
// Setting the mode to `waiting` carries none of those
// conflicting bindings and still provides bindings for
// `escape`/`ctrl-c` to `ClearOperators`.
operator_id = active_operator.id();
mode = "waiting".to_string();
} else {
operator_id = active_operator.id();
mode = "operator".to_string();