use std::panic::{self, PanicHookInfo};
type PanicHook = Box<dyn Fn(&PanicHookInfo<'_>) + Sync + Send + 'static>;
pub fn install_panic_hook() {
let original = panic::take_hook();
panic::set_hook(compose_hook(
|| {
ratatui::restore();
},
original,
));
}
fn compose_hook(restore: impl Fn() + Sync + Send + 'static, next: PanicHook) -> PanicHook {
Box::new(move |info| {
restore();
next(info);
})
}
#[cfg(test)]
mod tests {
use crate::test_support::PANIC_HOOK_GUARD;
use super::*;
use std::sync::{Arc, Mutex, PoisonError};
#[test]
#[allow(clippy::panic, reason = "driving the panic hook requires a real panic")]
fn test_compose_hook_restores_then_delegates_once() {
let _hook_guard = PANIC_HOOK_GUARD
.lock()
.unwrap_or_else(PoisonError::into_inner);
let order = Arc::new(Mutex::new(Vec::<&'static str>::new()));
let restore_order = order.clone();
let next_order = order.clone();
let next: PanicHook = Box::new(move |_info| {
next_order
.lock()
.expect("order mutex should not be poisoned")
.push("next");
});
let hook = compose_hook(
move || {
restore_order
.lock()
.expect("order mutex should not be poisoned")
.push("restore");
},
next,
);
let previous = panic::take_hook();
panic::set_hook(hook);
let _ = panic::catch_unwind(|| panic!("trigger"));
panic::set_hook(previous);
let recorded = order
.lock()
.expect("order mutex should not be poisoned")
.clone();
assert_eq!(
recorded,
vec!["restore", "next"],
"restore must run exactly once, before the delegated hook"
);
}
}