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
use std::io;
use crossterm::{
event::{
DisableFocusChange, DisableMouseCapture, EnableFocusChange, EnableMouseCapture,
KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
},
execute,
terminal::{
self, EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
supports_keyboard_enhancement,
},
};
/// RAII guard that puts the terminal into raw / alt-screen mode on
/// construction and restores it on drop. Using this instead of paired
/// setup/teardown calls means a panic mid-session still leaves the terminal
/// usable.
pub struct TerminalGuard {
/// Tracks whether we pushed kitty keyboard flags so Drop only pops if we
/// actually pushed. Terminals that don't support the protocol leave the
/// stack untouched.
kitty_pushed: bool,
}
impl TerminalGuard {
pub fn new() -> io::Result<Self> {
execute!(
io::stdout(),
EnterAlternateScreen,
terminal::Clear(terminal::ClearType::All),
EnableFocusChange,
EnableMouseCapture,
)?;
enable_raw_mode()?;
// Kitty keyboard protocol disambiguates control characters that
// otherwise alias to legacy keys (Ctrl-H vs Backspace, Ctrl-J vs
// Enter, Ctrl-I vs Tab, Ctrl-M vs Enter). Only push on terminals
// that advertise support — otherwise the escape is ignored but the
// pop on Drop would also do nothing, so it's a wash either way.
let kitty_pushed = supports_keyboard_enhancement().unwrap_or(false);
if kitty_pushed {
execute!(
io::stdout(),
PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES,),
)?;
}
Ok(Self { kitty_pushed })
}
}
impl Drop for TerminalGuard {
fn drop(&mut self) {
if self.kitty_pushed {
let _ = execute!(io::stdout(), PopKeyboardEnhancementFlags);
}
let _ = disable_raw_mode();
let _ = execute!(
io::stdout(),
LeaveAlternateScreen,
DisableFocusChange,
DisableMouseCapture,
);
}
}