aboutsummaryrefslogtreecommitdiff
path: root/src/app/mod.rs
diff options
context:
space:
mode:
authorZack Drach <zackdrach@gmail.com>2026-05-26 03:40:29 -0700
committerGitHub <noreply@github.com>2026-05-26 13:40:29 +0300
commit6c4a947244b7be05496f9d3bd5ad2d00a69cca03 (patch)
tree421b4a009e2a201c9be65806fd0e13fe9899c73a /src/app/mod.rs
parent33e71ed2f50e5ca8950029f89f41e0ec135b87c0 (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.rs23
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(),