vim: Fix Helix jump on selected lines (#57565)

Resolves https://github.com/zed-industries/zed/issues/57486

This diff fixes `g w` after selecting lines with `x` in Helix mode. `x`
leaves Zed in Helix normal mode with a non-empty selection, but jump
target collection treated non-visual selections as ranges to skip. As a
result, words on the selected line did not receive jump labels.

With this change, Helix normal mode keeps existing selection ranges
eligible for jump targets, matching Helix's behavior where normal mode
can still carry selections. The regression covers `x` followed by `g w`
targeting a word inside the selected line.

Release Notes:

- Fixed `g w` not targeting words on lines selected with `x` in Helix
mode.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
liamandSmit Barmase authored and GitHub committed 2026-05-26 14:05:40 +00:00
1 parent 35ba059398
commit 3e57e2b546
3 files changed
+30 -8

No files matched your search

+24 -7
View File
@@ -1004,8 +1004,9 @@ impl Vim {
window: &mut Window,
cx: &mut Context<Self>,
) {
let is_visual = self.mode.is_visual();
let Some(data) = self.collect_helix_jump_data(is_visual, window, cx) else {
let allow_targets_in_selection = self.mode.has_selection();
let Some(data) = self.collect_helix_jump_data(allow_targets_in_selection, window, cx)
else {
return;
};
@@ -1031,7 +1032,7 @@ impl Vim {
fn collect_helix_jump_data(
&mut self,
is_visual: bool,
allow_targets_in_selection: bool,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<HelixJumpUiData> {
@@ -1044,7 +1045,11 @@ impl Vim {
let end_offset = buffer_snapshot.point_to_offset(visible_range.end);
let selections = editor.selections.all::<Point>(&display_snapshot);
let skip_data = Self::selection_skip_offsets(buffer_snapshot, &selections, is_visual);
let skip_data = Self::selection_skip_offsets(
buffer_snapshot,
&selections,
allow_targets_in_selection,
);
// Get the primary cursor position for alternating forward/backward labeling
let cursor_offset = selections
@@ -1254,7 +1259,7 @@ impl Vim {
fn selection_skip_offsets(
buffer: &MultiBufferSnapshot,
selections: &[Selection<Point>],
is_visual: bool,
allow_targets_in_selection: bool,
) -> HelixJumpSkipData {
let mut skip_points = Vec::with_capacity(selections.len());
let mut skip_ranges = Vec::new();
@@ -1263,8 +1268,7 @@ impl Vim {
let head_offset = buffer.point_to_offset(selection.head());
skip_points.push(head_offset);
// In visual mode, don't skip ranges so we can shrink the selection
if !is_visual && selection.start != selection.end {
if !allow_targets_in_selection && selection.start != selection.end {
let mut start = buffer.point_to_offset(selection.start);
let mut end = buffer.point_to_offset(selection.end);
if start > end {
@@ -3489,6 +3493,19 @@ mod test {
assert_eq!(cx.active_operator(), None);
}
#[gpui::test]
async fn test_helix_jump_includes_line_selection_targets(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.enable_helix();
cx.set_state("alpha beta\nˇfoo bar baz\nqux quux", Mode::HelixNormal);
cx.simulate_keystrokes("x");
jump_to_word(&mut cx, "bar");
cx.assert_state("alpha beta\nfoo «barˇ» baz\nqux quux", Mode::HelixNormal);
assert_eq!(cx.active_operator(), None);
}
#[gpui::test]
async fn test_vim_jump_moves_to_target_word_start(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
+5
View File
@@ -80,6 +80,11 @@ impl Mode {
matches!(self, Self::HelixNormal | Self::HelixSelect)
}
/// `HelixNormal` qualifies because its cursor is itself a one-character selection.
pub fn has_selection(&self) -> bool {
self.is_visual() || matches!(self, Self::HelixNormal)
}
pub fn is_normal(&self) -> bool {
matches!(self, Self::Normal | Self::HelixNormal)
}
+1 -1
View File
@@ -2233,7 +2233,7 @@ impl Vim {
input_enabled: self.editor_input_enabled(),
expects_character_input: self.expects_character_input(),
autoindent: self.should_autoindent(),
cursor_offset_on_selection: self.mode.is_visual() || self.mode.is_helix(),
cursor_offset_on_selection: self.mode.has_selection(),
line_mode: matches!(self.mode, Mode::VisualLine),
hide_edit_predictions: !matches!(self.mode, Mode::Insert | Mode::Replace)
&& !(self.mode.is_normal()