1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
//! Raw mode, the alternate screen, and getting out of both no matter how the
//! process ends.
//!
//! **This is the worst failure a TUI can have**, because it outlives the
//! process: a crash that leaves raw mode on and the alternate screen entered
//! leaves the operator with no echo, no line editing, no visible cursor and
//! often no scrollback, in a shell that looks broken and is.
//!
//! Two mechanisms, both, because neither covers the other's case:
//!
//! 1. **A panic hook** ([`install_panic_hook`]) that restores and *then* calls
//! the previous hook. Order matters: restoring first puts the default hook's
//! backtrace on a cooked terminal, on the main screen, where it can be read
//! and scrolled. A hook does not run on an ordinary early return.
//! 2. **A [`RestoreGuard`]** whose `Drop` restores. `Drop` does not run under
//! `panic = "abort"` — which this workspace does not set — and covers every
//! `?` and early `return` the hook does not.
//!
//! [`restore`] is idempotent, because on a panic BOTH of them fire.
//!
//! Nothing that can panic is installed between the hook and raw mode: the hook
//! goes on first, then raw mode, then the alternate screen.
//!
//! **ratatui 0.30's own `init()` would install a restoring hook too.** It is
//! not used here for one reason: it picks the terminal, the backend and the
//! hook as a bundle, and this phase needs the backend swappable for
//! `TestBackend` so the UI loop itself is testable. The four lines below keep
//! that seam and cost nothing.
//!
//! [`enter`] and [`RestoreGuard`]'s real caller is `super::mod`'s `lookout`.
//! [`install_panic_hook`] has an additional, permanent caller —
//! [`probe_panic_for_test`] calls it from behind an env-var gate in `main`,
//! so its own ordering can be checked by a headless subprocess test instead
//! of the by-hand `script` session Task 7's report describes doing once and
//! deleting.
use ;
use ;
use ;
/// Puts the terminal back the way it was found.
///
/// Every step ignores its own failure: this runs from a panic hook, where
/// there is nothing sensible to do with an error and where returning one would
/// mean skipping the steps after it. Safe to call twice, and routinely is.
/// Chains a restoring panic hook in front of whatever hook is installed.
///
/// Call before [`enter`], and only once per process.
/// Installs the panic hook, then panics on purpose.
///
/// Exists for exactly one caller: `main`, gated behind the
/// `SHEP_TERM_PANIC_PROBE` environment variable so it can never fire by
/// accident and never appears on the command surface — no clap variant, no
/// `--help` entry, same reasoning as the hidden `daemon`/`dog` subcommands.
///
/// This is the permanent replacement for the by-hand check Phase 12a's
/// Task 7 report describes doing once, under `script`, and then deleting:
/// confirming that [`install_panic_hook`] restores the terminal BEFORE the
/// previous hook prints its backtrace, so a crash lands on a cooked
/// main-screen terminal rather than a raw alternate one.
/// `tests/term_panic_order.rs` drives this function through a real
/// subprocess and checks the byte order of the two writes.
///
/// # Panics
/// Always — that is the entire point. The message names what is being
/// tested so a stray run (there should never be one) is self-explanatory in
/// a crash report.
!
/// Enters raw mode and the alternate screen, and hides the cursor.
///
/// **Fails clean.** Raw mode goes on first and the alternate screen second, so
/// there is a window in which the first step has succeeded and the second has
/// not — and a bare `?` there would return `Err` with raw mode still ON, to a
/// caller that is about to return an exit code and never had a guard. The
/// operator would be left with no echo and no line editing, which
/// this module's own doc calls the worst failure a TUI can have. So
/// the second step restores before it reports. The caller arms its
/// [`RestoreGuard`] before calling this as well; both, not either, is the same
/// argument the panic hook and the guard are two of.
///
/// # Errors
/// Whatever `crossterm` could not do to the terminal.
/// Restores on drop.
///
/// Holds a closure rather than calling [`restore`] directly so its own test can
/// observe that dropping it acts, without a terminal to act on — the behaviour
/// under test is "the guard runs its action exactly once when it goes out of
/// scope", and that is what regresses if someone converts this to a plain
/// struct with a manual teardown call.