Skip to main content

sbom_tools/tui/viewmodel/
status.rs

1//! Status message management for TUI views.
2//!
3//! Provides a unified way to display temporary status messages
4//! across both diff and view TUI modes.
5
6use std::time::{Duration, Instant};
7
8/// Manages temporary status messages with optional auto-clear.
9///
10/// Status messages are displayed briefly to notify users of actions
11/// (e.g., "Exported to file.json", "Copied to clipboard").
12///
13/// # Example
14///
15/// ```ignore
16/// use crate::tui::viewmodel::StatusMessage;
17///
18/// let mut status = StatusMessage::new();
19///
20/// status.set("File exported successfully");
21/// assert!(status.message().is_some());
22///
23/// status.clear();
24/// assert!(status.message().is_none());
25/// ```
26#[derive(Debug, Clone, Default)]
27pub struct StatusMessage {
28    /// The current message (if any)
29    message: Option<String>,
30    /// When the message was set (for auto-clear)
31    set_at: Option<Instant>,
32    /// Auto-clear duration (None = no auto-clear)
33    auto_clear_after: Option<Duration>,
34}
35
36impl StatusMessage {
37    /// Create a new status message manager.
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    /// Create a status message manager with auto-clear after duration.
43    pub fn with_auto_clear(duration: Duration) -> Self {
44        Self {
45            message: None,
46            set_at: None,
47            auto_clear_after: Some(duration),
48        }
49    }
50
51    /// Set a status message.
52    pub fn set(&mut self, msg: impl Into<String>) {
53        self.message = Some(msg.into());
54        self.set_at = Some(Instant::now());
55    }
56
57    /// Clear the status message.
58    pub fn clear(&mut self) {
59        self.message = None;
60        self.set_at = None;
61    }
62
63    /// Get the current message (checking auto-clear if configured).
64    pub fn message(&mut self) -> Option<&str> {
65        // Check auto-clear
66        if let (Some(set_at), Some(duration)) = (self.set_at, self.auto_clear_after) {
67            if set_at.elapsed() >= duration {
68                self.message = None;
69                self.set_at = None;
70            }
71        }
72        self.message.as_deref()
73    }
74
75    /// Get the current message without checking auto-clear.
76    ///
77    /// Use this when you don't want to mutate state.
78    pub fn peek(&self) -> Option<&str> {
79        self.message.as_deref()
80    }
81
82    /// Check if there's an active message.
83    pub fn has_message(&self) -> bool {
84        self.message.is_some()
85    }
86
87    /// Get the message directly (for backwards compatibility).
88    ///
89    /// Returns a reference to the Option<String> without auto-clear logic.
90    pub fn as_option(&self) -> &Option<String> {
91        &self.message
92    }
93
94    /// Take the message, clearing it.
95    pub fn take(&mut self) -> Option<String> {
96        self.set_at = None;
97        self.message.take()
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use std::thread;
105
106    #[test]
107    fn test_status_message_set_clear() {
108        let mut status = StatusMessage::new();
109
110        assert!(!status.has_message());
111        assert!(status.peek().is_none());
112
113        status.set("Test message");
114        assert!(status.has_message());
115        assert_eq!(status.peek(), Some("Test message"));
116
117        status.clear();
118        assert!(!status.has_message());
119        assert!(status.peek().is_none());
120    }
121
122    #[test]
123    fn test_status_message_take() {
124        let mut status = StatusMessage::new();
125
126        status.set("Take me");
127        let taken = status.take();
128
129        assert_eq!(taken, Some("Take me".to_string()));
130        assert!(!status.has_message());
131    }
132
133    #[test]
134    fn test_status_message_auto_clear() {
135        let mut status = StatusMessage::with_auto_clear(Duration::from_millis(50));
136
137        status.set("Auto clear message");
138        assert!(status.message().is_some());
139
140        // Wait for auto-clear
141        thread::sleep(Duration::from_millis(60));
142        assert!(status.message().is_none());
143    }
144
145    #[test]
146    fn test_status_message_no_auto_clear_default() {
147        let mut status = StatusMessage::new();
148
149        status.set("No auto clear");
150        thread::sleep(Duration::from_millis(10));
151        assert!(status.message().is_some()); // Still there
152    }
153}