use crossterm::{
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use std::io::{self, Write};
use tracing::{error, warn};
pub struct RawModeGuard {
enabled: bool,
}
impl RawModeGuard {
pub fn new() -> io::Result<Self> {
enable_raw_mode()?;
Ok(Self { enabled: true })
}
}
impl Drop for RawModeGuard {
fn drop(&mut self) {
if self.enabled {
if let Err(e) = disable_raw_mode() {
error!("Failed to disable raw mode: {}", e);
let _ = writeln!(io::stderr(), "\r\nWarning: Failed to restore terminal mode");
} else {
self.enabled = false;
}
}
}
}
pub struct AlternateScreenGuard {
stdout: io::Stdout,
active: bool,
}
impl AlternateScreenGuard {
pub fn new() -> io::Result<Self> {
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
Ok(Self {
stdout,
active: true,
})
}
pub fn stdout_mut(&mut self) -> &mut io::Stdout {
&mut self.stdout
}
}
impl Drop for AlternateScreenGuard {
fn drop(&mut self) {
if self.active {
if let Err(e) = execute!(self.stdout, LeaveAlternateScreen) {
error!("Failed to leave alternate screen: {}", e);
let _ = writeln!(io::stderr(), "\r\nWarning: Failed to restore screen");
} else {
self.active = false;
}
let _ = execute!(self.stdout, crossterm::cursor::Show);
}
}
}
pub struct TerminalGuard {
_raw_mode: RawModeGuard,
alternate_screen: AlternateScreenGuard,
}
impl TerminalGuard {
pub fn new() -> io::Result<Self> {
let raw_mode = RawModeGuard::new()?;
let alternate_screen = AlternateScreenGuard::new()?;
Ok(Self {
_raw_mode: raw_mode,
alternate_screen,
})
}
pub fn stdout_mut(&mut self) -> &mut io::Stdout {
self.alternate_screen.stdout_mut()
}
}
impl Drop for TerminalGuard {
fn drop(&mut self) {
if std::thread::panicking() {
warn!("Terminal cleanup during panic");
let _ = execute!(io::stdout(), crossterm::cursor::Show);
let _ = disable_raw_mode();
let _ = write!(io::stderr(), "\x1b[0m\x1b[?25h");
let _ = io::stderr().flush();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_guard_creation() {
{
let _guard = RawModeGuard { enabled: false };
}
{
let _guard = AlternateScreenGuard {
stdout: io::stdout(),
active: false,
};
}
}
#[test]
fn test_panic_handling() {
std::panic::catch_unwind(|| {
let _guard = RawModeGuard { enabled: false };
panic!("Test panic");
})
.unwrap_err();
}
}