pub mod app;
mod event;
pub mod theme;
mod ui;
pub use app::App;
use anyhow::Result;
use crossterm::{
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::{Terminal, backend::CrosstermBackend};
use std::io::{self, Stdout};
use std::panic;
struct RawModeGuard;
impl Drop for RawModeGuard {
fn drop(&mut self) {
let _ = disable_raw_mode();
}
}
struct AltScreenGuard;
impl Drop for AltScreenGuard {
fn drop(&mut self) {
let _ = execute!(io::stdout(), LeaveAlternateScreen);
}
}
struct TerminalGuard {
terminal: Terminal<CrosstermBackend<Stdout>>,
}
impl TerminalGuard {
fn new() -> Result<Self> {
enable_raw_mode()?;
let raw_guard = RawModeGuard;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
let alt_guard = AltScreenGuard;
let backend = CrosstermBackend::new(stdout);
let terminal = Terminal::new(backend)?;
std::mem::forget(raw_guard);
std::mem::forget(alt_guard);
Ok(TerminalGuard { terminal })
}
}
impl Drop for TerminalGuard {
fn drop(&mut self) {
let _ = disable_raw_mode();
let _ = execute!(self.terminal.backend_mut(), LeaveAlternateScreen);
let _ = self.terminal.show_cursor();
}
}
pub fn run() -> Result<()> {
let original_hook = panic::take_hook();
panic::set_hook(Box::new(move |info| {
let _ = disable_raw_mode();
let _ = execute!(io::stdout(), LeaveAlternateScreen);
original_hook(info);
}));
let mut guard = TerminalGuard::new()?;
let mut app = App::new();
app.load_timeline()?;
loop {
guard.terminal.draw(|f| ui::render(f, &app))?;
event::handle_events(&mut app)?;
if app.should_quit {
break;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
struct RecordingGuard {
log: Arc<Mutex<Vec<&'static str>>>,
label: &'static str,
}
impl Drop for RecordingGuard {
fn drop(&mut self) {
self.log.lock().unwrap().push(self.label);
}
}
#[test]
fn setup_guards_clean_up_in_reverse_order_on_failure() {
let log: Arc<Mutex<Vec<&'static str>>> = Arc::new(Mutex::new(Vec::new()));
let result: Result<(), &str> = {
let _raw = RecordingGuard {
log: log.clone(),
label: "raw",
};
let _alt = RecordingGuard {
log: log.clone(),
label: "alt",
};
Err("setup failed")
};
assert!(result.is_err());
assert_eq!(*log.lock().unwrap(), vec!["alt", "raw"]);
}
#[test]
fn setup_guards_skipped_when_forgotten() {
let log: Arc<Mutex<Vec<&'static str>>> = Arc::new(Mutex::new(Vec::new()));
{
let raw = RecordingGuard {
log: log.clone(),
label: "raw",
};
let alt = RecordingGuard {
log: log.clone(),
label: "alt",
};
std::mem::forget(raw);
std::mem::forget(alt);
}
assert!(log.lock().unwrap().is_empty());
}
}