use std::cmp::min;
use std::iter::repeat;
use std::io;
use std::sync::{Arc, Mutex, MutexGuard};
use std::time::Duration;
use crate::terminal::{
CursorMode, RawRead, SignalSet, Size,
Terminal, TerminalReader, TerminalWriter,
};
pub const DEFAULT_SIZE: Size = Size{
columns: 80,
lines: 24,
};
#[derive(Clone, Debug)]
pub struct MemoryTerminal {
write: Arc<Mutex<Writer>>,
read: Arc<Mutex<Reader>>,
}
#[derive(Debug)]
struct Writer {
memory: Vec<char>,
input: Vec<u8>,
col: usize,
line: usize,
cursor_mode: CursorMode,
size: Size,
}
#[derive(Debug)]
struct Reader {
input: Vec<u8>,
resize: Option<Size>,
}
pub struct MemoryReadGuard<'a>(MutexGuard<'a, Reader>);
pub struct MemoryWriteGuard<'a>(MutexGuard<'a, Writer>);
impl MemoryTerminal {
pub fn new() -> MemoryTerminal {
MemoryTerminal::default()
}
pub fn with_size(size: Size) -> MemoryTerminal {
MemoryTerminal{
read: Arc::new(Mutex::new(Reader::new())),
write: Arc::new(Mutex::new(Writer::new(size))),
}
}
pub fn clear_all(&self) {
self.lock_writer().clear_all();
}
pub fn clear_to_end(&self) {
self.lock_writer().clear_to_end();
}
pub fn clear_input(&self) {
self.lock_reader().clear_input();
}
pub fn has_input(&self) -> bool {
self.lock_reader().has_input()
}
pub fn lines(&self) -> Lines {
Lines{
writer: self.lock_writer(),
line: 0,
}
}
pub fn move_up(&self, n: usize) {
self.lock_writer().move_up(n);
}
pub fn move_down(&self, n: usize) {
self.lock_writer().move_down(n);
}
pub fn move_left(&self, n: usize) {
self.lock_writer().move_left(n);
}
pub fn move_right(&self, n: usize) {
self.lock_writer().move_right(n);
}
pub fn move_to_first_column(&self) {
self.lock_writer().move_to_first_column()
}
pub fn push_input(&self, s: &str) {
self.lock_reader().push_input(s.as_bytes());
}
pub fn read_input(&self, buf: &mut [u8]) -> usize {
self.lock_reader().read_input(buf)
}
pub fn resize(&self, new_size: Size) {
self.lock_writer().resize(new_size);
self.lock_reader().resize(new_size);
}
pub fn scroll_up(&self, n: usize) {
self.lock_writer().scroll_up(n);
}
pub fn cursor(&self) -> (usize, usize) {
let r = self.lock_writer();
(r.line, r.col)
}
pub fn set_cursor_mode(&self, mode: CursorMode) {
self.lock_writer().set_cursor_mode(mode);
}
pub fn cursor_mode(&self) -> CursorMode {
self.lock_writer().cursor_mode()
}
pub fn size(&self) -> Size {
self.lock_writer().size
}
pub fn write(&self, s: &str) {
self.lock_writer().write(s);
}
fn lock_reader(&self) -> MutexGuard<Reader> {
self.read.lock().unwrap()
}
fn lock_writer(&self) -> MutexGuard<Writer> {
self.write.lock().unwrap()
}
}
impl Default for MemoryTerminal {
fn default() -> MemoryTerminal {
MemoryTerminal::with_size(DEFAULT_SIZE)
}
}
impl Reader {
fn new() -> Reader {
Reader{
input: Vec::new(),
resize: None,
}
}
fn has_input(&mut self) -> bool {
self.resize.is_some() || !self.input.is_empty()
}
fn clear_input(&mut self) {
self.input.clear();
}
fn push_input(&mut self, bytes: &[u8]) {
self.input.extend(bytes);
}
fn read_input(&mut self, buf: &mut [u8]) -> usize {
let n = min(buf.len(), self.input.len());
buf[..n].copy_from_slice(&self.input[..n]);
let _ = self.input.drain(..n);
n
}
fn resize(&mut self, size: Size) {
self.resize = Some(size);
}
}
impl Writer {
fn new(size: Size) -> Writer {
assert!(size.lines != 0 && size.columns != 0,
"zero-area terminal buffer: {:?}", size);
let n_chars = size.lines * size.columns;
Writer{
memory: vec![' '; n_chars],
input: Vec::new(),
col: 0,
line: 0,
cursor_mode: CursorMode::Normal,
size: size,
}
}
fn clear_all(&mut self) {
for ch in &mut self.memory {
*ch = ' ';
}
self.col = 0;
self.line = 0;
}
fn clear_to_end(&mut self) {
let idx = self.index();
for ch in &mut self.memory[idx..] {
*ch = ' ';
}
}
fn move_up(&mut self, n: usize) {
self.line = self.line.saturating_sub(n);
}
fn move_down(&mut self, n: usize) {
self.line = min(self.size.lines - 1, self.line + n);
}
fn move_left(&mut self, n: usize) {
self.col = self.col.saturating_sub(n);
}
fn move_right(&mut self, n: usize) {
self.col = min(self.size.columns - 1, self.col + n);
}
fn move_to_first_column(&mut self) {
self.col = 0;
}
fn resize(&mut self, new_size: Size) {
if self.size != new_size {
let n_chars = new_size.lines.checked_mul(new_size.columns)
.unwrap_or_else(|| panic!("terminal size too large: {:?}", new_size));
assert!(n_chars != 0, "zero-area terminal buffer: {:?}", new_size);
let mut new_buf = Vec::with_capacity(n_chars);
let (n_copy, n_extra) = if new_size.columns > self.size.columns {
(self.size.columns, new_size.columns - self.size.columns)
} else {
(new_size.columns, 0)
};
for line in self.memory.chunks(self.size.columns).take(new_size.lines) {
new_buf.extend(&line[..n_copy]);
new_buf.extend(repeat(' ').take(n_extra));
}
if new_size.lines > self.size.lines {
let n_lines = new_size.lines - self.size.lines;
new_buf.extend(repeat(' ').take(n_lines * new_size.columns));
}
debug_assert_eq!(new_buf.len(), n_chars);
self.col = min(self.col, new_size.columns);
self.line = min(self.line, new_size.lines);
self.size = new_size;
self.memory = new_buf;
}
}
fn scroll_up(&mut self, n: usize) {
let chars = min(self.memory.len(), self.size.columns * n);
self.memory.drain(..chars);
self.memory.extend(repeat(' ').take(chars));
self.line = self.line.saturating_sub(n);
}
fn set_cursor_mode(&mut self, mode: CursorMode) {
self.cursor_mode = mode;
}
fn cursor_mode(&self) -> CursorMode {
self.cursor_mode
}
fn write(&mut self, s: &str) {
for ch in s.chars() {
if ch == '\n' {
self.advance_line();
} else if ch == '\r' {
self.col = 0;
} else {
self.write_char(ch);
}
}
}
fn advance_line(&mut self) {
self.line += 1;
self.col = 0;
if self.line == self.size.lines {
self.scroll_up(1);
}
}
fn write_char(&mut self, ch: char) {
if self.col >= self.size.columns {
self.advance_line();
}
let idx = self.index();
self.memory[idx] = ch;
self.col += 1;
}
fn index(&self) -> usize {
self.line * self.size.columns + self.col
}
}
pub struct Lines<'a> {
writer: MutexGuard<'a, Writer>,
line: usize,
}
impl<'a> Lines<'a> {
pub fn next(&mut self) -> Option<&[char]> {
if self.line >= self.writer.size.lines {
None
} else {
let start = self.writer.size.columns * self.line;
self.line += 1;
let end = self.writer.size.columns * self.line;
Some(&self.writer.memory[start..end])
}
}
pub fn lines_remaining(&self) -> usize {
self.writer.size.lines - self.line
}
}
impl Terminal for MemoryTerminal {
type PrepareState = ();
fn name(&self) -> &str { "memory-terminal" }
fn lock_read<'a>(&'a self) -> Box<dyn TerminalReader<Self> + 'a> {
Box::new(MemoryReadGuard(self.lock_reader()))
}
fn lock_write<'a>(&'a self) -> Box<dyn TerminalWriter<Self> + 'a> {
Box::new(MemoryWriteGuard(self.lock_writer()))
}
}
impl<'a> TerminalReader<MemoryTerminal> for MemoryReadGuard<'a> {
fn wait_for_input(&mut self, _timeout: Option<Duration>) -> io::Result<bool> {
Ok(!self.0.input.is_empty())
}
fn prepare(&mut self, _block_signals: bool, _report_signals: SignalSet)
-> io::Result<()> { Ok(()) }
unsafe fn prepare_with_lock(&mut self,
_lock: &mut dyn TerminalWriter<MemoryTerminal>,
_block_signals: bool, _report_signals: SignalSet)
-> io::Result<()> { Ok(()) }
fn restore(&mut self, _state: ()) -> io::Result<()> { Ok(()) }
unsafe fn restore_with_lock(&mut self,
_lock: &mut dyn TerminalWriter<MemoryTerminal>, _state: ())
-> io::Result<()> { Ok(()) }
fn read(&mut self, buf: &mut Vec<u8>) -> io::Result<RawRead> {
if let Some(size) = self.0.resize.take() {
return Ok(RawRead::Resize(size));
}
buf.reserve(16);
let cap = buf.capacity();
let len = buf.len();
let n;
unsafe {
buf.set_len(cap);
n = self.0.read_input(&mut buf[len..]);
buf.set_len(len + n);
}
Ok(RawRead::Bytes(n))
}
}
impl<'a> TerminalWriter<MemoryTerminal> for MemoryWriteGuard<'a> {
fn size(&self) -> io::Result<Size> {
Ok(self.0.size)
}
fn clear_screen(&mut self) -> io::Result<()> {
self.0.clear_all();
Ok(())
}
fn clear_to_screen_end(&mut self) -> io::Result<()> {
self.0.clear_to_end();
Ok(())
}
fn move_up(&mut self, n: usize) -> io::Result<()> {
self.0.move_up(n);
Ok(())
}
fn move_down(&mut self, n: usize) -> io::Result<()> {
self.0.move_down(n);
Ok(())
}
fn move_left(&mut self, n: usize) -> io::Result<()> {
self.0.move_left(n);
Ok(())
}
fn move_right(&mut self, n: usize) -> io::Result<()> {
self.0.move_right(n);
Ok(())
}
fn move_to_first_column(&mut self) -> io::Result<()> {
self.0.move_to_first_column();
Ok(())
}
fn set_cursor_mode(&mut self, mode: CursorMode) -> io::Result<()> {
self.0.set_cursor_mode(mode);
Ok(())
}
fn write(&mut self, s: &str) -> io::Result<()> {
self.0.write(s);
Ok(())
}
fn flush(&mut self) -> io::Result<()> { Ok(()) }
}
#[cfg(test)]
mod test {
use super::MemoryTerminal;
use crate::terminal::Size;
fn assert_lines(mem: &MemoryTerminal, tests: &[&str]) {
let mut lines = mem.lines();
let mut test_iter = tests.iter();
while let Some(line) = lines.next() {
let test = test_iter.next().unwrap();
assert!(line.iter().cloned().eq(test.chars()),
"mem: {:?}; tests: {:?}", mem.lock_writer().memory, tests);
}
}
#[test]
fn test_memory_term() {
let mem = MemoryTerminal::with_size(Size{lines: 3, columns: 4});
assert_lines(&mem, &[" "; 3]);
mem.write("ab");
assert_lines(&mem, &["ab ", " ", " "]);
mem.write("c\nd");
assert_lines(&mem, &["abc ", "d ", " "]);
mem.write("efg\nhi");
assert_lines(&mem, &["abc ", "defg", "hi "]);
mem.write("\njk\n");
assert_lines(&mem, &["hi ", "jk ", " "]);
mem.write("\n\n\n\n\nlmno");
assert_lines(&mem, &[" ", " ", "lmno"]);
mem.move_up(1);
mem.move_left(3);
mem.write("xx");
assert_lines(&mem, &[" ", " xx ", "lmno"]);
mem.clear_all();
mem.write("xyz");
assert_lines(&mem, &["xyz ", " ", " "]);
mem.write("\nabcd");
assert_lines(&mem, &["xyz ", "abcd", " "]);
mem.move_to_first_column();
mem.write("ab");
mem.clear_to_end();
assert_lines(&mem, &["xyz ", "ab ", " "]);
mem.move_to_first_column();
mem.move_down(1);
mem.write("c");
mem.move_right(1);
mem.write("d");
assert_lines(&mem, &["xyz ", "ab ", "c d "]);
}
#[test]
fn test_resize() {
let mem = MemoryTerminal::with_size(Size{lines: 3, columns: 4});
assert_lines(&mem, &[" "; 3]);
mem.write("xxxx\nxxxx\nxxxx");
assert_lines(&mem, &["xxxx"; 3]);
mem.resize(Size{lines: 4, columns: 3});
assert_lines(&mem, &["xxx", "xxx", "xxx", " "]);
mem.clear_all();
mem.write("yyy\nyyy\nyyy\nyyy");
assert_lines(&mem, &["yyy"; 4]);
mem.resize(Size{lines: 3, columns: 4});
assert_lines(&mem, &["yyy ", "yyy ", "yyy "]);
}
}