Skip to content

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

add color_eyre to 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<()>.

main.rs
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.

tui.rs
/// Initialize the terminal
pub 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:

main.rs
use color_eyre::eyre::WrapErr;

Call wrap_err from methods that can fail with an error.

main.rs
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
main.rs
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(())
}
tui.rs
use std::io::{self, stdout, Stdout};
use ratatui::{
backend::CrosstermBackend,
crossterm::{
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
},
Terminal,
};
/// Initialize the terminal
pub 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 state
pub fn restore() -> io::Result<()> {
execute!(stdout(), LeaveAlternateScreen)?;
disable_raw_mode()?;
Ok(())
}

Panic

Panic

With RUST_BACKTRACE=full:

Panic Full

Error

Error

With RUST_BACKTRACE=full:

Error Full

Normal exit

Quit

Further Steps

See the color_eyre docs and examples for more advanced setups. E.g.: