mermaid_cli/app/
terminal.rs1use std::io::{self, Stdout, Write};
12use std::sync::atomic::{AtomicBool, Ordering};
13
14use anyhow::{Context, Result};
15use crossterm::cursor::Show;
16use crossterm::event::{
17 DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
18};
19use crossterm::execute;
20use crossterm::terminal::{
21 EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
22};
23use ratatui::Terminal;
24use ratatui::backend::CrosstermBackend;
25
26static TERMINAL_NEEDS_RESTORE: AtomicBool = AtomicBool::new(false);
27
28pub struct TerminalGuard {
35 inner: Terminal<CrosstermBackend<Stdout>>,
36 restored: bool,
37}
38
39impl TerminalGuard {
40 pub fn setup() -> Result<Self> {
41 enable_raw_mode().context("failed to enable raw mode")?;
42 TERMINAL_NEEDS_RESTORE.store(true, Ordering::SeqCst);
43 let mut stdout = io::stdout();
44 if let Err(error) = execute!(
45 stdout,
46 EnterAlternateScreen,
47 EnableMouseCapture,
48 EnableBracketedPaste,
49 ) {
50 restore_terminal_once();
51 return Err(error).context(
52 "failed to enter alternate screen / enable mouse / enable bracketed paste",
53 );
54 }
55
56 let backend = CrosstermBackend::new(stdout);
57 let terminal = match Terminal::new(backend).context("failed to create terminal") {
58 Ok(terminal) => terminal,
59 Err(error) => {
60 restore_terminal_once();
61 return Err(error);
62 },
63 };
64
65 install_panic_hook();
66
67 Ok(Self {
68 inner: terminal,
69 restored: false,
70 })
71 }
72
73 pub fn inner_mut(&mut self) -> &mut Terminal<CrosstermBackend<Stdout>> {
75 &mut self.inner
76 }
77
78 pub fn restore_now(&mut self) {
81 if self.restored {
82 return;
83 }
84 restore_terminal_once();
85 let _ = self.inner.show_cursor();
86 self.restored = true;
87 }
88}
89
90impl Drop for TerminalGuard {
91 fn drop(&mut self) {
92 self.restore_now();
93 }
94}
95
96fn restore_terminal() {
103 let mut stdout = io::stdout();
104 let _ = execute!(
105 stdout,
106 DisableMouseCapture,
107 DisableBracketedPaste,
108 LeaveAlternateScreen,
109 Show,
110 );
111 let _ = stdout.write_all(
112 b"\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1005l\x1b[?1006l\x1b[?1015l\x1b[?2004l\x1b[?1049l\x1b[?25h\x1b[0m",
113 );
114 let _ = stdout.flush();
115 let _ = disable_raw_mode();
116}
117
118fn restore_terminal_once() {
119 if !TERMINAL_NEEDS_RESTORE.swap(false, Ordering::SeqCst) {
120 return;
121 }
122 restore_terminal();
123}
124
125fn install_panic_hook() {
130 static HOOK_INSTALLED: AtomicBool = AtomicBool::new(false);
131 if HOOK_INSTALLED
132 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
133 .is_err()
134 {
135 return;
136 }
137
138 let original = std::panic::take_hook();
139 std::panic::set_hook(Box::new(move |info| {
140 restore_terminal_once();
141 original(info);
142 }));
143}
144
145pub fn force_restore_terminal() {
148 restore_terminal();
149}