Use `color_eyre` with Ratatui
The color_eyre
crate provides error report handlers for panics and errors. It displays the
reports formatted and in color. To use these handlers, a Ratatui app needs to restore the terminal
before displaying the errors.
Installation
First add the crate to your Cargo.toml
cargo add color_eyre
Call the color_eyre::install
method from your main function and update the return value to
color_eyre::Result<()>
.
fn main() -> color_eyre::Result<()> { color_eyre::install()?;9 collapsed lines
let terminal = tui::init()?; let result = run(terminal).wrap_err("run failed"); if let Err(err) = tui::restore() { eprintln!( "failed to restore terminal. Run `reset` or restart your terminal to recover: {}", err ); } result}
In your terminal initialization function, add some new code that replaces rusts default panic handler with one that restores the terminal before displaying the panic details. This will be used by both panics and unhandled errors that fall through to the end of the program.
/// Initialize the terminalpub fn init() -> io::Result<ratatui::Terminal<CrosstermBackend<Stdout>>> { execute!(stdout(), EnterAlternateScreen)?; enable_raw_mode()?; set_panic_hook(); Terminal::new(CrosstermBackend::new(stdout()))}
fn set_panic_hook() { let hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |panic_info| { let _ = restore(); // ignore any errors as we are already failing hook(panic_info); }));}
Usage
In your application, wrap errors with extra context as needed:
Add the following import:
use color_eyre::eyre::WrapErr;
Call wrap_err from methods that can fail with an error.
fn main() -> color_eyre::Result<()> { color_eyre::install()?; let terminal = tui::init()?; let result = run(terminal).wrap_err("run failed"); if let Err(err) = tui::restore() { eprintln!( "failed to restore terminal. Run `reset` or restart your terminal to recover: {}", err ); } result}
Demo
Full code
use std::panic;
use color_eyre::eyre::WrapErr;use color_eyre::eyre::bail;use ratatui::{ backend::Backend, crossterm::event::{self, Event, KeyCode, KeyEvent}, widgets::Paragraph, Terminal,};
mod tui;
fn main() -> color_eyre::Result<()> { color_eyre::install()?; let terminal = tui::init()?; let result = run(terminal).wrap_err("run failed"); if let Err(err) = tui::restore() { eprintln!( "failed to restore terminal. Run `reset` or restart your terminal to recover: {}", err ); } result}
fn run(mut terminal: Terminal<impl Backend>) -> color_eyre::Result<()> { loop { terminal.draw(|frame| { let message = "Press <Q> to quit, <P> to panic, or <E> to error"; frame.render_widget(Paragraph::new(message), frame.area()); })?; match event::read()? { Event::Key(KeyEvent { code: KeyCode::Char('q'), .. }) => break, Event::Key(KeyEvent { code: KeyCode::Char('p'), .. }) => panic!("User triggered panic"), Event::Key(KeyEvent { code: KeyCode::Char('e'), .. }) => bail!("user triggered error"), _ => {} } } Ok(())}
use std::io::{self, stdout, Stdout};
use ratatui::{ backend::CrosstermBackend, crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }, Terminal,};
/// Initialize the terminalpub fn init() -> io::Result<ratatui::Terminal<CrosstermBackend<Stdout>>> { execute!(stdout(), EnterAlternateScreen)?; enable_raw_mode()?; set_panic_hook(); Terminal::new(CrosstermBackend::new(stdout()))}
fn set_panic_hook() { let hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |panic_info| { let _ = restore(); // ignore any errors as we are already failing hook(panic_info); }));}
/// Restore the terminal to its original statepub fn restore() -> io::Result<()> { execute!(stdout(), LeaveAlternateScreen)?; disable_raw_mode()?; Ok(())}
Panic
With RUST_BACKTRACE=full
:
Error
With RUST_BACKTRACE=full
:
Normal exit
Further Steps
See the color_eyre
docs and examples for more advanced setups. E.g.: