aboutsummaryrefslogtreecommitdiff
path: root/src/client/shell/mouse.rs
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-11 22:58:01 +0300
committerGitHub <noreply@github.com>2026-09-11 22:58:01 +0300
commita0474ae770ed185f6531a3c69ab64c60bed2c670 (patch)
treeae924822c99383aff5d40f11eb89fa21104194c8 /src/client/shell/mouse.rs
parentb11ac859c738b50ca569d9ac998d5baeeb30d33d (diff)
fix: preserve whole-word selection while dragging (#3969)
refs #1836
Diffstat (limited to 'src/client/shell/mouse.rs')
-rw-r--r--src/client/shell/mouse.rs86
1 files changed, 59 insertions, 27 deletions
diff --git a/src/client/shell/mouse.rs b/src/client/shell/mouse.rs
index af324174..912e81ed 100644
--- a/src/client/shell/mouse.rs
+++ b/src/client/shell/mouse.rs
@@ -162,14 +162,44 @@ impl ClientShellState {
)
}
+ fn active_selection_pane(&self) -> Option<PaneHit> {
+ let pane_id = if let Some(gesture) = self.word_selection_gesture.as_ref() {
+ if gesture.released {
+ return None;
+ }
+ &gesture.pane_id
+ } else {
+ &self
+ .selection
+ .as_ref()
+ .filter(|selection| selection.is_in_progress())?
+ .pane_id
+ };
+ self.hits
+ .panes
+ .iter()
+ .find(|hit| &hit.pane_id == pane_id)
+ .cloned()
+ }
+
fn update_selection_cursor_with_metrics(
&mut self,
hit: &PaneHit,
column: u16,
row: u16,
metrics: Option<crate::pane::ScrollMetrics>,
+ outcome: &mut ClientShellInput,
) {
- if let Some(selection) = self.selection.as_mut() {
+ if self.word_selection_gesture.is_some() {
+ let viewport_row = row
+ .saturating_sub(hit.inner_rect.y)
+ .min(hit.inner_rect.height.saturating_sub(1));
+ let col = column
+ .saturating_sub(hit.inner_rect.x)
+ .min(hit.inner_rect.width.saturating_sub(1));
+ let absolute_row = crate::selection::absolute_row_for_viewport(viewport_row, metrics);
+ self.drag_word_selection((absolute_row, col), outcome);
+ } else if let Some(selection) = self.selection.as_mut() {
selection.drag(column, row, hit.inner_rect, metrics);
}
}
@@ -190,8 +220,11 @@ impl ClientShellState {
let (anchor_row, anchor_col) = selection.anchor_screen_pos(hit.inner_rect, metrics);
anchor_row != row || anchor_col != column
});
- let is_dragging = was_dragging || moved_from_anchor;
- self.update_selection_cursor_with_metrics(hit, column, row, metrics);
+ self.update_selection_cursor_with_metrics(hit, column, row, metrics, outcome);
+ let is_dragging = self
+ .word_selection_gesture
+ .as_ref()
+ .map_or(was_dragging || moved_from_anchor, |gesture| gesture.dragged);
if is_dragging {
if let Some(selection) = self.selection.as_mut() {
if selection.is_just_click() {
@@ -244,7 +277,7 @@ impl ClientShellState {
offset_from_bottom,
..metrics
};
- self.update_selection_cursor_with_metrics(hit, column, row, Some(projected));
+ self.update_selection_cursor_with_metrics(hit, column, row, Some(projected), outcome);
self.push_pane_scroll_offset(hit.pane_id.clone(), offset_from_bottom, outcome);
}
self.selection_autoscroll = Some(ClientSelectionAutoscroll {
@@ -268,20 +301,10 @@ impl ClientShellState {
if !matches!(
mouse.kind,
MouseEventKind::ScrollUp | MouseEventKind::ScrollDown
- ) || !self
- .selection
- .as_ref()
- .is_some_and(crate::selection::Selection::is_in_progress)
- {
+ ) {
return false;
}
- let Some(hit) = self.selection.as_ref().and_then(|selection| {
- self.hits
- .panes
- .iter()
- .find(|hit| hit.pane_id == selection.pane_id)
- .cloned()
- }) else {
+ let Some(hit) = self.active_selection_pane() else {
return false;
};
let Some(metrics) = self.selection_scroll_metrics(&hit) else {
@@ -307,6 +330,7 @@ impl ClientShellState {
mouse.column,
mouse.row,
Some(projected),
+ outcome,
);
self.push_pane_scroll_offset(hit.pane_id, offset_from_bottom, outcome);
outcome.repaint = true;
@@ -344,9 +368,15 @@ impl ClientShellState {
self.selection_autoscroll_deadline = None;
return outcome;
};
- if !self.selection.as_ref().is_some_and(|selection| {
- selection.pane_id == autoscroll.pane_id && selection.is_dragging()
- }) {
+ let dragging = self.word_selection_gesture.as_ref().map_or_else(
+ || {
+ self.selection.as_ref().is_some_and(|selection| {
+ selection.pane_id == autoscroll.pane_id && selection.is_dragging()
+ })
+ },
+ |gesture| gesture.pane_id == autoscroll.pane_id && gesture.dragged && !gesture.released,
+ );
+ if !dragging {
self.stop_selection_autoscroll();
return outcome;
}
@@ -388,6 +418,7 @@ impl ClientShellState {
autoscroll.last_mouse_column,
autoscroll.last_mouse_row,
Some(metrics),
+ &mut outcome,
);
self.push_pane_scroll_offset(autoscroll.pane_id.clone(), next_offset, &mut outcome);
self.selection_autoscroll = Some(autoscroll);
@@ -1659,13 +1690,7 @@ impl ClientShellState {
}
if mouse.kind == MouseEventKind::Drag(MouseButton::Left) {
- let selection_hit = self.selection.as_ref().and_then(|selection| {
- self.hits
- .panes
- .iter()
- .find(|hit| hit.pane_id == selection.pane_id)
- .cloned()
- });
+ let selection_hit = self.active_selection_pane();
if let Some(hit) = selection_hit {
self.update_selection_drag(&hit, mouse.column, mouse.row, outcome);
// Consume every motion, but do not rebuild a frame for every intermediate position.
@@ -1674,6 +1699,13 @@ impl ClientShellState {
return;
}
}
+ if mouse.kind == MouseEventKind::Up(MouseButton::Left)
+ && self.word_selection_gesture.is_some()
+ {
+ self.finish_word_selection(outcome);
+ outcome.repaint = true;
+ return;
+ }
if mouse.kind == MouseEventKind::Up(MouseButton::Left) && self.selection.is_some() {
self.stop_selection_autoscroll();
let copied = self
@@ -1854,7 +1886,7 @@ impl ClientShellState {
}
self.stop_selection_autoscroll();
self.selection_highlight_clear_deadline = None;
- self.pending_word_selection = None;
+ self.word_selection_gesture = None;
let previous_pane_click = self.last_pane_click.take();
self.workspace_press = None;
self.tab_press = None;