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