From 3e57e2b54657e420020a558cfda8f3d3f95c9366 Mon Sep 17 00:00:00 2001 From: liam Date: Tue, 26 May 2026 10:05:40 -0400 Subject: [PATCH] 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 --- crates/vim/src/helix.rs | 31 ++++++++++++++++++++++++------- crates/vim/src/state.rs | 5 +++++ crates/vim/src/vim.rs | 2 +- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index 314327b048..ad5cd37e3f 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -1004,8 +1004,9 @@ impl Vim { window: &mut Window, cx: &mut Context, ) { - 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, ) -> Option { @@ -1044,7 +1045,11 @@ impl Vim { let end_offset = buffer_snapshot.point_to_offset(visible_range.end); let selections = editor.selections.all::(&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], - 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; diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 1c15c7d486..b92adf4cb5 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -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) } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index b8e387e972..678b30d8ad 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -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()