feat: handle crashes better for the terminal state

This commit is contained in:
Lukas Wölfer
2026-01-24 08:46:31 +01:00
parent e8e68682d8
commit 15c792fc21

View File

@@ -1,6 +1,7 @@
use std::io::{self, Read, Stdout}; use std::io::{self, Read};
use std::time::Duration; use std::time::Duration;
use crossterm::cursor::Show;
use crossterm::event::{self, Event, KeyCode}; use crossterm::event::{self, Event, KeyCode};
use crossterm::execute; use crossterm::execute;
use crossterm::terminal::{ use crossterm::terminal::{
@@ -12,6 +13,33 @@ use ratatui::layout::{Constraint, Direction, Layout};
use ratatui::style::Style; use ratatui::style::Style;
use ratatui::widgets::{Block, Borders, List, ListItem, ListState}; 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<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut input = String::new(); let mut input = String::new();
io::stdin().read_to_string(&mut input)?; io::stdin().read_to_string(&mut input)?;
@@ -27,6 +55,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
enable_raw_mode()?; enable_raw_mode()?;
let mut stdout = std::io::stdout(); let mut stdout = std::io::stdout();
execute!(stdout, EnterAlternateScreen)?; execute!(stdout, EnterAlternateScreen)?;
let mut _mode_guard = TerminalModeGuard::new();
let backend = CrosstermBackend::new(stdout); let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?; let mut terminal = Terminal::new(backend)?;
@@ -63,49 +94,41 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
f.render_stateful_widget(list, chunks[0], &mut state); f.render_stateful_widget(list, chunks[0], &mut state);
})?; })?;
// Event handling
if event::poll(Duration::from_millis(100))? { if event::poll(Duration::from_millis(100))? {
match event::read()? { match event::read()? {
Event::Key(key) => { Event::Key(key) => match key.code {
match key.code { KeyCode::Char('q') => break,
KeyCode::Char('q') => break, KeyCode::Up => {
KeyCode::Up => { if let Some(i) = state.selected()
if let Some(i) = state.selected() && i > 0
&& 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) =>
{ {
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) => { Event::Mouse(s) => {
if let event::MouseEventKind::Down(event::MouseButton::Left) = s.kind { if let event::MouseEventKind::Down(event::MouseButton::Left) = s.kind {
let area = terminal.size()?; let area = terminal.size()?;
@@ -122,9 +145,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
} }
disable_raw_mode()?; _mode_guard.cleanup();
let mut stdout: Stdout = std::io::stdout();
execute!(stdout, LeaveAlternateScreen)?;
terminal.show_cursor()?; terminal.show_cursor()?;
Ok(()) Ok(())