Skip to content

How to use `color_eyre` with Ratatui

Full source code for this how to article is available at: https://github.com/ratatui-org/ratatui-website/tree/main/code/how-to-color_eyre/

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

Add the following imports to main.rs

main.rs
use color_eyre::eyre;

Create a new function install_hooks() which will ensure your app calls tui::restore() before exiting with a panic or an error.

main.rs
/// This replaces the standard color_eyre panic and error hooks with hooks that
/// restore the terminal before printing the panic or error.
pub fn install_hooks() -> color_eyre::Result<()> {
// add any extra configuration you need to the hook builder
let hook_builder = color_eyre::config::HookBuilder::default();
let (panic_hook, eyre_hook) = hook_builder.into_hooks();
// convert from a color_eyre PanicHook to a standard panic hook
let panic_hook = panic_hook.into_panic_hook();
panic::set_hook(Box::new(move |panic_info| {
tui::restore().unwrap();
panic_hook(panic_info);
}));
// convert from a color_eyre EyreHook to a eyre ErrorHook
let eyre_hook = eyre_hook.into_eyre_hook();
eyre::set_hook(Box::new(move |error| {
tui::restore().unwrap();
eyre_hook(error)
}))?;
Ok(())
}

This example assumes that you have a tui module in your app with init and restore functions

Example tui.rs module
tui.rs
use std::io::{self, stdout};
use crossterm::{
terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen,
LeaveAlternateScreen,
},
ExecutableCommand,
};
use ratatui::prelude::*;
pub fn init() -> io::Result<Terminal<impl Backend>> {
stdout().execute(EnterAlternateScreen)?;
enable_raw_mode()?;
Terminal::new(CrosstermBackend::new(stdout()))
}
pub fn restore() -> io::Result<()> {
stdout().execute(LeaveAlternateScreen)?;
disable_raw_mode()?;
Ok(())
}

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<()> {
install_hooks()?;
let terminal = tui::init()?;
run(terminal).wrap_err("run failed")?;
tui::restore()?;
println!("user triggered quit");
Ok(())
}

Demo

Full code
main.rs
use std::panic;
use color_eyre::eyre;
use color_eyre::eyre::WrapErr;
use color_eyre::eyre::bail;
use crossterm::event::{self, Event, KeyCode, KeyEvent};
use ratatui::{prelude::*, widgets::*};
mod tui;
fn main() -> color_eyre::Result<()> {
install_hooks()?;
let terminal = tui::init()?;
run(terminal).wrap_err("run failed")?;
tui::restore()?;
println!("user triggered quit");
Ok(())
}
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.size());
})?;
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(())
}
/// This replaces the standard color_eyre panic and error hooks with hooks that
/// restore the terminal before printing the panic or error.
pub fn install_hooks() -> color_eyre::Result<()> {
// add any extra configuration you need to the hook builder
let hook_builder = color_eyre::config::HookBuilder::default();
let (panic_hook, eyre_hook) = hook_builder.into_hooks();
// convert from a color_eyre PanicHook to a standard panic hook
let panic_hook = panic_hook.into_panic_hook();
panic::set_hook(Box::new(move |panic_info| {
tui::restore().unwrap();
panic_hook(panic_info);
}));
// convert from a color_eyre EyreHook to a eyre ErrorHook
let eyre_hook = eyre_hook.into_eyre_hook();
eyre::set_hook(Box::new(move |error| {
tui::restore().unwrap();
eyre_hook(error)
}))?;
Ok(())
}
tui.rs
use std::io::{self, stdout};
use crossterm::{
terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen,
LeaveAlternateScreen,
},
ExecutableCommand,
};
use ratatui::prelude::*;
pub fn init() -> io::Result<Terminal<impl Backend>> {
stdout().execute(EnterAlternateScreen)?;
enable_raw_mode()?;
Terminal::new(CrosstermBackend::new(stdout()))
}
pub fn restore() -> io::Result<()> {
stdout().execute(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.: