Skip to main content

tess/
terminal.rs

1use std::io;
2use std::sync::Arc;
3use std::sync::atomic::AtomicBool;
4
5use crossterm::cursor::{Hide, Show};
6use crossterm::terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen};
7
8/// RAII guard that enables raw mode + alt screen on construction and restores
9/// the terminal on drop (including during panic unwind).
10pub struct TerminalGuard;
11
12impl TerminalGuard {
13    pub fn enter() -> io::Result<Self> {
14        enable_raw_mode()?;
15        crossterm::execute!(io::stdout(), EnterAlternateScreen, Hide)?;
16        Ok(TerminalGuard)
17    }
18}
19
20impl Drop for TerminalGuard {
21    fn drop(&mut self) {
22        let _ = crossterm::execute!(io::stdout(), Show, LeaveAlternateScreen);
23        let _ = disable_raw_mode();
24    }
25}
26
27/// Restore terminal manually (for panic hook). Idempotent and best-effort.
28pub fn restore_terminal_best_effort() {
29    let _ = crossterm::execute!(io::stdout(), Show, LeaveAlternateScreen);
30    let _ = disable_raw_mode();
31}
32
33pub fn install_panic_hook() {
34    let prev = std::panic::take_hook();
35    std::panic::set_hook(Box::new(move |info| {
36        restore_terminal_best_effort();
37        prev(info);
38    }));
39}
40
41/// Returns a flag that becomes `true` on SIGTERM or SIGHUP.
42pub fn install_signal_flag() -> Arc<AtomicBool> {
43    let flag = Arc::new(AtomicBool::new(false));
44    let _ = signal_hook::flag::register(signal_hook::consts::SIGTERM, Arc::clone(&flag));
45    let _ = signal_hook::flag::register(signal_hook::consts::SIGHUP, Arc::clone(&flag));
46    flag
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use std::sync::atomic::Ordering;
53
54    #[test]
55    fn signal_flag_starts_false() {
56        let f = install_signal_flag();
57        assert!(!f.load(Ordering::SeqCst));
58    }
59
60    #[test]
61    fn restore_is_idempotent() {
62        // Should not panic when raw mode was never enabled.
63        restore_terminal_best_effort();
64        restore_terminal_best_effort();
65    }
66}