aboutsummaryrefslogtreecommitdiff
path: root/src/client/shell/state.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/state.rs
parentb11ac859c738b50ca569d9ac998d5baeeb30d33d (diff)
fix: preserve whole-word selection while dragging (#3969)
refs #1836
Diffstat (limited to 'src/client/shell/state.rs')
-rw-r--r--src/client/shell/state.rs90
1 files changed, 58 insertions, 32 deletions
diff --git a/src/client/shell/state.rs b/src/client/shell/state.rs
index 9507797d..e2e406a2 100644
--- a/src/client/shell/state.rs
+++ b/src/client/shell/state.rs
@@ -696,7 +696,6 @@ pub(super) enum PendingEndpointKind {
WordSelection {
pane_id: String,
absolute_row: u32,
- col: u16,
generation: u64,
},
PaneLinkActivate {
@@ -941,7 +940,7 @@ pub(crate) struct ClientShellState {
pub(super) selection_autoscroll: Option<ClientSelectionAutoscroll>,
pub(super) selection_autoscroll_deadline: Option<std::time::Instant>,
pub(super) selection_highlight_clear_deadline: Option<std::time::Instant>,
- pub(super) pending_word_selection: Option<u64>,
+ pub(super) word_selection_gesture: Option<ClientWordSelection>,
pub(super) word_selection_generation: u64,
pub(super) copy_mode: Option<ClientCopyModeState>,
pub(super) copy_session_generation: u64,
@@ -1099,7 +1098,7 @@ impl ClientShellState {
selection_autoscroll: None,
selection_autoscroll_deadline: None,
selection_highlight_clear_deadline: None,
- pending_word_selection: None,
+ word_selection_gesture: None,
word_selection_generation: 0,
copy_mode: None,
copy_session_generation: 0,
@@ -1288,7 +1287,7 @@ impl ClientShellState {
self.selection_autoscroll = None;
self.selection_autoscroll_deadline = None;
self.selection_highlight_clear_deadline = None;
- self.pending_word_selection = None;
+ self.word_selection_gesture = None;
self.copy_mode = None;
if self.mode == ClientShellMode::Copy {
self.mode = ClientShellMode::Terminal;
@@ -1431,18 +1430,32 @@ impl ClientShellState {
{
self.reveal_focused_tab = true;
}
- if self.selection.as_ref().is_some_and(|selection| {
- snapshot.focused_pane_id.as_deref() != Some(selection.pane_id.as_str())
- || !snapshot
- .panes
- .iter()
- .any(|pane| pane.pane_id == selection.pane_id)
- }) {
+ let selection_focus_lost = if let Some(gesture) = self.word_selection_gesture.as_mut() {
+ let focused_pane = snapshot.focused_pane_id.as_deref();
+ // Remember confirmed focus across intermediate snapshots with no
+ // focused pane, without rejecting the gesture's in-flight focus request.
+ gesture.focus_confirmed |= focused_pane == Some(gesture.pane_id.as_str());
+ !snapshot
+ .panes
+ .iter()
+ .any(|pane| pane.pane_id == gesture.pane_id)
+ || (gesture.focus_confirmed
+ && focused_pane.is_some_and(|pane_id| pane_id != gesture.pane_id))
+ } else {
+ self.selection.as_ref().is_some_and(|selection| {
+ snapshot.focused_pane_id.as_deref() != Some(selection.pane_id.as_str())
+ || !snapshot
+ .panes
+ .iter()
+ .any(|pane| pane.pane_id == selection.pane_id)
+ })
+ };
+ if selection_focus_lost {
self.selection = None;
self.selection_autoscroll = None;
self.selection_autoscroll_deadline = None;
self.selection_highlight_clear_deadline = None;
- self.pending_word_selection = None;
+ self.word_selection_gesture = None;
self.last_pane_click = None;
}
if let Some(copy_pane_id) = self
@@ -1667,7 +1680,7 @@ impl ClientShellState {
self.selection_autoscroll = None;
self.selection_autoscroll_deadline = None;
self.selection_highlight_clear_deadline = None;
- self.pending_word_selection = None;
+ self.word_selection_gesture = None;
self.copy_mode = None;
self.reset_copy_pipeline();
self.chrome_drag = None;
@@ -1685,38 +1698,51 @@ impl ClientShellState {
self.popup_pending = false;
self.popup_pending_deadline = None;
}
- let selection_content_changed = self.selection.as_ref().is_some_and(|selection| {
+ let selection_pane = match &self.word_selection_gesture {
+ Some(gesture) => Some(&gesture.pane_id),
+ None => self.selection.as_ref().map(|selection| &selection.pane_id),
+ };
+ let selection_content_changed = selection_pane.is_some_and(|pane_id| {
let Some(previous_surface) = self.pane_surface.as_ref() else {
return false;
};
let previous = previous_surface
.panes
.iter()
- .find(|pane| pane.pane_id == selection.pane_id);
- let next = surface
- .panes
- .iter()
- .find(|pane| pane.pane_id == selection.pane_id);
+ .find(|pane| &pane.pane_id == pane_id);
+ let next = surface.panes.iter().find(|pane| &pane.pane_id == pane_id);
let (Some(previous), Some(next)) = (previous, next) else {
return false;
};
- previous.inner_rect.width != next.inner_rect.width
+ if previous.inner_rect.width != next.inner_rect.width
|| previous.inner_rect.height != next.inner_rect.height
|| previous.alternate_screen_active != next.alternate_screen_active
- // Manual mouse selections track a live buffer range, not a content revision.
- || (self.config.copy_on_select
- && previous.content_revision != next.content_revision
- && (!previous.content_revision.is_multiple_of(2)
- || !next.content_revision.is_multiple_of(2)
- || !selection_cells_unchanged(
- selection,
- previous_surface,
- previous,
- &surface,
- next,
- )))
+ {
+ return true;
+ }
+ if previous.content_revision == next.content_revision {
+ return false;
+ }
+ match (&self.word_selection_gesture, &self.selection) {
+ // Word gestures cache boundaries outside the selected cells too.
+ (Some(_), _) => true,
+ (None, Some(selection)) => {
+ self.config.copy_on_select
+ && (!previous.content_revision.is_multiple_of(2)
+ || !next.content_revision.is_multiple_of(2)
+ || !selection_cells_unchanged(
+ selection,
+ previous_surface,
+ previous,
+ &surface,
+ next,
+ ))
+ }
+ (None, None) => false,
+ }
});
if selection_content_changed {
+ self.word_selection_gesture = None;
self.selection = None;
self.stop_selection_autoscroll();
self.selection_highlight_clear_deadline = None;