use std::io::{Stderr, Write};
use std::sync::atomic::{AtomicBool, Ordering};
use crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use crossterm::ExecutableCommand;
use ratatui::backend::CrosstermBackend;
use ratatui::Terminal;
use crate::core::error::{SsError, ERR_BUG};
static ACTIVE: AtomicBool = AtomicBool::new(false);
pub fn is_active() -> bool {
ACTIVE.load(Ordering::SeqCst)
}
pub fn restore_on_signal() {
if !ACTIVE.swap(false, Ordering::SeqCst) {
return;
}
let _ = disable_raw_mode();
let _ = std::io::stderr().execute(LeaveAlternateScreen);
}
pub struct TerminalGuard {
terminal: Terminal<CrosstermBackend<Stderr>>,
}
impl TerminalGuard {
pub fn enter() -> Result<Self, SsError> {
enable_raw_mode().map_err(term_err)?;
let mut stderr = std::io::stderr();
if let Err(e) = stderr.execute(EnterAlternateScreen) {
let _ = disable_raw_mode();
return Err(term_err(e));
}
let backend = CrosstermBackend::new(stderr);
let terminal = Terminal::new(backend).map_err(|e| {
let _ = std::io::stderr().execute(LeaveAlternateScreen);
let _ = disable_raw_mode();
term_err(e)
})?;
ACTIVE.store(true, Ordering::SeqCst);
Ok(Self { terminal })
}
pub fn terminal(&mut self) -> &mut Terminal<CrosstermBackend<Stderr>> {
&mut self.terminal
}
}
impl Drop for TerminalGuard {
fn drop(&mut self) {
ACTIVE.store(false, Ordering::SeqCst);
let _ = self.terminal.show_cursor();
let _ = self.terminal.backend_mut().flush();
let _ = std::io::stderr().execute(LeaveAlternateScreen);
let _ = disable_raw_mode();
}
}
fn term_err(e: std::io::Error) -> SsError {
SsError::new(ERR_BUG, format!("Failed to set up the terminal UI: {e}"))
.with_suggestion("Run in a real terminal, or use `saferskills search <query> --json`.")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn restore_on_signal_is_noop_when_inactive() {
ACTIVE.store(false, Ordering::SeqCst);
restore_on_signal();
assert!(!is_active());
}
}