1#![allow(clippy::await_holding_refcell_ref)]
20#![allow(clippy::collapsible_else_if)]
21#![warn(anonymous_parameters, bad_style, missing_docs)]
22#![warn(unused, unused_extern_crates, unused_import_braces, unused_qualifications)]
23#![warn(unsafe_code)]
24
25use async_channel::{Receiver, Sender, TryRecvError};
26use async_trait::async_trait;
27use crossterm::event::{self, KeyEventKind};
28use crossterm::tty::IsTty;
29use crossterm::{cursor, style, terminal, QueueableCommand};
30use endbasic_core::exec::Signal;
31use endbasic_std::console::{
32 get_env_var_as_u16, read_key_from_stdin, remove_control_chars, CharsXY, ClearType, Console, Key,
33};
34use std::cmp::Ordering;
35use std::collections::VecDeque;
36use std::io::{self, StdoutLock, Write};
37
38pub struct TerminalConsole {
40 is_tty: bool,
43
44 fg_color: Option<u8>,
46
47 bg_color: Option<u8>,
49
50 cursor_visible: bool,
52
53 alt_active: bool,
55
56 sync_enabled: bool,
58
59 on_key_rx: Receiver<Key>,
61}
62
63impl Drop for TerminalConsole {
64 fn drop(&mut self) {
65 if self.is_tty {
66 terminal::disable_raw_mode().unwrap();
67 }
68 }
69}
70
71impl TerminalConsole {
72 pub fn from_stdio(signals_tx: Sender<Signal>) -> io::Result<Self> {
77 let (terminal, _on_key_tx) = Self::from_stdio_with_injector(signals_tx)?;
78 Ok(terminal)
79 }
80
81 pub fn from_stdio_with_injector(signals_tx: Sender<Signal>) -> io::Result<(Self, Sender<Key>)> {
89 let (on_key_tx, on_key_rx) = async_channel::unbounded();
90
91 let is_tty = io::stdin().is_tty() && io::stdout().is_tty();
92
93 if is_tty {
94 terminal::enable_raw_mode()?;
95 tokio::task::spawn(TerminalConsole::raw_key_handler(on_key_tx.clone(), signals_tx));
96 } else {
97 tokio::task::spawn(TerminalConsole::stdio_key_handler(on_key_tx.clone()));
98 }
99
100 Ok((
101 Self {
102 is_tty,
103 fg_color: None,
104 bg_color: None,
105 cursor_visible: true,
106 alt_active: false,
107 sync_enabled: true,
108 on_key_rx,
109 },
110 on_key_tx,
111 ))
112 }
113
114 async fn raw_key_handler(on_key_tx: Sender<Key>, signals_tx: Sender<Signal>) {
117 use event::{KeyCode, KeyModifiers};
118
119 let mut done = false;
120 while !done {
121 let key = match event::read() {
122 Ok(event::Event::Key(ev)) => {
123 if ev.kind != KeyEventKind::Press {
124 continue;
125 }
126
127 match ev.code {
128 KeyCode::Backspace => Key::Backspace,
129 KeyCode::End => Key::End,
130 KeyCode::Esc => Key::Escape,
131 KeyCode::Home => Key::Home,
132 KeyCode::Tab => Key::Tab,
133 KeyCode::Up => Key::ArrowUp,
134 KeyCode::Down => Key::ArrowDown,
135 KeyCode::Left => Key::ArrowLeft,
136 KeyCode::Right => Key::ArrowRight,
137 KeyCode::PageDown => Key::PageDown,
138 KeyCode::PageUp => Key::PageUp,
139 KeyCode::Char('a') if ev.modifiers == KeyModifiers::CONTROL => Key::Home,
140 KeyCode::Char('b') if ev.modifiers == KeyModifiers::CONTROL => {
141 Key::ArrowLeft
142 }
143 KeyCode::Char('c') if ev.modifiers == KeyModifiers::CONTROL => {
144 Key::Interrupt
145 }
146 KeyCode::Char('d') if ev.modifiers == KeyModifiers::CONTROL => Key::Eof,
147 KeyCode::Char('e') if ev.modifiers == KeyModifiers::CONTROL => Key::End,
148 KeyCode::Char('f') if ev.modifiers == KeyModifiers::CONTROL => {
149 Key::ArrowRight
150 }
151 KeyCode::Char('j') if ev.modifiers == KeyModifiers::CONTROL => Key::NewLine,
152 KeyCode::Char('m') if ev.modifiers == KeyModifiers::CONTROL => Key::NewLine,
153 KeyCode::Char('n') if ev.modifiers == KeyModifiers::CONTROL => {
154 Key::ArrowDown
155 }
156 KeyCode::Char('p') if ev.modifiers == KeyModifiers::CONTROL => Key::ArrowUp,
157 KeyCode::Char(ch) => Key::Char(ch),
158 KeyCode::Enter => Key::NewLine,
159 _ => Key::Unknown(format!("{:?}", ev)),
160 }
161 }
162 Ok(_) => {
163 continue;
165 }
166 Err(e) => {
167 Key::Unknown(format!("{:?}", e))
171 }
172 };
173
174 done = key == Key::Eof;
175 if key == Key::Interrupt {
176 signals_tx
182 .send(Signal::Break)
183 .await
184 .expect("Send to unbounded channel should not have failed")
185 }
186
187 let _ = on_key_tx.send(key).await;
191 }
192
193 signals_tx.close();
194 on_key_tx.close();
195 }
196
197 async fn stdio_key_handler(on_key_tx: Sender<Key>) {
200 let mut buffer = VecDeque::default();
207
208 let mut done = false;
209 while !done {
210 let key = match read_key_from_stdin(&mut buffer) {
211 Ok(key) => key,
212 Err(e) => {
213 Key::Unknown(format!("{:?}", e))
217 }
218 };
219
220 done = key == Key::Eof;
221
222 let _ = on_key_tx.send(key).await;
226 }
227
228 on_key_tx.close();
229 }
230
231 fn maybe_flush(&self, mut lock: StdoutLock<'_>) -> io::Result<()> {
233 if self.sync_enabled {
234 lock.flush()
235 } else {
236 Ok(())
237 }
238 }
239}
240
241#[async_trait(?Send)]
242impl Console for TerminalConsole {
243 fn clear(&mut self, how: ClearType) -> io::Result<()> {
244 let how = match how {
245 ClearType::All => terminal::ClearType::All,
246 ClearType::CurrentLine => terminal::ClearType::CurrentLine,
247 ClearType::PreviousChar => {
248 let stdout = io::stdout();
249 let mut stdout = stdout.lock();
250 stdout.write_all(b"\x08 \x08")?;
251 return self.maybe_flush(stdout);
252 }
253 ClearType::UntilNewLine => terminal::ClearType::UntilNewLine,
254 };
255 let stdout = io::stdout();
256 let mut stdout = stdout.lock();
257 stdout.queue(terminal::Clear(how))?;
258 if how == terminal::ClearType::All {
259 stdout.queue(cursor::MoveTo(0, 0))?;
260 }
261 self.maybe_flush(stdout)
262 }
263
264 fn color(&self) -> (Option<u8>, Option<u8>) {
265 (self.fg_color, self.bg_color)
266 }
267
268 fn set_color(&mut self, fg: Option<u8>, bg: Option<u8>) -> io::Result<()> {
269 if fg == self.fg_color && bg == self.bg_color {
270 return Ok(());
271 }
272
273 let stdout = io::stdout();
274 let mut stdout = stdout.lock();
275 if fg != self.fg_color {
276 let ct_fg = match fg {
277 None => style::Color::Reset,
278 Some(color) => style::Color::AnsiValue(color),
279 };
280 stdout.queue(style::SetForegroundColor(ct_fg))?;
281 self.fg_color = fg;
282 }
283 if bg != self.bg_color {
284 let ct_bg = match bg {
285 None => style::Color::Reset,
286 Some(color) => style::Color::AnsiValue(color),
287 };
288 stdout.queue(style::SetBackgroundColor(ct_bg))?;
289 self.bg_color = bg;
290 }
291 self.maybe_flush(stdout)
292 }
293
294 fn enter_alt(&mut self) -> io::Result<()> {
295 if !self.alt_active {
296 let stdout = io::stdout();
297 let mut stdout = stdout.lock();
298 stdout.queue(terminal::EnterAlternateScreen)?;
299 self.alt_active = true;
300 self.maybe_flush(stdout)
301 } else {
302 Ok(())
303 }
304 }
305
306 fn hide_cursor(&mut self) -> io::Result<()> {
307 if self.cursor_visible {
308 let stdout = io::stdout();
309 let mut stdout = stdout.lock();
310 stdout.queue(cursor::Hide)?;
311 self.cursor_visible = false;
312 self.maybe_flush(stdout)
313 } else {
314 Ok(())
315 }
316 }
317
318 fn is_interactive(&self) -> bool {
319 self.is_tty
320 }
321
322 fn leave_alt(&mut self) -> io::Result<()> {
323 if self.alt_active {
324 let stdout = io::stdout();
325 let mut stdout = stdout.lock();
326 stdout.queue(terminal::LeaveAlternateScreen)?;
327 self.alt_active = false;
328 self.maybe_flush(stdout)
329 } else {
330 Ok(())
331 }
332 }
333
334 fn locate(&mut self, pos: CharsXY) -> io::Result<()> {
335 #[cfg(debug_assertions)]
336 {
337 let size = self.size_chars()?;
338 assert!(pos.x < size.x);
339 assert!(pos.y < size.y);
340 }
341
342 let stdout = io::stdout();
343 let mut stdout = stdout.lock();
344 stdout.queue(cursor::MoveTo(pos.x, pos.y))?;
345 self.maybe_flush(stdout)
346 }
347
348 fn move_within_line(&mut self, off: i16) -> io::Result<()> {
349 let stdout = io::stdout();
350 let mut stdout = stdout.lock();
351 match off.cmp(&0) {
352 Ordering::Less => stdout.queue(cursor::MoveLeft(-off as u16)),
353 Ordering::Equal => return Ok(()),
354 Ordering::Greater => stdout.queue(cursor::MoveRight(off as u16)),
355 }?;
356 self.maybe_flush(stdout)
357 }
358
359 fn print(&mut self, text: &str) -> io::Result<()> {
360 let text = remove_control_chars(text.to_owned());
361
362 let stdout = io::stdout();
363 let mut stdout = stdout.lock();
364 stdout.write_all(text.as_bytes())?;
365 if self.is_tty {
366 stdout.write_all(b"\r\n")?;
367 } else {
368 stdout.write_all(b"\n")?;
369 }
370 Ok(())
371 }
372
373 async fn poll_key(&mut self) -> io::Result<Option<Key>> {
374 match self.on_key_rx.try_recv() {
375 Ok(k) => Ok(Some(k)),
376 Err(TryRecvError::Empty) => Ok(None),
377 Err(TryRecvError::Closed) => Ok(Some(Key::Eof)),
378 }
379 }
380
381 async fn read_key(&mut self) -> io::Result<Key> {
382 match self.on_key_rx.recv().await {
383 Ok(k) => Ok(k),
384 Err(_) => Ok(Key::Eof),
385 }
386 }
387
388 fn show_cursor(&mut self) -> io::Result<()> {
389 if !self.cursor_visible {
390 let stdout = io::stdout();
391 let mut stdout = stdout.lock();
392 stdout.queue(cursor::Show)?;
393 self.cursor_visible = true;
394 self.maybe_flush(stdout)
395 } else {
396 Ok(())
397 }
398 }
399
400 fn size_chars(&self) -> io::Result<CharsXY> {
401 let lines = get_env_var_as_u16("LINES");
405 let columns = get_env_var_as_u16("COLUMNS");
406 let size = match (lines, columns) {
407 (Some(l), Some(c)) => CharsXY::new(c, l),
408 (l, c) => {
409 let (actual_columns, actual_lines) = terminal::size()?;
410 CharsXY::new(c.unwrap_or(actual_columns), l.unwrap_or(actual_lines))
411 }
412 };
413 Ok(size)
414 }
415
416 fn write(&mut self, text: &str) -> io::Result<()> {
417 let text = remove_control_chars(text.to_owned());
418
419 let stdout = io::stdout();
420 let mut stdout = stdout.lock();
421 stdout.write_all(text.as_bytes())?;
422 self.maybe_flush(stdout)
423 }
424
425 fn sync_now(&mut self) -> io::Result<()> {
426 if self.sync_enabled {
427 Ok(())
428 } else {
429 io::stdout().flush()
430 }
431 }
432
433 fn set_sync(&mut self, enabled: bool) -> io::Result<bool> {
434 if !self.sync_enabled {
435 io::stdout().flush()?;
436 }
437 let previous = self.sync_enabled;
438 self.sync_enabled = enabled;
439 Ok(previous)
440 }
441}