Skip to content

Setup Panic Hooks

ratatui を使用して TUI を構築する場合、アプリケーションがパニックに遭遇した場合に、元のターミナル状態に正常に復帰できるようにすることが重要です。これにより、ターミナルが変更後の状態で停止することを防ぎ、ユーザーにとって大きな混乱を招く可能性があります。

Rust 標準ライブラリを使用すると、パニックが発生するたびに実行されるパニック フックをアプリケーションで設定できます。Ratatui アプリケーションはこれを使用して、raw モードを無効にし、メイン画面に戻る必要があります。

1 秒の遅延後にパニックが発生する次のアプリケーションを基準として、各バックエンドにフックを実装できます。

main.rs
pub fn main() -> io::Result<()> {
init_panic_hook();
let mut tui = init_tui()?;
tui.draw(|frame| frame.render_widget(Span::from("Hello, world!"), frame.area()))?;
sleep(Duration::from_secs(1));
panic!("This is a panic!");
}

Crossterm

CrosstermBackend を使用するアプリでターミナル状態を復元するのは非常に簡単です。 init_panic_hook メソッドは、現在のフックのコピーを保存し、元のフックを呼び出す前に元の状態にターミナルを復元する新しいフックをセットアップします。ターミナル状態を復元しながらパニックに陥らないようにすることが重要です。そうしないと、元のパニック理由が失われる可能性があります。あなた自身のアプリでは、これはファイルなどへのログで補足される可能性があります。

main.rs
26 collapsed lines
use std::{
io::{self, stdout},
panic::{set_hook, take_hook},
thread::sleep,
time::Duration,
};
use ratatui::{
backend::{Backend, CrosstermBackend},
crossterm::{
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
},
text::Span,
Terminal,
};
pub fn main() -> io::Result<()> {
init_panic_hook();
let mut tui = init_tui()?;
tui.draw(|frame| frame.render_widget(Span::from("Hello, world!"), frame.area()))?;
sleep(Duration::from_secs(1));
panic!("This is a panic!");
}
pub fn init_panic_hook() {
let original_hook = take_hook();
set_hook(Box::new(move |panic_info| {
// intentionally ignore errors here since we're already in a panic
let _ = restore_tui();
original_hook(panic_info);
}));
}
pub fn init_tui() -> io::Result<Terminal<impl Backend>> {
enable_raw_mode()?;
execute!(stdout(), EnterAlternateScreen)?;
Terminal::new(CrosstermBackend::new(stdout()))
}
pub fn restore_tui() -> io::Result<()> {
disable_raw_mode()?;
execute!(stdout(), LeaveAlternateScreen)?;
Ok(())
}

Termion

Termion では、raw モードを有効または無効にするコードは RawTerminal タイプでのみ使用できるため、もう少し手間がかかります。このタイプは、構築時にターミナル状態のコピーを保存し、ドロップ時にそれを復元します。ターミナル状態を一時的に復元する suspend_raw_mode 関数があります。

init_tui メソッドがターミナルをクックされた状態 (raw の反対) で確認できるようにするには、init_panic_hook メソッドで、パニック フックで使用される RawTerminal を作成し、raw モードを直ちに一時停止する必要があります。

Termion は代替画面用の同様のラッパー タイプを提供しますが、このタイプはドロップ時を除いて代替画面を終了するメソッドを実装していません。アプリでは、IntoAlternateScreen ラッパーではなく ToAlternateScreen / ToMainScreen を使用する必要があります。また、この変更を有効にするには、stdout().flush を必ず呼び出してください。

main.rs
23 collapsed lines
use std::{
io::{self, stdout, Write},
panic::{set_hook, take_hook},
thread::sleep,
time::Duration,
};
use ratatui::{
backend::{Backend, TermionBackend},
termion::{
raw::IntoRawMode,
screen::{ToAlternateScreen, ToMainScreen},
},
text::Span,
Terminal,
};
pub fn main() -> io::Result<()> {
init_panic_hook()?;
let mut tui = init_tui()?;
tui.draw(|frame| frame.render_widget(Span::from("Hello, world!"), frame.area()))?;
sleep(Duration::from_secs(1));
panic!("This is a panic!");
}
pub fn init_panic_hook() -> io::Result<()> {
let raw_output = stdout().into_raw_mode()?;
raw_output.suspend_raw_mode()?;
let original_hook = take_hook();
set_hook(Box::new(move |panic_info| {
// intentionally ignore errors here since we're already in a panic
let _ = raw_output.suspend_raw_mode();
let _ = restore_tui();
original_hook(panic_info);
}));
Ok(())
}
pub fn init_tui() -> io::Result<Terminal<impl Backend>> {
let mut stdout = stdout().into_raw_mode()?;
write!(stdout, "{}", ToAlternateScreen)?;
stdout.flush()?;
Terminal::new(TermionBackend::new(stdout))
}
pub fn restore_tui() -> io::Result<()> {
write!(stdout(), "{}", ToMainScreen)?;
stdout().flush()?;
Ok(())
}

これについての詳細については、参照してください。

Termwiz

TermWizは、RAWモードを無効にして退出する方法がターミナルインスタンスへの可変アクセスが必要であるため、もう少し困難です。

// TODO

結論

一般的なルールとして、元のパニック フックを取得し、ターミナルをクリーンアップした後に実行します。次のセクションでは、エラーやパニックの処理でより優れた出力を提供するのに役立つサードパーティ パッケージについて説明します。