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 SHOW_DIAGNOSTICS: AtomicBool = AtomicBool::new(false);
23static PANIC_HOOK_ONCE: Once = Once::new();
24
25pub fn set_debug_mode(enabled: bool) {
29 DEBUG_MODE.store(enabled, Ordering::SeqCst);
30}
31
32pub fn is_debug_mode() -> bool {
34 DEBUG_MODE.load(Ordering::SeqCst)
35}
36
37pub fn set_show_diagnostics(enabled: bool) {
40 SHOW_DIAGNOSTICS.store(enabled, Ordering::SeqCst);
41}
42
43pub fn show_diagnostics() -> bool {
45 SHOW_DIAGNOSTICS.load(Ordering::SeqCst)
46}
47
48pub fn init_panic_hook() {
55 PANIC_HOOK_ONCE.call_once(|| {
56 let original_hook = panic::take_hook();
58
59 let better_panic_hook = BetterPanicSettings::new()
61 .verbosity(BetterPanicVerbosity::Full)
62 .most_recent_first(false)
63 .lineno_suffix(true)
64 .create_panic_handler();
65
66 panic::set_hook(Box::new(move |panic_info| {
67 let is_tui = TUI_INITIALIZED.load(Ordering::SeqCst);
68 let is_debug = DEBUG_MODE.load(Ordering::SeqCst);
69
70 if is_tui {
72 let _ = restore_tui();
74 }
75
76 if is_debug {
77 better_panic_hook(panic_info);
78 } else {
79 eprintln!("\nVTCode encountered a critical error and needs to shut down.");
80 eprintln!("If this keeps happening, please report it with a backtrace.");
81 eprintln!("Hint: run with --debug and set RUST_BACKTRACE=1.\n");
82 original_hook(panic_info);
83 }
84
85 std::process::exit(1);
87 }));
88 });
89}
90
91pub fn mark_tui_initialized() {
93 TUI_INITIALIZED.store(true, Ordering::SeqCst);
94}
95
96pub fn mark_tui_deinitialized() {
98 TUI_INITIALIZED.store(false, Ordering::SeqCst);
99}
100
101pub fn restore_tui() -> io::Result<()> {
110 while let Ok(true) = ratatui::crossterm::event::poll(std::time::Duration::from_millis(0)) {
113 let _ = ratatui::crossterm::event::read();
114 }
115
116 let mut stderr = io::stderr();
118
119 let _ = execute!(stderr, MoveToColumn(0), Clear(ClearType::CurrentLine));
121
122 let _ = execute!(stderr, LeaveAlternateScreen);
125
126 let _ = execute!(stderr, DisableBracketedPaste);
128 let _ = execute!(stderr, DisableFocusChange);
129 let _ = execute!(stderr, DisableMouseCapture);
130 let _ = execute!(stderr, PopKeyboardEnhancementFlags);
131
132 let _ = execute!(
134 stderr,
135 SetCursorStyle::DefaultUserShape,
136 Show,
137 RestorePosition
138 );
139
140 let _ = disable_raw_mode();
142
143 let _ = stderr.flush();
145
146 Ok(())
147}
148
149pub struct TuiPanicGuard;
154
155impl TuiPanicGuard {
156 pub fn new() -> Self {
160 mark_tui_initialized();
161 Self
162 }
163}
164
165impl Default for TuiPanicGuard {
166 fn default() -> Self {
167 Self::new()
168 }
169}
170
171impl Drop for TuiPanicGuard {
172 fn drop(&mut self) {
173 mark_tui_deinitialized();
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use std::sync::atomic::Ordering;
181
182 #[test]
183 fn test_panic_guard_initialization() {
184 TUI_INITIALIZED.store(false, Ordering::SeqCst);
186
187 {
188 let _guard = TuiPanicGuard::new();
189 assert_eq!(
190 TUI_INITIALIZED.load(Ordering::SeqCst),
191 true,
192 "TUI should be marked as initialized"
193 );
194
195 }
197
198 assert_eq!(
199 TUI_INITIALIZED.load(Ordering::SeqCst),
200 false,
201 "TUI should be marked as deinitialized after guard drops"
202 );
203 }
204
205 #[test]
206 fn test_restore_terminal_no_panic_when_not_initialized() {
207 TUI_INITIALIZED.store(false, Ordering::SeqCst);
209
210 let result = restore_tui();
212 assert!(result.is_ok() || result.is_err());
214 }
215
216 #[test]
217 fn test_guard_lifecycle() {
218 TUI_INITIALIZED.store(false, Ordering::SeqCst);
219
220 {
222 let _guard = TuiPanicGuard::new();
223 assert_eq!(
224 TUI_INITIALIZED.load(Ordering::SeqCst),
225 true,
226 "Guard should mark TUI as initialized"
227 );
228 }
229
230 assert_eq!(
231 TUI_INITIALIZED.load(Ordering::SeqCst),
232 false,
233 "Drop should mark TUI as deinitialized"
234 );
235 }
236}