diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 4c61479157268e4f0276bddf9dd1eb913284d27e..3b39bd468c57d171b29d84c6dcc2a92fd9e82af6 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -1303,6 +1303,39 @@ async fn test_mouse_selection(cx: &mut TestAppContext) { cx.assert_state("one «ˇtwo» three", Mode::Visual) } +#[gpui::test] +async fn test_mouse_drag_across_anchor_does_not_drift(cx: &mut TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.set_state("ˇone two three four", Mode::Normal); + + let click_pos = cx.pixel_position("one ˇtwo three four"); + let drag_left = cx.pixel_position("ˇone two three four"); + let anchor_pos = cx.pixel_position("one tˇwo three four"); + + cx.simulate_mouse_down(click_pos, MouseButton::Left, Modifiers::none()); + cx.run_until_parked(); + + cx.simulate_mouse_move(drag_left, MouseButton::Left, Modifiers::none()); + cx.run_until_parked(); + cx.assert_state("«ˇone t»wo three four", Mode::Visual); + + cx.simulate_mouse_move(anchor_pos, MouseButton::Left, Modifiers::none()); + cx.run_until_parked(); + + cx.simulate_mouse_move(drag_left, MouseButton::Left, Modifiers::none()); + cx.run_until_parked(); + cx.assert_state("«ˇone t»wo three four", Mode::Visual); + + cx.simulate_mouse_move(anchor_pos, MouseButton::Left, Modifiers::none()); + cx.run_until_parked(); + cx.simulate_mouse_move(drag_left, MouseButton::Left, Modifiers::none()); + cx.run_until_parked(); + cx.assert_state("«ˇone t»wo three four", Mode::Visual); + + cx.simulate_mouse_up(drag_left, MouseButton::Left, Modifiers::none()); +} + #[perf] #[gpui::test] async fn test_lowercase_marks(cx: &mut TestAppContext) { diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 26fec968fb261fbb80a9f84211357623147ca0f4..6a4be853b714e8e7e53cfdc43b96086c5e83c5ea 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -23,8 +23,9 @@ use crate::normal::paste::Paste as VimPaste; use collections::HashMap; use editor::{ Anchor, Bias, Editor, EditorEvent, EditorSettings, HideMouseCursorOrigin, MultiBufferOffset, - SelectionEffects, ToPoint, + SelectionEffects, actions::Paste, + display_map::ToDisplayPoint, movement::{self, FindRange}, }; use gpui::{ @@ -37,6 +38,8 @@ use language::{ }; pub use mode_indicator::ModeIndicator; use motion::Motion; +use multi_buffer::ToPoint as _; +use multi_buffer::ToPoint as _; use normal::search::SearchSubmit; use object::Object; use schemars::JsonSchema; @@ -503,6 +506,7 @@ pub(crate) struct Vim { pub(crate) current_anchor: Option>, pub(crate) undo_modes: HashMap, pub(crate) undo_last_line_tx: Option, + extended_pending_selection_id: Option, selected_register: Option, pub search: SearchState, @@ -561,6 +565,7 @@ impl Vim { current_tx: None, undo_last_line_tx: None, current_anchor: None, + extended_pending_selection_id: None, undo_modes: HashMap::default(), status_label: None, @@ -1218,17 +1223,32 @@ impl Vim { s.select_anchor_ranges(vec![pos..pos]) } - let snapshot = s.display_snapshot(); - if let Some(pending) = s.pending_anchor_mut() - && pending.reversed + let mut should_extend_pending = false; + if !last_mode.is_visual() && mode.is_visual() - && !last_mode.is_visual() + && let Some(pending) = s.pending_anchor() { - let mut end = pending.end.to_point(&snapshot.buffer_snapshot()); - end = snapshot - .buffer_snapshot() - .clip_point(end + Point::new(0, 1), Bias::Right); - pending.end = snapshot.buffer_snapshot().anchor_before(end); + let snapshot = s.display_snapshot(); + let is_empty = pending + .start + .cmp(&pending.end, &snapshot.buffer_snapshot()) + .is_eq(); + should_extend_pending = pending.reversed + && !is_empty + && vim.extended_pending_selection_id != Some(pending.id); + }; + + if should_extend_pending { + let snapshot = s.display_snapshot(); + if let Some(pending) = s.pending_anchor_mut() { + let end = pending.end.to_point(&snapshot.buffer_snapshot()); + let end = end.to_display_point(&snapshot); + let new_end = movement::right(&snapshot, end); + pending.end = snapshot + .buffer_snapshot() + .anchor_before(new_end.to_point(&snapshot)); + } + vim.extended_pending_selection_id = s.pending_anchor().map(|p| p.id) } s.move_with(|map, selection| { @@ -1240,8 +1260,10 @@ impl Vim { point = map.clip_point(point, Bias::Left); } selection.collapse_to(point, selection.goal) - } else if !last_mode.is_visual() && mode.is_visual() && selection.is_empty() { - selection.end = movement::right(map, selection.start); + } else if !last_mode.is_visual() && mode.is_visual() { + if selection.is_empty() { + selection.end = movement::right(map, selection.start); + } } }); })