Skip to main content

reratui_runtime/
managed_terminal.rs

1//! Terminal management for Reratui runtime
2//!
3//! This module provides terminal initialization, cleanup, and management
4//! functionality for TUI applications.
5
6use ratatui::crossterm::{
7    event::{DisableMouseCapture, EnableMouseCapture},
8    execute,
9    terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
10};
11use ratatui::{Terminal, backend::CrosstermBackend};
12use std::io::{self, Stdout};
13
14/// A managed terminal instance that handles setup and cleanup
15pub struct ManagedTerminal {
16    terminal: Terminal<CrosstermBackend<Stdout>>,
17}
18
19impl ManagedTerminal {
20    /// Initialize a new terminal with proper setup
21    pub fn new() -> io::Result<Self> {
22        // Enable raw mode for input handling
23        enable_raw_mode()?;
24
25        // Get stdout
26        let mut stdout = io::stdout();
27
28        // Enter alternate screen to preserve terminal state
29        execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
30
31        // Create the terminal backend
32        let backend = CrosstermBackend::new(stdout);
33        let terminal = Terminal::new(backend)?;
34
35        Ok(Self { terminal })
36    }
37
38    /// Get a mutable reference to the terminal
39    pub fn terminal_mut(&mut self) -> &mut Terminal<CrosstermBackend<Stdout>> {
40        &mut self.terminal
41    }
42
43    /// Get the terminal size
44    pub fn size(&self) -> io::Result<ratatui::layout::Rect> {
45        self.terminal
46            .size()
47            .map(|size| ratatui::layout::Rect::new(0, 0, size.width, size.height))
48    }
49
50    /// Clear the terminal
51    pub fn clear(&mut self) -> io::Result<()> {
52        self.terminal.clear()
53    }
54
55    /// Draw the terminal with a closure
56    pub fn draw<F>(&mut self, f: F) -> io::Result<()>
57    where
58        F: FnOnce(&mut ratatui::Frame),
59    {
60        self.terminal.draw(f)?;
61        Ok(())
62    }
63}
64
65impl Drop for ManagedTerminal {
66    /// Cleanup terminal state when dropped
67    fn drop(&mut self) {
68        // Restore terminal state
69        let _ = disable_raw_mode();
70        let _ = execute!(
71            self.terminal.backend_mut(),
72            LeaveAlternateScreen,
73            DisableMouseCapture
74        );
75        let _ = self.terminal.show_cursor();
76    }
77}
78
79/// Initialize terminal for TUI applications
80pub fn setup_terminal() -> io::Result<ManagedTerminal> {
81    ManagedTerminal::new()
82}
83
84/// Restore terminal to original state
85pub fn restore_terminal() -> io::Result<()> {
86    // Disable raw mode
87    disable_raw_mode()?;
88
89    // Leave alternate screen and disable mouse capture
90    execute!(std::io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?;
91
92    Ok(())
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use std::sync::{Arc, Mutex};
99    use std::thread;
100    use std::time::Duration;
101
102    /// Test that ManagedTerminal can be created and dropped safely
103    #[test]
104    fn test_managed_terminal_creation_and_cleanup() {
105        // This test verifies basic terminal lifecycle
106        // Note: In CI environments, this might fail due to lack of TTY
107        // but it's useful for local development
108
109        // We can't easily test actual terminal setup in unit tests
110        // since it requires a real terminal, so we test the API structure
111        // Placeholder - actual terminal tests need integration environment
112    }
113
114    /// Test terminal size conversion from Size to Rect
115    #[test]
116    fn test_size_conversion() {
117        // Test the logic we use in the size() method
118        let width = 80u16;
119        let height = 24u16;
120
121        let rect = ratatui::layout::Rect::new(0, 0, width, height);
122
123        assert_eq!(rect.x, 0);
124        assert_eq!(rect.y, 0);
125        assert_eq!(rect.width, width);
126        assert_eq!(rect.height, height);
127    }
128
129    /// Test that setup_terminal function exists and has correct signature
130    #[test]
131    fn test_setup_terminal_signature() {
132        // Verify the function signature compiles
133        let _setup_fn: fn() -> io::Result<ManagedTerminal> = setup_terminal;
134    }
135
136    /// Test that restore_terminal function exists and has correct signature
137    #[test]
138    fn test_restore_terminal_signature() {
139        // Verify the function signature compiles
140        let _restore_fn: fn() -> io::Result<()> = restore_terminal;
141    }
142
143    /// Test ManagedTerminal method signatures
144    #[test]
145    fn test_managed_terminal_methods() {
146        // Test that all expected methods exist with correct signatures
147        // This is a compile-time test
148
149        // We can't actually create a ManagedTerminal in tests without a TTY,
150        // but we can verify the method signatures exist
151        fn _test_methods(mut terminal: ManagedTerminal) -> io::Result<()> {
152            let _size = terminal.size()?;
153            terminal.clear()?;
154            terminal.draw(|_frame| {})?;
155            let _term_ref = terminal.terminal_mut();
156            Ok(())
157        }
158
159        // Compilation success means methods exist
160    }
161
162    /// Test error handling scenarios
163    #[test]
164    fn test_error_handling() {
165        // Test that our error types are compatible
166        let _io_error: io::Error = io::Error::other("test");
167
168        // Verify our functions return the expected error types
169        fn _test_error_types() {
170            let _: io::Result<ManagedTerminal> = setup_terminal();
171            let _: io::Result<()> = restore_terminal();
172        }
173    }
174
175    /// Test thread safety considerations
176    #[test]
177    fn test_thread_safety() {
178        // Test that our types can be used safely across threads
179        let counter = Arc::new(Mutex::new(0));
180        let counter_clone = Arc::clone(&counter);
181
182        let handle = thread::spawn(move || {
183            let mut num = counter_clone.lock().unwrap();
184            *num += 1;
185
186            // Test that our functions can be called from different threads
187            let _setup_fn = setup_terminal;
188            let _restore_fn = restore_terminal;
189        });
190
191        handle.join().unwrap();
192        assert_eq!(*counter.lock().unwrap(), 1);
193    }
194
195    /// Test Drop implementation behavior
196    #[test]
197    fn test_drop_implementation() {
198        // Verify that ManagedTerminal has drop behavior
199        // This is important for RAII cleanup
200        use std::mem;
201
202        // Test that ManagedTerminal needs drop (has custom Drop implementation)
203        assert!(mem::needs_drop::<ManagedTerminal>());
204
205        // We can't test the actual Drop behavior without a real terminal,
206        // but we can verify the type requires cleanup
207    }
208
209    /// Performance test for rapid terminal operations
210    #[test]
211    fn test_performance_characteristics() {
212        // Test that our terminal operations complete in reasonable time
213        let start = std::time::Instant::now();
214
215        // Simulate the work our functions would do
216        for _ in 0..1000 {
217            let _rect = ratatui::layout::Rect::new(0, 0, 80, 24);
218        }
219
220        let duration = start.elapsed();
221
222        // Should complete very quickly since it's just struct creation
223        assert!(duration < Duration::from_millis(10));
224    }
225
226    /// Test memory usage patterns
227    #[test]
228    fn test_memory_usage() {
229        // Test that our structs have reasonable memory footprint
230        use std::mem;
231
232        // ManagedTerminal should be relatively small
233        let terminal_size = mem::size_of::<ManagedTerminal>();
234
235        // Should be reasonable size (less than 1KB)
236        assert!(terminal_size < 1024);
237
238        // Test that Rect creation is efficient
239        let rect_size = mem::size_of::<ratatui::layout::Rect>();
240        assert!(rect_size <= 8); // Should be just 4 u16s
241    }
242}