editor: Add support for drag_and_drop_selection (#30671)

Closes #4958 

Release Notes:

- Added support for drag and drop text selection. It can be disabled by
setting `drag_and_drop_selection` to `false`.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
CharlesChen0823 2025-06-09 12:51:18 +08:00 committed by GitHub
parent b15aef4310
commit 4fe05530b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 214 additions and 16 deletions

View file

@ -906,6 +906,18 @@ struct InlineBlamePopover {
popover_state: InlineBlamePopoverState,
}
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> },
/// State when the mouse is dragging the selection in the editor.
Dragging {
selection: Selection<Anchor>,
drop_cursor: Selection<Anchor>,
},
}
/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
/// a breakpoint on them.
#[derive(Clone, Copy, Debug)]
@ -1091,6 +1103,8 @@ pub struct Editor {
hide_mouse_mode: HideMouseMode,
pub change_list: ChangeList,
inline_value_cache: InlineValueCache,
selection_drag_state: SelectionDragState,
drag_and_drop_selection_enabled: bool,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
@ -1985,6 +1999,8 @@ impl Editor {
.unwrap_or_default(),
change_list: ChangeList::new(),
mode,
selection_drag_state: SelectionDragState::None,
drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
};
if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
editor
@ -3530,6 +3546,7 @@ impl Editor {
pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
self.selection_mark_mode = false;
self.selection_drag_state = SelectionDragState::None;
if self.clear_expanded_diff_hunks(cx) {
cx.notify();
@ -10584,6 +10601,56 @@ impl Editor {
});
}
pub fn drop_selection(
&mut self,
point_for_position: Option<PointForPosition>,
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;
}
}
_ => {}
}
}
self.selection_drag_state = SelectionDragState::None;
false
}
pub fn duplicate(
&mut self,
upwards: bool,
@ -18987,6 +19054,7 @@ impl Editor {
self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
}
if old_cursor_shape != self.cursor_shape {