diff options
| author | Zack Drach <zackdrach@gmail.com> | 2026-05-26 03:40:29 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-26 13:40:29 +0300 |
| commit | 6c4a947244b7be05496f9d3bd5ad2d00a69cca03 (patch) | |
| tree | 421b4a009e2a201c9be65806fd0e13fe9899c73a /src/app/mod.rs | |
| parent | 33e71ed2f50e5ca8950029f89f41e0ec135b87c0 (diff) | |
Add tmux-style double-click word copy (#296)
* feat: add tmux-style double-click copy
Double-clicking terminal output now selects and copies the token under the cursor, similar to tmux. The copied token briefly stays highlighted as feedback, and token detection handles common shell output like paths, URLs, variables, CJK text, and surrounding punctuation.
Mouse events are still forwarded unchanged when the child terminal has mouse reporting enabled.
refs #142
* fix: tighten double-click copy
refs #142
---------
Co-authored-by: Can Celik <ogulcancelik@gmail.com>
Diffstat (limited to 'src/app/mod.rs')
| -rw-r--r-- | src/app/mod.rs | 23 |
1 files changed, 23 insertions, 0 deletions
diff --git a/src/app/mod.rs b/src/app/mod.rs index 0b4c792c..e4ec2751 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -36,6 +36,8 @@ const GIT_REMOTE_STATUS_REFRESH_INTERVAL: Duration = Duration::from_millis(1500) const AUTO_UPDATE_CHECK_INTERVAL: Duration = Duration::from_secs(30 * 60); const SESSION_SAVE_DEBOUNCE: Duration = Duration::from_secs(5); const SIDEBAR_DOUBLE_CLICK_WINDOW: Duration = Duration::from_millis(350); +const PANE_DOUBLE_CLICK_WINDOW: Duration = Duration::from_millis(350); +const PANE_COPY_HIGHLIGHT_DURATION: Duration = Duration::from_millis(500); use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture}, @@ -61,6 +63,23 @@ pub(crate) struct OverlayPaneState { temp_files: Vec<std::path::PathBuf>, } +#[derive(Debug, Clone, Copy)] +pub(crate) struct PaneClickState { + pane_id: crate::layout::PaneId, + viewport_row: u16, + col: u16, + at: Instant, +} + +impl PaneClickState { + fn is_double_click_for(self, next: Self) -> bool { + self.pane_id == next.pane_id + && next.at.duration_since(self.at) <= PANE_DOUBLE_CLICK_WINDOW + && self.viewport_row.abs_diff(next.viewport_row) <= 1 + && self.col.abs_diff(next.col) <= 1 + } +} + pub struct App { pub state: AppState, pub(crate) terminal_runtimes: crate::terminal::TerminalRuntimeRegistry, @@ -77,10 +96,12 @@ pub struct App { pub(crate) last_git_remote_status_refresh: Instant, pub(crate) git_refresh_in_flight: bool, pub(crate) last_sidebar_divider_click: Option<Instant>, + pub(crate) last_pane_click: Option<PaneClickState>, pub(crate) next_resize_poll: Instant, pub(crate) next_animation_tick: Option<Instant>, pub(crate) next_auto_update_check: Option<Instant>, pub(crate) selection_autoscroll_deadline: Option<Instant>, + pub(crate) selection_highlight_clear_deadline: Option<Instant>, pub(crate) session_save_deadline: Option<Instant>, pub(crate) persist_pane_history: bool, pub(crate) last_render_at: Option<Instant>, @@ -532,12 +553,14 @@ impl App { last_git_remote_status_refresh: Instant::now() - GIT_REMOTE_STATUS_REFRESH_INTERVAL, git_refresh_in_flight: false, last_sidebar_divider_click: None, + last_pane_click: None, next_resize_poll: Instant::now() + RESIZE_POLL_INTERVAL, next_animation_tick: None, next_auto_update_check: auto_updates_enabled(no_session) .then_some(Instant::now() + AUTO_UPDATE_CHECK_INTERVAL), session_save_deadline: None, selection_autoscroll_deadline: None, + selection_highlight_clear_deadline: None, persist_pane_history: config.experimental.pane_history, last_render_at: None, suppressed_repeat_keys: HashSet::new(), |
