editor: Fix select when click on existing selection (#32365)

Follow-up for https://github.com/zed-industries/zed/pull/30671

Now, when clicking on an existing selection, the cursor will change on
`mouse_up` when `drag_and_drop_selection` is `true`. When
`drag_and_drop_selection` is `false`, it will change on `mouse_down`
(previous default).

Release Notes:

- N/A
This commit is contained in:
Smit Barmase 2025-06-09 11:58:06 +05:30 committed by GitHub
parent ebea734515
commit c57a6263aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 76 additions and 49 deletions

View file

@ -910,7 +910,10 @@ enum SelectionDragState {
/// State when no drag related activity is detected.
None,
/// State when the mouse is down on a selection that is about to be dragged.
ReadyToDrag { selection: Selection<Anchor> },
ReadyToDrag {
selection: Selection<Anchor>,
click_position: gpui::Point<Pixels>,
},
/// State when the mouse is dragging the selection in the editor.
Dragging {
selection: Selection<Anchor>,
@ -10601,54 +10604,42 @@ impl Editor {
});
}
pub fn drop_selection(
pub fn move_selection_on_drop(
&mut self,
point_for_position: Option<PointForPosition>,
selection: &Selection<Anchor>,
target: DisplayPoint,
is_cut: bool,
window: &mut Window,
cx: &mut Context<Self>,
) -> bool {
if let Some(point_for_position) = point_for_position {
match self.selection_drag_state {
SelectionDragState::Dragging { ref selection, .. } => {
let snapshot = self.snapshot(window, cx);
let selection_display =
selection.map(|anchor| anchor.to_display_point(&snapshot));
if !point_for_position.intersects_selection(&selection_display) {
let point = point_for_position.previous_valid;
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
let mut edits = Vec::new();
let insert_point = display_map
.clip_point(point, Bias::Left)
.to_point(&display_map);
let text = buffer
.text_for_range(selection.start..selection.end)
.collect::<String>();
if is_cut {
edits.push(((selection.start..selection.end), String::new()));
}
let insert_anchor = buffer.anchor_before(insert_point);
edits.push(((insert_anchor..insert_anchor), text));
let last_edit_start = insert_anchor.bias_left(buffer);
let last_edit_end = insert_anchor.bias_right(buffer);
self.transact(window, cx, |this, window, cx| {
this.buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx);
});
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
s.select_anchor_ranges([last_edit_start..last_edit_end]);
});
});
self.selection_drag_state = SelectionDragState::None;
return true;
}
}
_ => {}
}
) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
let mut edits = Vec::new();
let insert_point = display_map
.clip_point(target, Bias::Left)
.to_point(&display_map);
let text = buffer
.text_for_range(selection.start..selection.end)
.collect::<String>();
if is_cut {
edits.push(((selection.start..selection.end), String::new()));
}
let insert_anchor = buffer.anchor_before(insert_point);
edits.push(((insert_anchor..insert_anchor), text));
let last_edit_start = insert_anchor.bias_left(buffer);
let last_edit_end = insert_anchor.bias_right(buffer);
self.transact(window, cx, |this, window, cx| {
this.buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx);
});
this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
s.select_anchor_ranges([last_edit_start..last_edit_end]);
});
});
}
pub fn clear_selection_drag_state(&mut self) {
self.selection_drag_state = SelectionDragState::None;
false
}
pub fn duplicate(

View file

@ -641,6 +641,7 @@ impl EditorElement {
if point_for_position.intersects_selection(&selection) {
editor.selection_drag_state = SelectionDragState::ReadyToDrag {
selection: newest_anchor.clone(),
click_position: event.position,
};
cx.stop_propagation();
return;
@ -832,9 +833,44 @@ impl EditorElement {
let pending_nonempty_selections = editor.has_pending_nonempty_selection();
let point_for_position = position_map.point_for_position(event.position);
let is_cut = !event.modifiers.control;
if editor.drop_selection(Some(point_for_position), is_cut, window, cx) {
return;
match editor.selection_drag_state {
SelectionDragState::ReadyToDrag {
selection: _,
ref click_position,
} => {
if event.position == *click_position {
editor.select(
SelectPhase::Begin {
position: point_for_position.previous_valid,
add: false,
click_count: 1, // ready to drag state only occurs on click count 1
},
window,
cx,
);
editor.selection_drag_state = SelectionDragState::None;
cx.stop_propagation();
return;
}
}
SelectionDragState::Dragging { ref selection, .. } => {
let snapshot = editor.snapshot(window, cx);
let selection_display = selection.map(|anchor| anchor.to_display_point(&snapshot));
if !point_for_position.intersects_selection(&selection_display) {
let is_cut = !event.modifiers.control;
editor.move_selection_on_drop(
&selection.clone(),
point_for_position.previous_valid,
is_cut,
window,
cx,
);
editor.selection_drag_state = SelectionDragState::None;
cx.stop_propagation();
return;
}
}
_ => {}
}
if end_selection {
@ -951,7 +987,7 @@ impl EditorElement {
drop_cursor.start = drop_anchor;
drop_cursor.end = drop_anchor;
}
SelectionDragState::ReadyToDrag { ref selection } => {
SelectionDragState::ReadyToDrag { ref selection, .. } => {
let drop_cursor = Selection {
id: post_inc(&mut editor.selections.next_selection_id),
start: drop_anchor,

View file

@ -915,8 +915,8 @@ impl Vim {
if mode == Mode::Normal || mode != last_mode {
self.current_tx.take();
self.current_anchor.take();
self.update_editor(window, cx, |_, editor, window, cx| {
editor.drop_selection(None, false, window, cx);
self.update_editor(window, cx, |_, editor, _, _| {
editor.clear_selection_drag_state();
});
}
Vim::take_forced_motion(cx);