Setup Panic Hooks
ratatui
を使用して TUI を構築する場合、アプリケーションがパニックに遭遇した場合に、元のターミナル状態に正常に復帰できるようにすることが重要です。これにより、ターミナルが変更後の状態で停止することを防ぎ、ユーザーにとって大きな混乱を招く可能性があります。
Rust 標準ライブラリを使用すると、パニックが発生するたびに実行されるパニック フックをアプリケーションで設定できます。Ratatui アプリケーションはこれを使用して、raw モードを無効にし、メイン画面に戻る必要があります。
1 秒の遅延後にパニックが発生する次のアプリケーションを基準として、各バックエンドにフックを実装できます。
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
メソッドは、現在のフックのコピーを保存し、元のフックを呼び出す前に元の状態にターミナルを復元する新しいフックをセットアップします。ターミナル状態を復元しながらパニックに陥らないようにすることが重要です。そうしないと、元のパニック理由が失われる可能性があります。あなた自身のアプリでは、これはファイルなどへのログで補足される可能性があります。
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
を必ず呼び出してください。
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(())}
これについての詳細については、参照してください。
- https://github.com/ratatui/ratatui/issues/1005
- https://gitlab.redox-os.org/redox-os/termion/-/issues/176
Termwiz
TermWizは、RAWモードを無効にして退出する方法がターミナルインスタンスへの可変アクセスが必要であるため、もう少し困難です。
// TODO
結論
一般的なルールとして、元のパニック フックを取得し、ターミナルをクリーンアップした後に実行します。次のセクションでは、エラーやパニックの処理でより優れた出力を提供するのに役立つサードパーティ パッケージについて説明します。