diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-11 22:58:01 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-11 22:58:01 +0300 |
| commit | a0474ae770ed185f6531a3c69ab64c60bed2c670 (patch) | |
| tree | ae924822c99383aff5d40f11eb89fa21104194c8 /src/client/shell/state.rs | |
| parent | b11ac859c738b50ca569d9ac998d5baeeb30d33d (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.rs | 90 |
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; |
