diff options
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/api.rs | 8 | ||||
| -rw-r--r-- | src/app/mod.rs | 43 | ||||
| -rw-r--r-- | src/app/runtime.rs | 10 | ||||
| -rw-r--r-- | src/app/state.rs | 7 |
4 files changed, 68 insertions, 0 deletions
diff --git a/src/app/api.rs b/src/app/api.rs index c51a1bb0..2488ce40 100644 --- a/src/app/api.rs +++ b/src/app/api.rs @@ -16,6 +16,7 @@ impl App { pub(crate) fn handle_internal_event(&mut self, ev: AppEvent) { if let AppEvent::ClipboardWrite { content } = ev { crate::selection::write_osc52_bytes(&content); + self.show_clipboard_feedback(); return; } @@ -177,6 +178,13 @@ impl App { self.shutdown_detached_terminal_runtimes(); } + pub(crate) fn show_clipboard_feedback(&mut self) { + self.state.copy_feedback = Some(crate::app::state::CopyFeedback { + message: "copied to clipboard".to_string(), + }); + self.copy_feedback_deadline = Some(Instant::now() + super::COPY_FEEDBACK_DURATION); + } + fn restore_overlay_after_exit(&mut self, overlay: OverlayPaneState) { for temp_file in &overlay.temp_files { let _ = std::fs::remove_file(temp_file); diff --git a/src/app/mod.rs b/src/app/mod.rs index 534a81d8..1c221be8 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -38,6 +38,7 @@ 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); +const COPY_FEEDBACK_DURATION: Duration = Duration::from_secs(2); use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture}, @@ -93,6 +94,7 @@ pub struct App { pub(crate) last_terminal_size: Option<(u16, u16)>, pub(crate) config_diagnostic_deadline: Option<Instant>, pub(crate) toast_deadline: Option<Instant>, + pub(crate) copy_feedback_deadline: Option<Instant>, pub(crate) last_git_remote_status_refresh: Instant, pub(crate) git_refresh_in_flight: bool, pub(crate) last_sidebar_divider_click: Option<Instant>, @@ -467,6 +469,7 @@ impl App { update_dismissed: false, config_diagnostic, toast: None, + copy_feedback: None, outer_terminal_focus: None, prefix_code, prefix_mods, @@ -547,6 +550,7 @@ impl App { Self { config_diagnostic_deadline: None, toast_deadline: None, + copy_feedback_deadline: None, state, terminal_runtimes: restored_terminal_runtimes, event_tx, @@ -1465,6 +1469,45 @@ mod tests { } #[test] + fn clipboard_write_event_shows_feedback_toast() { + let mut app = test_app(); + + app.handle_internal_event(AppEvent::ClipboardWrite { + content: b"copied".to_vec(), + }); + + assert!(app.state.toast.is_none()); + let feedback = app.state.copy_feedback.as_ref().expect("copy feedback"); + assert_eq!(feedback.message, "copied to clipboard"); + assert!(app.copy_feedback_deadline.is_some()); + } + + #[test] + fn clipboard_feedback_does_not_replace_notification_toast() { + let mut app = test_app(); + app.state.toast = Some(crate::app::state::ToastNotification { + kind: crate::app::state::ToastKind::NeedsAttention, + title: "pi needs attention".to_string(), + context: "background ยท 2".to_string(), + target: None, + }); + let original_toast = app.state.toast.clone(); + + app.handle_internal_event(AppEvent::ClipboardWrite { + content: b"copied".to_vec(), + }); + + assert_eq!(app.state.toast, original_toast); + assert_eq!( + app.state + .copy_feedback + .as_ref() + .map(|feedback| feedback.message.as_str()), + Some("copied to clipboard") + ); + } + + #[test] fn internal_event_drain_limits_work_per_tick() { let mut app = test_app(); for i in 0..=APP_EVENT_DRAIN_LIMIT { diff --git a/src/app/runtime.rs b/src/app/runtime.rs index bbef7444..2e1f4d81 100644 --- a/src/app/runtime.rs +++ b/src/app/runtime.rs @@ -163,6 +163,15 @@ impl App { } if self + .copy_feedback_deadline + .is_some_and(|deadline| now >= deadline) + { + self.copy_feedback_deadline = None; + self.state.copy_feedback = None; + changed = true; + } + + if self .next_animation_tick .is_some_and(|deadline| now >= deadline) { @@ -431,6 +440,7 @@ impl App { include_resize_poll.then_some(self.next_resize_poll), self.config_diagnostic_deadline, self.toast_deadline, + self.copy_feedback_deadline, self.next_animation_tick, self.git_refresh_deadline(), self.next_auto_update_check, diff --git a/src/app/state.rs b/src/app/state.rs index 5e6aeafd..de1db052 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -1001,6 +1001,11 @@ pub struct ToastNotification { pub target: Option<ToastTarget>, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CopyFeedback { + pub message: String, +} + pub struct ReleaseNotesState { pub version: String, pub body: String, @@ -1103,6 +1108,7 @@ pub struct AppState { pub update_dismissed: bool, pub config_diagnostic: Option<String>, pub toast: Option<ToastNotification>, + pub copy_feedback: Option<CopyFeedback>, /// Last reported focus state for the outer terminal hosting herdr. /// None means unsupported or not yet reported, which preserves active-pane suppression. pub outer_terminal_focus: Option<bool>, @@ -1410,6 +1416,7 @@ impl AppState { update_dismissed: false, config_diagnostic: None, toast: None, + copy_feedback: None, outer_terminal_focus: None, prefix_code: KeyCode::Char('b'), prefix_mods: KeyModifiers::CONTROL, |
