//! 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, SavedReply}; use types::log::{text_of, text_words}; use crate::grid::Grid; use crate::line::Line; /// 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); /// A key already typed, without waiting: a byte, `\n` for Enter, 8 /// for Backspace. fn poll_key(&mut self) -> Option; /// Keeps the caller of the request being handled, to answer later. fn save_reply(&mut self) -> Option; fn answer(&mut self, reply: SavedReply, words: [usize; 4]); 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; /// Keys typed while no line is being read, kept for the next one. const TYPEAHEAD: usize = 64; /// Callers waiting for a line at once, at most. const READERS: usize = 4; pub struct TerminalServer { console: C, grid: Grid, /// A typed line not yet handed out in full, and how much of it was. line: Line, /// Callers waiting for a line, oldest first, and whether theirs is /// secret: the oldest gets the line being typed. waiting: [Option<(SavedReply, bool)>; READERS], 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: Line::new(), waiting: [None; READERS], 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 one just typed. fn take_key(&mut self) -> Option { if self.typeahead_len == 0 { return self.console.poll_key(); } let key = self.typeahead[0]; self.typeahead.copy_within(1..self.typeahead_len, 0); self.typeahead_len -= 1; Some(key) } /// Keys typed while nobody reads a line: Ctrl+C interrupts, the rest /// wait for the next line. fn keys_arrived(&mut self) { if self.line.typing() { if self.type_keys() { self.serve_waiting(); } return; } 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; } } } fn begin_line(&mut self, secret: bool) { self.console.flush(); // Flushed now; a flush timer still running is harmless. self.flush_pending = false; self.line.start(secret); } /// Feeds the keys there are into the line; whether it is done. fn type_keys(&mut self) -> bool { let mut done = false; while !done && let Some(key) = self.take_key() { done = self.line.key(key, &mut self.grid); self.redraw(); self.console.flush(); } done } /// The oldest waiting caller gets the finished line; once it is all /// read, the next waiting caller's line starts. fn serve_waiting(&mut self) { loop { if self.line.unread() { let Some((reply, _)) = self.waiting[0] else { return }; self.waiting.rotate_left(1); self.waiting[READERS - 1] = None; let chunk = self.line.chunk(); self.console.answer(reply, chunk); // More of it comes when it asks again. if self.line.unread() { return; } } let Some((_, secret)) = self.waiting[0] else { return }; self.begin_line(secret); if !self.type_keys() { return; } } } /// The next chunk of the typed line now, if there is one; otherwise /// the caller is kept and answered once the line is typed. fn read(&mut self, secret: bool) -> Option<[usize; 4]> { if self.line.unread() { let chunk = self.line.chunk(); if !self.line.unread() { self.serve_waiting(); } return Some(chunk); } let nobody_waits = self.waiting[0].is_none(); if nobody_waits && !self.line.typing() { self.begin_line(secret); } if nobody_waits && self.type_keys() { return Some(self.line.chunk()); } let free = self.waiting.iter().position(Option::is_none); match (free, free.and_then(|_| self.console.save_reply())) { (Some(i), Some(reply)) => { self.waiting[i] = Some((reply, secret)); None } // Too many waiting: an empty line. _ => Some(text_words(b"\n")), } } } 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] { // Kept for later: what goes back now is not sent. types::terminal::READ => self.read(false).unwrap_or([0; 4]), types::terminal::READ_SECRET => self.read(true).unwrap_or([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) } }