diff --git a/src/main.rs b/src/main.rs index 8ca487f..43f96e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ -use std::io::{self, Read, Stdout}; +use std::io::{self, Read}; use std::time::Duration; +use crossterm::cursor::Show; use crossterm::event::{self, Event, KeyCode}; use crossterm::execute; use crossterm::terminal::{ @@ -12,6 +13,33 @@ use ratatui::layout::{Constraint, Direction, Layout}; use ratatui::style::Style; use ratatui::widgets::{Block, Borders, List, ListItem, ListState}; +// RAII guard to ensure terminal is restored on panic/unwind +struct TerminalModeGuard { + // track whether we still need to clean up + active: bool, +} + +impl TerminalModeGuard { + fn new() -> Self { + TerminalModeGuard { active: true } + } + + fn cleanup(&mut self) { + if !self.active { + return; + } + let _ = disable_raw_mode(); + let mut stdout = std::io::stdout(); + let _ = execute!(stdout, LeaveAlternateScreen, Show); + self.active = false; + } +} + +impl Drop for TerminalModeGuard { + fn drop(&mut self) { + self.cleanup(); + } +} fn main() -> Result<(), Box> { let mut input = String::new(); io::stdin().read_to_string(&mut input)?; @@ -27,6 +55,9 @@ fn main() -> Result<(), Box> { enable_raw_mode()?; let mut stdout = std::io::stdout(); execute!(stdout, EnterAlternateScreen)?; + + let mut _mode_guard = TerminalModeGuard::new(); + let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; @@ -63,49 +94,41 @@ fn main() -> Result<(), Box> { f.render_stateful_widget(list, chunks[0], &mut state); })?; - // Event handling if event::poll(Duration::from_millis(100))? { match event::read()? { - Event::Key(key) => { - match key.code { - KeyCode::Char('q') => break, - KeyCode::Up => { - if let Some(i) = state.selected() - && i > 0 - { - state.select(Some(i - 1)); - } - } - KeyCode::Down => { - if let Some(i) = state.selected() - && i + 1 < lines.len() - { - state.select(Some(i + 1)); - } - } - KeyCode::Char(' ') => { - if let Some(i) = state.selected() - && i < marked.len() - { - assert!(i < marked.len()); - if marked[i] { - marked[i] = false; - } else { - marked[i] = true; - // move cursor down - let next = lines.len().min(i + 1); - state.select(Some(next)); - } - } - } - KeyCode::Char('c') - if key.modifiers.contains(event::KeyModifiers::CONTROL) => + Event::Key(key) => match key.code { + KeyCode::Char('q') => break, + KeyCode::Up => { + if let Some(i) = state.selected() + && i > 0 { - break; + state.select(Some(i - 1)); } - _ => {} } - } + KeyCode::Down => { + if let Some(i) = state.selected() + && i + 1 < lines.len() + { + state.select(Some(i + 1)); + } + } + KeyCode::Char(' ') => { + if let Some(i) = state.selected() + && i < marked.len() + { + assert!(i < marked.len()); + if !marked[i] { + let next = lines.len().min(i + 1); + state.select(Some(next)); + } + marked[i] = !marked[i]; + } + } + KeyCode::Char('c') if key.modifiers.contains(event::KeyModifiers::CONTROL) => { + break; + } + _ => {} + }, Event::Mouse(s) => { if let event::MouseEventKind::Down(event::MouseButton::Left) = s.kind { let area = terminal.size()?; @@ -122,9 +145,7 @@ fn main() -> Result<(), Box> { } } - disable_raw_mode()?; - let mut stdout: Stdout = std::io::stdout(); - execute!(stdout, LeaveAlternateScreen)?; + _mode_guard.cleanup(); terminal.show_cursor()?; Ok(())