steer_tui/tui/
terminal.rs1use ratatui::crossterm::{
2 event::{
3 DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableFocusChange,
4 EnableMouseCapture, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags,
5 PushKeyboardEnhancementFlags,
6 },
7 execute,
8 terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
9};
10use std::io::{self, Write};
11use std::sync::atomic::{AtomicBool, Ordering};
12
13pub struct TerminalState {
15 pub(crate) raw: AtomicBool,
16 pub(crate) alt_screen: AtomicBool,
17 pub(crate) bracketed_paste: AtomicBool,
18 pub(crate) keyboard_flags_pushed: AtomicBool,
19 pub(crate) mouse_capture: AtomicBool,
20 pub(crate) focus_change: AtomicBool,
21}
22
23impl Default for TerminalState {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl TerminalState {
30 pub const fn new() -> Self {
31 Self {
32 raw: AtomicBool::new(false),
33 alt_screen: AtomicBool::new(false),
34 bracketed_paste: AtomicBool::new(false),
35 keyboard_flags_pushed: AtomicBool::new(false),
36 mouse_capture: AtomicBool::new(false),
37 focus_change: AtomicBool::new(false),
38 }
39 }
40}
41
42pub static TERMINAL_STATE: TerminalState = TerminalState::new();
43
44pub fn setup<W: Write>(w: &mut W) -> io::Result<()> {
46 enable_raw_mode()?;
48 TERMINAL_STATE.raw.store(true, Ordering::Relaxed);
49
50 execute!(w, EnterAlternateScreen)?;
52 TERMINAL_STATE.alt_screen.store(true, Ordering::Relaxed);
53
54 execute!(w, ratatui::crossterm::event::EnableBracketedPaste)?;
56 TERMINAL_STATE
57 .bracketed_paste
58 .store(true, Ordering::Relaxed);
59
60 execute!(
62 w,
63 PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES)
64 )?;
65 TERMINAL_STATE
66 .keyboard_flags_pushed
67 .store(true, Ordering::Relaxed);
68
69 execute!(w, EnableMouseCapture)?;
71 TERMINAL_STATE.mouse_capture.store(true, Ordering::Relaxed);
72
73 execute!(w, EnableFocusChange)?;
75 TERMINAL_STATE.focus_change.store(true, Ordering::Relaxed);
76
77 Ok(())
78}
79
80pub fn cleanup_with_writer<W: Write>(writer: &mut W) {
83 if TERMINAL_STATE
84 .keyboard_flags_pushed
85 .swap(false, Ordering::Relaxed)
86 {
87 let _ = execute!(writer, PopKeyboardEnhancementFlags);
88 }
89 if TERMINAL_STATE.focus_change.swap(false, Ordering::Relaxed) {
90 let _ = execute!(writer, DisableFocusChange);
91 }
92 if TERMINAL_STATE.mouse_capture.swap(false, Ordering::Relaxed) {
93 let _ = execute!(writer, DisableMouseCapture);
94 }
95 if TERMINAL_STATE
96 .bracketed_paste
97 .swap(false, Ordering::Relaxed)
98 {
99 let _ = execute!(writer, DisableBracketedPaste);
100 }
101 if TERMINAL_STATE.alt_screen.swap(false, Ordering::Relaxed) {
102 let _ = execute!(writer, LeaveAlternateScreen);
103 }
104 if TERMINAL_STATE.raw.swap(false, Ordering::Relaxed) {
105 let _ = disable_raw_mode();
106 }
107 let _ = writer.flush();
108}
109
110pub fn cleanup() {
112 {
113 let mut out = io::stdout();
114 cleanup_with_writer(&mut out);
115 let _ = out.flush();
116 }
117 {
118 let mut err = io::stderr();
119 cleanup_with_writer(&mut err);
120 let _ = err.flush();
121 }
122 #[cfg(not(windows))]
123 if let Ok(mut tty) = std::fs::OpenOptions::new().write(true).open("/dev/tty") {
124 cleanup_with_writer(&mut tty);
125 let _ = tty.flush();
126 }
127}
128
129pub struct SetupGuard {
132 armed: bool,
133}
134
135impl Default for SetupGuard {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141impl SetupGuard {
142 pub fn new() -> Self {
143 Self { armed: true }
144 }
145
146 pub fn disarm(&mut self) {
147 self.armed = false;
148 }
149}
150
151impl Drop for SetupGuard {
152 fn drop(&mut self) {
153 if self.armed {
154 cleanup();
155 }
156 }
157}