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