//! Text out (the log protocol) and lines in, on a screen and keyboard //! behind `Console`. use server_framework::{Encode, MALFORMED, Server}; use types::bounded::CapSlot; use types::log::{TEXT_BYTES, text_of, text_words}; use crate::grid::Grid; /// What the terminal draws on and reads from. pub trait Console { fn draw_row(&mut self, row: usize, text: &[u8]); /// Shows the cursor at a cell, over what `draw_row` drew there. fn draw_cursor(&mut self, col: usize, row: usize, under: u8); fn flush(&mut self); /// Moves everything drawn up by `rows` text rows, blank below. fn scroll(&mut self, rows: usize); /// Soon calls `TerminalServer::signal`, to flush a burst of output once. fn flush_later(&mut self); /// Blocks until a key is typed: a byte, `\n` for Enter, 8 for Backspace. fn next_key(&mut self) -> u8; /// A key already typed, without waiting. fn poll_key(&mut self) -> Option; /// Takes input focus back from whoever took it, and clears the screen. fn take_focus(&mut self); fn interrupt(&mut self, notification: CapSlot); /// Lets go of a notification it was given. fn forget(&mut self, notification: CapSlot); } pub const BACKSPACE: u8 = 8; const MAX_LINE: usize = 256; /// Keys typed while no line is being read, kept for the next one. const TYPEAHEAD: usize = 64; pub struct TerminalServer { console: C, grid: Grid, /// A typed line not yet handed out in full, and how much of it was. line: [u8; MAX_LINE], line_len: usize, line_read: usize, flush_pending: bool, /// How many cells of each row are drawn on the screen. drawn: [usize; crate::grid::MAX_ROWS], typeahead: [u8; TYPEAHEAD], typeahead_len: usize, /// Told about Ctrl+C typed while no line is being read. on_interrupt: Option, } /// An empty reply, whose tag is not text. pub struct Done; impl Encode for Done { fn encode(&self) -> [usize; 4] { [0; 4] } } impl TerminalServer { pub fn new(console: C, cols: usize, rows: usize) -> Self { let mut s = Self { console, grid: Grid::new(cols, rows), line: [0; MAX_LINE], line_len: 0, line_read: 0, flush_pending: false, drawn: [0; crate::grid::MAX_ROWS], typeahead: [0; TYPEAHEAD], typeahead_len: 0, on_interrupt: None, }; s.redraw(); s.console.flush(); s } pub fn console(&self) -> &C { &self.console } #[cfg(test)] pub fn console_mut_for_test(&mut self) -> &mut C { &mut self.console } pub fn grid(&self) -> &Grid { &self.grid } fn redraw(&mut self) { let scrolled = self.grid.take_scrolled(); if scrolled > 0 { self.console.scroll(scrolled); let rows = self.grid.rows(); self.drawn.copy_within(scrolled.min(rows)..rows, 0); self.drawn[rows - scrolled.min(rows)..rows].fill(0); } let mut dirty = [false; crate::grid::MAX_ROWS]; for r in self.grid.take_dirty() { dirty[r] = true; } for (r, _) in dirty.iter().enumerate().filter(|(_, d)| **d) { // As far as there is text, or as far as was drawn before (so // what is gone is erased): rarely the whole width. let row = self.grid.row(r); let text = row.iter().rposition(|b| *b != b' ').map_or(0, |i| i + 1); let width = text.max(self.drawn[r]).min(row.len()); if width > 0 { self.console.draw_row(r, &row[..width]); } self.drawn[r] = text; } let (col, row) = (self.grid.col, self.grid.row); let under = self.grid.row(row).get(col).copied().unwrap_or(b' '); self.console.draw_cursor(col, row, under); // The cursor cell is drawn too, so it is erased when it moves. self.drawn[row] = self.drawn[row].max(col + 1); } fn write(&mut self, bytes: &[u8]) { for &b in bytes { self.grid.put(b); } self.redraw(); if !self.flush_pending { self.flush_pending = true; self.console.flush_later(); } } /// A key typed ahead, or the next one. fn next_key(&mut self) -> u8 { if self.typeahead_len == 0 { return self.console.next_key(); } let key = self.typeahead[0]; self.typeahead.copy_within(1..self.typeahead_len, 0); self.typeahead_len -= 1; key } /// Keys typed while nobody reads a line: Ctrl+C interrupts, the rest /// wait for the next line. fn keys_arrived(&mut self) { while let Some(key) = self.console.poll_key() { if key == types::terminal::CTRL_C { self.typeahead_len = 0; self.write(b"^C\n"); if let Some(n) = self.on_interrupt { self.console.interrupt(n); } } else if self.typeahead_len < TYPEAHEAD { self.typeahead[self.typeahead_len] = key; self.typeahead_len += 1; } } } /// Edits a line from the keyboard, echoing it (as `*` if `secret`), /// until Enter. Ctrl+C drops it and reads as an empty line. fn read_line(&mut self, secret: bool) { self.console.flush(); // Flushed now; a flush timer still running is harmless. self.flush_pending = false; self.line_len = 0; self.line_read = 0; loop { match self.next_key() { types::terminal::CTRL_C => { self.line[0] = b'\n'; self.line_len = 1; for &b in b"^C\n" { self.grid.put(b); } break; } b'\n' => { self.line[self.line_len] = b'\n'; self.line_len += 1; self.grid.put(b'\n'); break; } BACKSPACE if self.line_len > 0 => { self.line_len -= 1; self.grid.back(); } BACKSPACE => continue, // Room is kept for the closing newline. b if self.line_len + 1 < MAX_LINE && (b' '..=b'~').contains(&b) => { self.line[self.line_len] = b; self.line_len += 1; self.grid.put(if secret { b'*' } else { b }); } _ => continue, } self.redraw(); self.console.flush(); } self.redraw(); self.console.flush(); } /// The next chunk of the typed line, reading one first if none is left. /// A secret line never reaches the screen, and is forgotten once read. fn read(&mut self, secret: bool) -> [usize; 4] { if self.line_read == self.line_len { self.read_line(secret); } let end = (self.line_read + TEXT_BYTES).min(self.line_len); let words = text_words(&self.line[self.line_read..end]); self.line_read = end; if secret && self.line_read == self.line_len { self.line.fill(0); } words } } impl Server for TerminalServer { type Request = Request; type Response = Done; fn handle(&mut self, request: Request) -> Done { match request { Request::Clear => { self.grid.clear(); self.redraw(); self.console.flush(); } } Done } fn handle_call(&mut self, badge: usize, words: [usize; 4], cap: Option) -> ([usize; 4], Option) { let reply = match words[0] { types::terminal::READ => self.read(false), types::terminal::READ_SECRET => self.read(true), types::terminal::REDRAW => { // Taking focus back clears the screen. self.console.take_focus(); self.drawn = [0; crate::grid::MAX_ROWS]; self.grid.touch_all(); self.redraw(); self.console.flush(); [0; 4] } types::terminal::ON_INTERRUPT => match cap { Some(n) if badge & types::terminal::CONTROL != 0 => { if let Some(old) = self.on_interrupt.replace(n) { self.console.forget(old); } [0; 4] } _ => [MALFORMED, 0, 0, 0], }, types::terminal::CLEAR => { self.handle(Request::Clear); [0; 4] } _ => match text_of(words) { Some((bytes, len)) => { self.write(&bytes[..len]); [0; 4] } None => [MALFORMED, 0, 0, 0], }, }; (reply, None) } /// Keys, or the flush timer: show a burst of output in one frame. fn signal(&mut self, bits: usize) { if bits & types::terminal::KEYS_BIT != 0 { self.keys_arrived(); } if bits & types::terminal::TIMER_BIT != 0 { self.flush_pending = false; self.console.flush(); } } } pub enum Request { Clear, } impl server_framework::Decode for Request { fn decode(words: [usize; 4]) -> Option { (words[0] == types::terminal::CLEAR).then_some(Request::Clear) } }