1use crossterm::{
5 cursor,
6 event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
7 style::{self, Color, SetForegroundColor},
8 terminal,
9};
10use std::io::{self, BufWriter, Write};
11use std::sync::atomic::{AtomicBool, Ordering};
12use std::time::Duration;
13
14use crossterm::{QueueableCommand, execute};
15
16thread_local! {
17 static STDOUT_BUF: std::cell::RefCell<BufWriter<io::Stdout>> =
18 std::cell::RefCell::new(BufWriter::with_capacity(65536, io::stdout()));
19}
20
21static RAW_MODE_ACTIVE: AtomicBool = AtomicBool::new(false);
23
24pub fn enable_raw_mode() -> Result<(), String> {
25 terminal::enable_raw_mode().map_err(|e| e.to_string())?;
26 RAW_MODE_ACTIVE.store(true, Ordering::SeqCst);
27 Ok(())
28}
29
30pub fn disable_raw_mode() -> Result<(), String> {
31 RAW_MODE_ACTIVE.store(false, Ordering::SeqCst);
32 terminal::disable_raw_mode().map_err(|e| e.to_string())
33}
34
35pub fn restore_terminal() {
38 if RAW_MODE_ACTIVE.swap(false, Ordering::SeqCst) {
39 let _ = execute!(io::stdout(), cursor::Show);
40 let _ = execute!(io::stdout(), style::ResetColor);
41 let _ = terminal::disable_raw_mode();
42 let _ = io::stdout().write_all(b"\n");
43 let _ = io::stdout().flush();
44 }
45}
46
47pub fn clear() -> Result<(), String> {
48 STDOUT_BUF.with(|buf| {
49 buf.borrow_mut()
50 .queue(terminal::Clear(terminal::ClearType::All))
51 .map(|_| ())
52 .map_err(|e| e.to_string())
53 })
54}
55
56pub fn move_to(x: i64, y: i64) -> Result<(), String> {
57 let x = u16::try_from(x).map_err(|_| format!("Terminal.moveTo: x={} out of range", x))?;
58 let y = u16::try_from(y).map_err(|_| format!("Terminal.moveTo: y={} out of range", y))?;
59 STDOUT_BUF.with(|buf| {
60 buf.borrow_mut()
61 .queue(cursor::MoveTo(x, y))
62 .map(|_| ())
63 .map_err(|e| e.to_string())
64 })
65}
66
67pub fn print_at_cursor(s: &str) -> Result<(), String> {
68 STDOUT_BUF.with(|buf| {
69 buf.borrow_mut()
70 .write_all(s.as_bytes())
71 .map_err(|e| e.to_string())
72 })
73}
74
75pub fn set_color(color: &str) -> Result<(), String> {
76 let c = parse_color(color)?;
77 STDOUT_BUF.with(|buf| {
78 buf.borrow_mut()
79 .queue(SetForegroundColor(c))
80 .map(|_| ())
81 .map_err(|e| e.to_string())
82 })
83}
84
85pub fn reset_color() -> Result<(), String> {
86 STDOUT_BUF.with(|buf| {
87 buf.borrow_mut()
88 .queue(style::ResetColor)
89 .map(|_| ())
90 .map_err(|e| e.to_string())
91 })
92}
93
94pub fn read_key() -> Option<String> {
96 if !event::poll(Duration::ZERO).ok()? {
97 return None;
98 }
99 let event = event::read().ok()?;
100 match event {
101 Event::Key(KeyEvent {
102 code, modifiers, ..
103 }) => {
104 if modifiers.contains(KeyModifiers::CONTROL) && code == KeyCode::Char('c') {
106 return Some("esc".to_string());
107 }
108 match code {
109 KeyCode::Up => Some("up".to_string()),
110 KeyCode::Down => Some("down".to_string()),
111 KeyCode::Left => Some("left".to_string()),
112 KeyCode::Right => Some("right".to_string()),
113 KeyCode::Esc => Some("esc".to_string()),
114 KeyCode::Enter => Some("enter".to_string()),
115 KeyCode::Char(c) => Some(c.to_string()),
116 _ => None,
117 }
118 }
119 _ => None,
120 }
121}
122
123pub fn size() -> Result<(i64, i64), String> {
124 let (w, h) = terminal::size().map_err(|e| e.to_string())?;
125 Ok((w as i64, h as i64))
126}
127
128pub fn hide_cursor() -> Result<(), String> {
129 STDOUT_BUF.with(|buf| {
130 buf.borrow_mut()
131 .queue(cursor::Hide)
132 .map(|_| ())
133 .map_err(|e| e.to_string())
134 })
135}
136
137pub fn show_cursor() -> Result<(), String> {
138 STDOUT_BUF.with(|buf| {
139 buf.borrow_mut()
140 .queue(cursor::Show)
141 .map(|_| ())
142 .map_err(|e| e.to_string())
143 })
144}
145
146pub fn flush() -> Result<(), String> {
147 STDOUT_BUF.with(|buf| buf.borrow_mut().flush().map_err(|e| e.to_string()))
148}
149
150#[derive(Default)]
153pub struct TerminalGuard;
154
155impl TerminalGuard {
156 pub fn new() -> Self {
157 Self
158 }
159}
160
161impl Drop for TerminalGuard {
162 fn drop(&mut self) {
163 restore_terminal();
164 }
165}
166
167fn parse_color(s: &str) -> Result<Color, String> {
168 match s {
169 "red" => Ok(Color::Red),
170 "green" => Ok(Color::Green),
171 "yellow" => Ok(Color::Yellow),
172 "blue" => Ok(Color::Blue),
173 "white" => Ok(Color::White),
174 "cyan" => Ok(Color::Cyan),
175 "magenta" => Ok(Color::Magenta),
176 "black" => Ok(Color::Black),
177 _ => Err(format!(
178 "Terminal.setColor: unknown color '{}' (expected: red, green, yellow, blue, white, cyan, magenta, black)",
179 s
180 )),
181 }
182}