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
8pub struct TerminalGuard {
11 mouse: bool,
12}
13
14impl TerminalGuard {
15 pub fn enter(mouse: bool) -> io::Result<Self> {
16 enable_raw_mode()?;
17 crossterm::execute!(io::stdout(), EnterAlternateScreen, Hide)?;
18 if mouse {
19 crossterm::execute!(io::stdout(), crossterm::event::EnableMouseCapture)?;
20 }
21 Ok(TerminalGuard { mouse })
22 }
23}
24
25impl Drop for TerminalGuard {
26 fn drop(&mut self) {
27 if self.mouse {
28 let _ = crossterm::execute!(io::stdout(), crossterm::event::DisableMouseCapture);
29 }
30 let _ = crossterm::execute!(io::stdout(), Show, LeaveAlternateScreen);
31 let _ = disable_raw_mode();
32 }
33}
34
35pub fn restore_terminal_best_effort() {
37 let _ = crossterm::execute!(io::stdout(), Show, LeaveAlternateScreen);
38 let _ = disable_raw_mode();
39}
40
41pub fn install_panic_hook() {
42 let prev = std::panic::take_hook();
43 std::panic::set_hook(Box::new(move |info| {
44 restore_terminal_best_effort();
45 prev(info);
46 }));
47}
48
49pub fn install_signal_flag() -> Arc<AtomicBool> {
51 let flag = Arc::new(AtomicBool::new(false));
52 let _ = signal_hook::flag::register(signal_hook::consts::SIGTERM, Arc::clone(&flag));
53 let _ = signal_hook::flag::register(signal_hook::consts::SIGHUP, Arc::clone(&flag));
54 flag
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use std::sync::atomic::Ordering;
61
62 #[test]
63 fn signal_flag_starts_false() {
64 let f = install_signal_flag();
65 assert!(!f.load(Ordering::SeqCst));
66 }
67
68 #[test]
69 fn restore_is_idempotent() {
70 restore_terminal_best_effort();
72 restore_terminal_best_effort();
73 }
74}