Skip to main content

hh_cli/cli/tui/
terminal.rs

1use std::io::{self, Stdout};
2
3use crossterm::{
4    event::{
5        DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
6        KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
7    },
8    execute,
9    terminal::{
10        Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode,
11        enable_raw_mode,
12    },
13};
14use ratatui::{Terminal, backend::CrosstermBackend};
15
16pub type Tui = Terminal<CrosstermBackend<Stdout>>;
17
18fn keyboard_enhancement_flags() -> KeyboardEnhancementFlags {
19    KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
20}
21
22/// Setup terminal for TUI mode (raw mode + alternate screen + mouse capture)
23pub fn setup_terminal() -> io::Result<Tui> {
24    enable_raw_mode()?;
25    let mut stdout = io::stdout();
26    execute!(
27        stdout,
28        EnterAlternateScreen,
29        EnableMouseCapture,
30        EnableBracketedPaste,
31        Clear(ClearType::All)
32    )?;
33    let _ = execute!(
34        stdout,
35        PushKeyboardEnhancementFlags(keyboard_enhancement_flags())
36    );
37    let backend = CrosstermBackend::new(stdout);
38    Terminal::new(backend)
39}
40
41/// Restore terminal to original state
42pub fn restore_terminal(terminal: &mut Tui) -> io::Result<()> {
43    disable_raw_mode()?;
44    let _ = execute!(terminal.backend_mut(), PopKeyboardEnhancementFlags);
45    execute!(
46        terminal.backend_mut(),
47        LeaveAlternateScreen,
48        DisableMouseCapture,
49        DisableBracketedPaste
50    )?;
51    terminal.show_cursor()?;
52    Ok(())
53}
54
55/// RAII guard for terminal cleanup on panic
56pub struct TuiGuard {
57    terminal: Tui,
58}
59
60impl TuiGuard {
61    pub fn new(terminal: Tui) -> Self {
62        Self { terminal }
63    }
64
65    pub fn get(&mut self) -> &mut Tui {
66        &mut self.terminal
67    }
68}
69
70impl Drop for TuiGuard {
71    fn drop(&mut self) {
72        let _ = restore_terminal(&mut self.terminal);
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn keyboard_enhancements_enable_escape_disambiguation() {
82        assert!(
83            keyboard_enhancement_flags()
84                .contains(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES)
85        );
86    }
87}