From 3342d2700354974d5ce575da2a1720426bcd9382 Mon Sep 17 00:00:00 2001 From: Florian Plattner Date: Thu, 21 May 2026 17:37:17 +0200 Subject: [PATCH 1/2] vim: Add test for helix selection duplication issue --- crates/vim/src/helix/duplicate.rs | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/crates/vim/src/helix/duplicate.rs b/crates/vim/src/helix/duplicate.rs index 2069f61c29c..3d621239326 100644 --- a/crates/vim/src/helix/duplicate.rs +++ b/crates/vim/src/helix/duplicate.rs @@ -147,6 +147,8 @@ fn display_point_range_to_offset_range( #[cfg(test)] mod tests { use db::indoc; + use gpui::{AppContext, UpdateGlobal}; + use settings::SettingsStore; use crate::{state::Mode, test::VimTestContext}; @@ -310,4 +312,50 @@ mod tests { Mode::HelixNormal, ); } + + #[gpui::test] + async fn test_selection_duplication_softwrap(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.enable_helix(); + cx.update_global(|settings: &mut SettingsStore, cx| { + settings.update_user_settings(cx, |settings| { + settings.project.all_languages.defaults.soft_wrap = + Some(settings::SoftWrap::Bounded); + settings + .project + .all_languages + .defaults + .preferred_line_length = Some(8); + }); + }); + + cx.set_state( + indoc! {" + The quick brown + foˇx jumps over the + lazy dog."}, + Mode::HelixNormal, + ); + + cx.simulate_keystrokes("C"); + + cx.assert_state( + indoc! {" + The quick brown + foˇx jumps over the + laˇzy dog."}, + Mode::HelixNormal, + ); + + cx.simulate_keystrokes("alt-C"); + + cx.assert_state( + indoc! {" + Thˇe quick brown + foˇx jumps over the + laˇzy dog."}, + Mode::HelixNormal, + ); + } } From 32b52664006e3dccf025923e41af200a06c5c401 Mon Sep 17 00:00:00 2001 From: Florian Plattner Date: Thu, 21 May 2026 17:37:17 +0200 Subject: [PATCH 2/2] vim: Fix helix selection duplication with softwrap --- crates/vim/src/helix/duplicate.rs | 55 +++++++++++++++---------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/crates/vim/src/helix/duplicate.rs b/crates/vim/src/helix/duplicate.rs index 3d621239326..85a88a99707 100644 --- a/crates/vim/src/helix/duplicate.rs +++ b/crates/vim/src/helix/duplicate.rs @@ -1,6 +1,9 @@ use std::ops::Range; -use editor::{DisplayPoint, MultiBufferOffset, display_map::DisplaySnapshot}; +use editor::{ + DisplayPoint, MultiBufferOffset, ToPoint, + display_map::{DisplayRow, DisplaySnapshot, ToDisplayPoint}, +}; use gpui::Context; use language::PointUtf16; use multi_buffer::MultiBufferRow; @@ -89,51 +92,47 @@ fn find_next_valid_duplicate_space( .column; let end_col_utf16 = buffer.point_to_point_utf16(origin.end.to_point(map)).column; - let mut candidate = origin; + let mut candidate_start_row = origin.start.to_point(map).row; + let mut candidate_end_row = origin.end.to_point(map).row; + loop { match direction { Direction::Below => { - if candidate.end.row() >= map.max_point().row() { + if candidate_end_row >= map.max_row().0 { return None; } - *candidate.start.row_mut() += 1; - *candidate.end.row_mut() += 1; + candidate_start_row += 1; + candidate_end_row += 1; } Direction::Above => { - if candidate.start.row() == DisplayPoint::zero().row() { + if candidate_start_row == 0 { return None; } - *candidate.start.row_mut() = candidate.start.row().0.saturating_sub(1); - *candidate.end.row_mut() = candidate.end.row().0.saturating_sub(1); + candidate_start_row = candidate_start_row.saturating_sub(1); + candidate_end_row = candidate_end_row.saturating_sub(1); } } - let start_row = DisplayPoint::new(candidate.start.row(), 0) - .to_point(map) - .row; - let end_row = DisplayPoint::new(candidate.end.row(), 0).to_point(map).row; - - if start_col_utf16 > buffer.line_len_utf16(MultiBufferRow(start_row)) - || end_col_utf16 > buffer.line_len_utf16(MultiBufferRow(end_row)) + if map.is_line_folded(MultiBufferRow(candidate_start_row)) + || map.is_line_folded(MultiBufferRow(candidate_end_row)) { continue; } - let start_col = buffer - .point_utf16_to_point(PointUtf16::new(start_row, start_col_utf16)) - .column; - let end_col = buffer - .point_utf16_to_point(PointUtf16::new(end_row, end_col_utf16)) - .column; - - let candidate_start = DisplayPoint::new(candidate.start.row(), start_col); - let candidate_end = DisplayPoint::new(candidate.end.row(), end_col); - - if map.clip_point(candidate_start, Bias::Left) == candidate_start - && map.clip_point(candidate_end, Bias::Right) == candidate_end + if start_col_utf16 > buffer.line_len_utf16(MultiBufferRow(candidate_start_row)) + || end_col_utf16 > buffer.line_len_utf16(MultiBufferRow(candidate_end_row)) { - return Some(candidate_start..candidate_end); + continue; } + + let candidate_start = buffer + .point_utf16_to_point(PointUtf16::new(candidate_start_row, start_col_utf16)) + .to_display_point(map); + let candidate_end = buffer + .point_utf16_to_point(PointUtf16::new(candidate_end_row, end_col_utf16)) + .to_display_point(map); + + return Some(candidate_start..candidate_end); } }