use std::{
io::{self, Stdout},
ops::{Deref, DerefMut},
sync::Once,
};
use anyhow::{Context, Result};
use crossterm::{
cursor::Show,
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::{Terminal, backend::CrosstermBackend};
static PANIC_HOOK: Once = Once::new();
pub struct TerminalGuard {
terminal: Terminal<CrosstermBackend<Stdout>>,
}
impl TerminalGuard {
pub fn new() -> Result<Self> {
enable_raw_mode().context("failed to enable raw mode")?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen).context("failed to enter alternate screen")?;
let backend = CrosstermBackend::new(stdout);
let terminal = Terminal::new(backend).context("failed to initialize terminal")?;
install_panic_hook();
Ok(Self { terminal })
}
}
impl Drop for TerminalGuard {
fn drop(&mut self) {
let _ = self.terminal.show_cursor();
restore_terminal();
}
}
impl Deref for TerminalGuard {
type Target = Terminal<CrosstermBackend<Stdout>>;
fn deref(&self) -> &Self::Target {
&self.terminal
}
}
impl DerefMut for TerminalGuard {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.terminal
}
}
fn install_panic_hook() {
PANIC_HOOK.call_once(|| {
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| {
restore_terminal();
previous(panic_info);
}));
});
}
fn restore_terminal() {
let _ = disable_raw_mode();
let mut stdout = io::stdout();
let _ = execute!(stdout, LeaveAlternateScreen, Show);
}