git_worktree_manager/tui/mod.rs
1//! TUI rendering layer built on ratatui + crossterm.
2//!
3//! Houses:
4//! - `arrow_select`: raw-mode arrow-key selector (pre-existing)
5//! - `list_view`: Inline Viewport renderer for `gw list` (new)
6//! - `style`: shared ratatui `Style` palette mirroring `crate::console`
7//!
8//! Simple commands with pure text output continue to use `crate::console`.
9//! ratatui is reserved for commands that need declarative/progressive rendering.
10
11pub mod arrow_select;
12pub mod list_view;
13pub mod style;
14
15// Re-export for backwards-compatible call sites that use `crate::tui::arrow_select(...)`.
16pub use arrow_select::arrow_select;
17
18use std::io::IsTerminal;
19use std::sync::atomic::{AtomicBool, Ordering};
20
21/// Whether stdout is attached to a terminal. Commands should fall back to
22/// static rendering when this returns false (pipes, redirects, CI).
23pub fn stdout_is_tty() -> bool {
24 std::io::stdout().is_terminal()
25}
26
27// #20/#4: tracks whether a ratatui terminal is currently active. The panic hook
28// checks this flag so `ratatui::restore()` is only called when it matters —
29// a non-ratatui panic must not clobber terminal state it never set up.
30//
31// Single-thread invariant: only the main thread creates ratatui terminals
32// in this codebase. Relaxed ordering is sufficient. If callers ever cross
33// threads, upgrade to Acquire/Release.
34static RATATUI_ACTIVE: AtomicBool = AtomicBool::new(false);
35
36/// Mark that a ratatui terminal is now active.
37///
38/// # Safety contract
39/// Must be called only from `TerminalGuard::new`. Direct callers can corrupt
40/// the panic-hook contract.
41pub(crate) fn mark_ratatui_active() {
42 RATATUI_ACTIVE.store(true, Ordering::Relaxed);
43}
44
45/// Mark that the ratatui terminal has been released.
46///
47/// # Safety contract
48/// Must be called only from `TerminalGuard::Drop`. Direct callers can corrupt
49/// the panic-hook contract.
50pub(crate) fn mark_ratatui_inactive() {
51 RATATUI_ACTIVE.store(false, Ordering::Relaxed);
52}
53
54/// Install a panic hook that restores the terminal state before the default
55/// panic handler prints. Safe to call once at process start.
56///
57/// The hook is gated on `RATATUI_ACTIVE` so it only calls `ratatui::restore()`
58/// when a ratatui terminal is actually in use — avoiding spurious restores for
59/// non-TTY panics (pipes, redirects, CI). `TerminalGuard` in `display.rs`
60/// sets and clears this flag.
61///
62/// `default(info)` chains to the original hook, which prints the panic message
63/// and respects `RUST_BACKTRACE` — so backtrace behaviour is preserved.
64pub fn install_panic_hook() {
65 let default = std::panic::take_hook();
66 std::panic::set_hook(Box::new(move |info| {
67 if RATATUI_ACTIVE.load(Ordering::Relaxed) {
68 // #6: catch_unwind guards against a second panic inside restore().
69 let _ = std::panic::catch_unwind(|| {
70 ratatui::restore();
71 });
72 }
73 default(info);
74 }));
75}