fdehau / tui-rs

Build terminal user interfaces and dashboards using Rust
MIT License
10.84k stars 487 forks source link

`panic!` does not show #177

Closed piegamesde closed 1 year ago

piegamesde commented 5 years ago

I tried this both with the Termion and the RustBox backend: when my application panics, it will simply quit without printing any messages to the output. This is presumably because the terminal is in raw mode and exits it after the message was printed, thus clearing the screen.

Is there any way to write stuff to console (especially errors), so that I know why my application crashed?

piegamesde commented 5 years ago

I managed to get it somewhat working with Termion with a custom panic handler. Since I don't know how to explicitly disable raw mode, I simply switch back to the main Screen and add \r to each println! command. Here's my current handler, with some copy-paste from Rust's default handler:

fn panic_hook(info: &PanicInfo<'_>) {
    let location = info.location().unwrap();  // The current implementation always returns Some

    let msg = match info.payload().downcast_ref::<&'static str>() {
        Some(s) => *s,
        None => match info.payload().downcast_ref::<String>() {
            Some(s) => &s[..],
            None => "Box<Any>",
        }
    };
    println!("{}thread '<unnamed>' panicked at '{}', {}\r", termion::screen::ToMainScreen, msg, location);
}

It works, but has quite a few drawbacks:

cjbassi commented 4 years ago

I had a similar issue with crossterm and I also used a panic_hook along with better_panic:

fn setup_panic_hook() {
    panic::set_hook(Box::new(|panic_info| {
        // Exits raw mode.
        cleanup_terminal();
        better_panic::Settings::auto().create_panic_handler()(panic_info);
    }));
}

https://github.com/cjbassi/ytop/blob/master/src/main.rs#L114

simpoir commented 4 years ago

With termion backend, the easy workaround is to set a raw handle on startup and to pass it to the hook. Here's a simple case:

use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use tui::backend::TermionBackend;

fn main() {
    setup_panic();

    let mut tui = tui::Terminal::new(TermionBackend::new(AlternateScreen::from(
        std::io::stdout().into_raw_mode().unwrap(),
    )))
    .unwrap();
    tui.clear().unwrap();

    panic!("noes");
}

fn setup_panic() {
    let raw_handle = std::io::stdout().into_raw_mode().unwrap();
    let default_hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |info| {
        raw_handle
            .suspend_raw_mode()
            .unwrap_or_else(|e| log::error!("Could not suspend raw mode: {}", e));
        default_hook(info);
        // or better_panic::Settings::new().create_panic_handler()(info);
    }));
}
ThomWright commented 4 years ago

I had a similar issue with crossterm and I also used a panic_hook along with better_panic:

fn setup_panic_hook() {
  panic::set_hook(Box::new(|panic_info| {
      // Exits raw mode.
      cleanup_terminal();
      better_panic::Settings::auto().create_panic_handler()(panic_info);
  }));
}

https://github.com/cjbassi/ytop/blob/master/src/main.rs#L114

Thanks for the example, this convinced me to move from termion to crossterm. Crossterm's handling of terminal state (raw mode etc.) seems much more sensible!

dtomvan commented 3 years ago

How do you accomplish this when using a AlternateScreen?

dtomvan commented 3 years ago

Nevermind:

std::panic::set_hook(Box::new(move |x| {
        stdout()
            .into_raw_mode()
            .unwrap()
            .suspend_raw_mode()
            .unwrap();
        write!(stdout().into_raw_mode().unwrap(), "{}", ToMainScreen).unwrap();
        write!(stdout(), "{:?}", x).unwrap();
}));