vtcode_tui/core_tui/
panic_hook.rs1use std::io::{self, Write};
6use std::panic;
7use std::sync::Once;
8use std::sync::atomic::{AtomicBool, Ordering};
9
10use better_panic::{Settings as BetterPanicSettings, Verbosity as BetterPanicVerbosity};
11use ratatui::crossterm::{
12 cursor::{MoveToColumn, RestorePosition, SetCursorStyle, Show},
13 event::{
14 DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, PopKeyboardEnhancementFlags,
15 },
16 execute,
17 terminal::{Clear, ClearType, LeaveAlternateScreen, disable_raw_mode},
18};
19
20static TUI_INITIALIZED: AtomicBool = AtomicBool::new(false);
21static DEBUG_MODE: AtomicBool = AtomicBool::new(cfg!(debug_assertions));
22static PANIC_HOOK_ONCE: Once = Once::new();
23
24pub fn set_debug_mode(enabled: bool) {
28 DEBUG_MODE.store(enabled, Ordering::SeqCst);
29}
30
31pub fn is_debug_mode() -> bool {
33 DEBUG_MODE.load(Ordering::SeqCst)
34}
35
36pub fn init_panic_hook() {
43 PANIC_HOOK_ONCE.call_once(|| {
44 let original_hook = panic::take_hook();
46
47 let better_panic_hook = BetterPanicSettings::new()
49 .verbosity(BetterPanicVerbosity::Full)
50 .most_recent_first(false)
51 .lineno_suffix(true)
52 .create_panic_handler();
53
54 panic::set_hook(Box::new(move |panic_info| {
55 let is_tui = TUI_INITIALIZED.load(Ordering::SeqCst);
56 let is_debug = DEBUG_MODE.load(Ordering::SeqCst);
57
58 if is_tui {
60 let _ = restore_tui();
62 }
63
64 if is_debug {
65 better_panic_hook(panic_info);
66 } else {
67 eprintln!("\nVTCode encountered a critical error and needs to shut down.");
68 eprintln!("If this keeps happening, please report it with a backtrace.");
69 eprintln!("Hint: run with --debug and set RUST_BACKTRACE=1.\n");
70 original_hook(panic_info);
71 }
72
73 std::process::exit(1);
75 }));
76 });
77}
78
79pub fn mark_tui_initialized() {
81 TUI_INITIALIZED.store(true, Ordering::SeqCst);
82}
83
84pub fn mark_tui_deinitialized() {
86 TUI_INITIALIZED.store(false, Ordering::SeqCst);
87}
88
89pub fn restore_tui() -> io::Result<()> {
98 while let Ok(true) = ratatui::crossterm::event::poll(std::time::Duration::from_millis(0)) {
101 let _ = ratatui::crossterm::event::read();
102 }
103
104 let mut stderr = io::stderr();
106
107 let _ = execute!(stderr, MoveToColumn(0), Clear(ClearType::CurrentLine));
109
110 let _ = execute!(stderr, LeaveAlternateScreen);
113
114 let _ = execute!(stderr, DisableBracketedPaste);
116 let _ = execute!(stderr, DisableFocusChange);
117 let _ = execute!(stderr, DisableMouseCapture);
118 let _ = execute!(stderr, PopKeyboardEnhancementFlags);
119
120 let _ = execute!(
122 stderr,
123 SetCursorStyle::DefaultUserShape,
124 Show,
125 RestorePosition
126 );
127
128 let _ = disable_raw_mode();
130
131 let _ = stderr.flush();
133
134 Ok(())
135}
136
137pub struct TuiPanicGuard;
142
143impl TuiPanicGuard {
144 pub fn new() -> Self {
148 mark_tui_initialized();
149 Self
150 }
151}
152
153impl Default for TuiPanicGuard {
154 fn default() -> Self {
155 Self::new()
156 }
157}
158
159impl Drop for TuiPanicGuard {
160 fn drop(&mut self) {
161 mark_tui_deinitialized();
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use std::sync::atomic::Ordering;
169
170 #[test]
171 fn test_panic_guard_initialization() {
172 TUI_INITIALIZED.store(false, Ordering::SeqCst);
174
175 {
176 let _guard = TuiPanicGuard::new();
177 assert_eq!(
178 TUI_INITIALIZED.load(Ordering::SeqCst),
179 true,
180 "TUI should be marked as initialized"
181 );
182
183 }
185
186 assert_eq!(
187 TUI_INITIALIZED.load(Ordering::SeqCst),
188 false,
189 "TUI should be marked as deinitialized after guard drops"
190 );
191 }
192
193 #[test]
194 fn test_restore_terminal_no_panic_when_not_initialized() {
195 TUI_INITIALIZED.store(false, Ordering::SeqCst);
197
198 let result = restore_tui();
200 assert!(result.is_ok() || result.is_err());
202 }
203
204 #[test]
205 fn test_guard_lifecycle() {
206 TUI_INITIALIZED.store(false, Ordering::SeqCst);
207
208 {
210 let _guard = TuiPanicGuard::new();
211 assert_eq!(
212 TUI_INITIALIZED.load(Ordering::SeqCst),
213 true,
214 "Guard should mark TUI as initialized"
215 );
216 }
217
218 assert_eq!(
219 TUI_INITIALIZED.load(Ordering::SeqCst),
220 false,
221 "Drop should mark TUI as deinitialized"
222 );
223 }
224}