@@ -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) {
@@ -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<Selection<Anchor>>,
pub(crate) undo_modes: HashMap<TransactionId, Mode>,
pub(crate) undo_last_line_tx: Option<TransactionId>,
+ extended_pending_selection_id: Option<usize>,
selected_register: Option<char>,
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);
+ }
}
});
})