Skip to main content

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}