agent_core/tui/keys/exit.rs
1//! Exit handling for TUI applications.
2//!
3//! This module provides:
4//! - [`ExitHandler`] trait for agent cleanup on exit
5//! - [`ExitState`] enum for tracking exit confirmation state
6
7use std::time::Instant;
8
9/// Exit confirmation state for key handlers.
10///
11/// This tracks whether the user has initiated an exit sequence
12/// that requires confirmation (e.g., press Ctrl+D twice to quit).
13#[derive(Debug, Clone)]
14pub enum ExitState {
15 /// Normal operation, no exit pending.
16 Normal,
17 /// Awaiting exit confirmation within the timeout.
18 AwaitingConfirmation {
19 /// When the exit sequence was initiated.
20 since: Instant,
21 /// Timeout in seconds for confirmation.
22 timeout_secs: u64,
23 },
24}
25
26impl Default for ExitState {
27 fn default() -> Self {
28 Self::Normal
29 }
30}
31
32impl ExitState {
33 /// Create a new exit confirmation state.
34 pub fn awaiting_confirmation(timeout_secs: u64) -> Self {
35 Self::AwaitingConfirmation {
36 since: Instant::now(),
37 timeout_secs,
38 }
39 }
40
41 /// Check if the confirmation has expired.
42 pub fn is_expired(&self) -> bool {
43 match self {
44 Self::Normal => false,
45 Self::AwaitingConfirmation { since, timeout_secs } => {
46 since.elapsed().as_secs() >= *timeout_secs
47 }
48 }
49 }
50
51 /// Check if awaiting confirmation (and not expired).
52 pub fn is_awaiting(&self) -> bool {
53 match self {
54 Self::Normal => false,
55 Self::AwaitingConfirmation { since, timeout_secs } => {
56 since.elapsed().as_secs() < *timeout_secs
57 }
58 }
59 }
60
61 /// Reset to normal state.
62 pub fn reset(&mut self) {
63 *self = Self::Normal;
64 }
65}
66
67/// Optional hook for agent cleanup on exit.
68///
69/// Implement this trait to run cleanup code before the application exits.
70/// This is useful for saving session state, closing connections, or other
71/// cleanup tasks.
72///
73/// # Example
74///
75/// ```ignore
76/// struct SaveOnExitHandler {
77/// session_file: PathBuf,
78/// }
79///
80/// impl ExitHandler for SaveOnExitHandler {
81/// fn on_exit(&mut self) -> bool {
82/// // Save session state
83/// self.save_session();
84/// true // proceed with exit
85/// }
86/// }
87/// ```
88pub trait ExitHandler: Send + 'static {
89 /// Called when exit is confirmed.
90 ///
91 /// Return `true` to proceed with exit, or `false` to cancel the exit.
92 /// This allows handlers to veto the exit if needed (e.g., unsaved changes).
93 fn on_exit(&mut self) -> bool {
94 true
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use std::time::Duration;
102
103 #[test]
104 fn test_exit_state_default() {
105 let state = ExitState::default();
106 assert!(!state.is_awaiting());
107 assert!(!state.is_expired());
108 }
109
110 #[test]
111 fn test_exit_state_awaiting() {
112 let state = ExitState::awaiting_confirmation(2);
113 assert!(state.is_awaiting());
114 assert!(!state.is_expired());
115 }
116
117 #[test]
118 fn test_exit_state_expired() {
119 let state = ExitState::AwaitingConfirmation {
120 since: Instant::now() - Duration::from_secs(3),
121 timeout_secs: 2,
122 };
123 assert!(!state.is_awaiting());
124 assert!(state.is_expired());
125 }
126
127 #[test]
128 fn test_exit_state_reset() {
129 let mut state = ExitState::awaiting_confirmation(2);
130 assert!(state.is_awaiting());
131 state.reset();
132 assert!(!state.is_awaiting());
133 }
134}