2022-08-01 06:58:07 -07:00
|
|
|
use crate::Error;
|
|
|
|
use crate::Terminal;
|
2022-07-29 06:17:30 -07:00
|
|
|
use crossterm::cursor::*;
|
|
|
|
use crossterm::style::*;
|
|
|
|
use crossterm::terminal::*;
|
|
|
|
|
2022-08-06 09:43:47 -07:00
|
|
|
/// A trait for performance optimized terminal output.
|
|
|
|
///
|
|
|
|
/// All commands are queue and have to be executed using [Printer::flush].
|
2022-08-01 03:48:24 -07:00
|
|
|
#[cfg_attr(test, mockall::automock)]
|
2022-07-29 06:17:30 -07:00
|
|
|
pub trait Printer {
|
2022-08-06 09:43:47 -07:00
|
|
|
/// Shows the cursor if it isn't visible.
|
2022-07-29 06:17:30 -07:00
|
|
|
fn show_cursor(&mut self) -> Result<(), Error>;
|
2022-08-06 09:43:47 -07:00
|
|
|
/// Hides the cursor if it is visible.
|
2022-07-29 06:17:30 -07:00
|
|
|
fn hide_cursor(&mut self) -> Result<(), Error>;
|
2022-08-06 09:43:47 -07:00
|
|
|
/// Prints a character.
|
|
|
|
/// # Panics
|
|
|
|
/// Panics if the character is a special character like `ESC`, `DEL` or `NEWLINE`.
|
2022-07-29 06:17:30 -07:00
|
|
|
fn print(&mut self, char: char) -> Result<(), Error>;
|
2022-08-06 09:43:47 -07:00
|
|
|
/// Moves the cursor to the specified position if it isn't there already.
|
2022-07-29 06:17:30 -07:00
|
|
|
fn move_to(&mut self, x: u16, y: u16) -> Result<(), Error>;
|
2022-08-06 09:43:47 -07:00
|
|
|
/// Returns the size of the terminal.
|
2022-07-29 06:17:30 -07:00
|
|
|
fn size(&self) -> Result<(u16, u16), Error>;
|
2022-08-06 09:43:47 -07:00
|
|
|
/// Sets the foreground color of the terminal.
|
2022-07-29 06:17:30 -07:00
|
|
|
fn set_foreground(&mut self, color: Color) -> Result<(), Error>;
|
2022-08-06 09:43:47 -07:00
|
|
|
/// Clears the terminal content.
|
2022-07-29 06:17:30 -07:00
|
|
|
fn clear(&mut self) -> Result<(), Error>;
|
2022-08-06 09:43:47 -07:00
|
|
|
/// Flushes all queue commands.
|
2022-07-29 06:17:30 -07:00
|
|
|
fn flush(&mut self) -> Result<(), Error>;
|
|
|
|
}
|
|
|
|
|
2022-08-06 09:43:47 -07:00
|
|
|
/// The implementation of [Printer].
|
2022-07-29 06:17:30 -07:00
|
|
|
pub struct PrinterImpl<T> {
|
|
|
|
term: T,
|
2022-08-01 05:03:02 -07:00
|
|
|
position: (u16, u16),
|
2022-07-29 06:17:30 -07:00
|
|
|
cursor: Option<bool>,
|
|
|
|
foreground: Option<Color>,
|
|
|
|
}
|
|
|
|
|
2022-08-01 05:03:02 -07:00
|
|
|
impl<T: Terminal> PrinterImpl<T> {
|
|
|
|
pub fn new(term: T) -> Result<Self, Error> {
|
|
|
|
let position = term.position()?;
|
|
|
|
|
|
|
|
Ok(Self {
|
2022-07-29 06:17:30 -07:00
|
|
|
term,
|
2022-08-01 05:03:02 -07:00
|
|
|
position,
|
2022-07-29 06:17:30 -07:00
|
|
|
cursor: None,
|
|
|
|
foreground: None,
|
2022-08-01 05:03:02 -07:00
|
|
|
})
|
2022-07-29 06:17:30 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: Terminal> Printer for PrinterImpl<T> {
|
|
|
|
fn show_cursor(&mut self) -> Result<(), Error> {
|
|
|
|
if self.cursor != Some(true) {
|
|
|
|
self.cursor = Some(true);
|
|
|
|
self.term.queue(Show)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn hide_cursor(&mut self) -> Result<(), Error> {
|
|
|
|
if self.cursor != Some(false) {
|
|
|
|
self.cursor = Some(false);
|
|
|
|
self.term.queue(Hide)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn print(&mut self, char: char) -> Result<(), Error> {
|
2022-08-01 05:03:02 -07:00
|
|
|
if char < '\u{20}' || char == '\u{7F}' {
|
|
|
|
return Err("Special chars can't be printed.".into());
|
|
|
|
}
|
|
|
|
self.position.0 += 1;
|
2022-07-29 06:17:30 -07:00
|
|
|
self.term.queue(Print(char))?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn move_to(&mut self, x: u16, y: u16) -> Result<(), Error> {
|
2022-08-01 05:03:02 -07:00
|
|
|
if self.position != (x, y) {
|
|
|
|
self.position = (x, y);
|
2022-07-29 06:17:30 -07:00
|
|
|
self.term.queue(MoveTo(x, y))?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn size(&self) -> Result<(u16, u16), Error> {
|
|
|
|
self.term.size()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_foreground(&mut self, color: Color) -> Result<(), Error> {
|
2022-08-01 05:03:02 -07:00
|
|
|
if self.foreground != Some(color) {
|
2022-07-29 06:17:30 -07:00
|
|
|
self.foreground = Some(color);
|
|
|
|
self.term.queue(SetForegroundColor(color))?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear(&mut self) -> Result<(), Error> {
|
|
|
|
self.term.queue(Clear(ClearType::Purge))?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn flush(&mut self) -> Result<(), Error> {
|
|
|
|
self.term.flush()?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
2022-08-01 06:58:07 -07:00
|
|
|
use crate::MockTerminal;
|
2022-07-29 06:17:30 -07:00
|
|
|
use mockall::predicate::eq;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn show_cursor() {
|
|
|
|
let mut mock = MockTerminal::new();
|
2022-08-01 05:03:02 -07:00
|
|
|
mock.expect_position().returning(|| Ok((0, 0)));
|
2022-07-29 06:17:30 -07:00
|
|
|
mock.expect_queue()
|
|
|
|
.with(eq(Show))
|
|
|
|
.once()
|
|
|
|
.returning(|_| Ok(()));
|
|
|
|
|
2022-08-01 05:03:02 -07:00
|
|
|
PrinterImpl::new(mock).unwrap().show_cursor().unwrap();
|
2022-07-29 06:17:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn show_cursor_twice_queues_once() {
|
|
|
|
let mut mock = MockTerminal::new();
|
2022-08-01 05:03:02 -07:00
|
|
|
mock.expect_position().returning(|| Ok((0, 0)));
|
2022-07-29 06:17:30 -07:00
|
|
|
mock.expect_queue()
|
|
|
|
.with(eq(Show))
|
|
|
|
.once()
|
|
|
|
.returning(|_| Ok(()));
|
|
|
|
|
2022-08-01 05:03:02 -07:00
|
|
|
let mut printer = PrinterImpl::new(mock).unwrap();
|
2022-07-29 06:17:30 -07:00
|
|
|
|
|
|
|
printer.show_cursor().unwrap();
|
|
|
|
printer.show_cursor().unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn show_cursor_after_hiding_queues_show() {
|
|
|
|
let mut mock = MockTerminal::new();
|
2022-08-01 05:03:02 -07:00
|
|
|
mock.expect_position().returning(|| Ok((0, 0)));
|
2022-07-29 06:17:30 -07:00
|
|
|
mock.expect_queue()
|
|
|
|
.with(eq(Show))
|
|
|
|
.once()
|
|
|
|
.returning(|_| Ok(()));
|
|
|
|
mock.expect_queue().with(eq(Hide)).returning(|_| Ok(()));
|
|
|
|
|
2022-08-01 05:03:02 -07:00
|
|
|
let mut printer = PrinterImpl::new(mock).unwrap();
|
2022-07-29 06:17:30 -07:00
|
|
|
|
|
|
|
printer.hide_cursor().unwrap();
|
|
|
|
printer.show_cursor().unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn hide_cursor() {
|
|
|
|
let mut mock = MockTerminal::new();
|
2022-08-01 05:03:02 -07:00
|
|
|
mock.expect_position().returning(|| Ok((0, 0)));
|
2022-07-29 06:17:30 -07:00
|
|
|
mock.expect_queue()
|
|
|
|
.with(eq(Hide))
|
|
|
|
.once()
|
|
|
|
.returning(|_| Ok(()));
|
|
|
|
|
2022-08-01 05:03:02 -07:00
|
|
|
PrinterImpl::new(mock).unwrap().hide_cursor().unwrap();
|
2022-07-29 06:17:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn hide_cursor_twice_queues_once() {
|
|
|
|
let mut mock = MockTerminal::new();
|
2022-08-01 05:03:02 -07:00
|
|
|
mock.expect_position().returning(|| Ok((0, 0)));
|
2022-07-29 06:17:30 -07:00
|
|
|
mock.expect_queue()
|
|
|
|
.with(eq(Hide))
|
|
|
|
.once()
|
|
|
|
.returning(|_| Ok(()));
|
|
|
|
|
2022-08-01 05:03:02 -07:00
|
|
|
let mut printer = PrinterImpl::new(mock).unwrap();
|
2022-07-29 06:17:30 -07:00
|
|
|
|
|
|
|
printer.hide_cursor().unwrap();
|
|
|
|
printer.hide_cursor().unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn hide_cursor_after_showing_queues_hide() {
|
|
|
|
let mut mock = MockTerminal::new();
|
2022-08-01 05:03:02 -07:00
|
|
|
mock.expect_position().returning(|| Ok((0, 0)));
|
2022-07-29 06:17:30 -07:00
|
|
|
mock.expect_queue()
|
|
|
|
.with(eq(Show))
|
|
|
|
.once()
|
|
|
|
.returning(|_| Ok(()));
|
|
|
|
mock.expect_queue().with(eq(Hide)).returning(|_| Ok(()));
|
|
|
|
|
2022-08-01 05:03:02 -07:00
|
|
|
let mut printer = PrinterImpl::new(mock).unwrap();
|
2022-07-29 06:17:30 -07:00
|
|
|
|
|
|
|
printer.show_cursor().unwrap();
|
|
|
|
printer.hide_cursor().unwrap();
|
|
|
|
}
|
|
|
|
|
2022-08-01 05:03:02 -07:00
|
|
|
#[test]
|
|
|
|
fn set_foreground() {
|
|
|
|
let mut mock = MockTerminal::new();
|
|
|
|
mock.expect_position().returning(|| Ok((0, 0)));
|
|
|
|
mock.expect_queue()
|
|
|
|
.with(eq(SetForegroundColor(Color::Blue)))
|
|
|
|
.once()
|
|
|
|
.returning(|_| Ok(()));
|
|
|
|
|
|
|
|
PrinterImpl::new(mock)
|
|
|
|
.unwrap()
|
|
|
|
.set_foreground(Color::Blue)
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn set_foreground_twice_queues_once() {
|
|
|
|
let mut mock = MockTerminal::new();
|
|
|
|
mock.expect_position().returning(|| Ok((0, 0)));
|
|
|
|
mock.expect_queue()
|
|
|
|
.once()
|
|
|
|
.returning(|_: SetForegroundColor| Ok(()));
|
|
|
|
|
|
|
|
let mut printer = PrinterImpl::new(mock).unwrap();
|
|
|
|
|
|
|
|
printer.set_foreground(Color::Red).unwrap();
|
|
|
|
printer.set_foreground(Color::Red).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn set_foreground_different_color_queues() {
|
|
|
|
let mut mock = MockTerminal::new();
|
|
|
|
mock.expect_position().returning(|| Ok((0, 0)));
|
|
|
|
mock.expect_queue()
|
|
|
|
.times(3)
|
|
|
|
.returning(|_: SetForegroundColor| Ok(()));
|
|
|
|
|
|
|
|
let mut printer = PrinterImpl::new(mock).unwrap();
|
|
|
|
|
|
|
|
printer.set_foreground(Color::Red).unwrap();
|
|
|
|
printer.set_foreground(Color::Blue).unwrap();
|
|
|
|
printer.set_foreground(Color::Red).unwrap();
|
|
|
|
}
|
|
|
|
|
2022-07-29 06:17:30 -07:00
|
|
|
#[test]
|
|
|
|
fn print() {
|
|
|
|
let mut mock = MockTerminal::new();
|
2022-08-01 05:03:02 -07:00
|
|
|
mock.expect_position().returning(|| Ok((0, 0)));
|
2022-07-29 06:17:30 -07:00
|
|
|
mock.expect_queue()
|
|
|
|
.with(eq(Print('R')))
|
|
|
|
.once()
|
|
|
|
.returning(|_| Ok(()));
|
|
|
|
|
2022-08-01 05:03:02 -07:00
|
|
|
PrinterImpl::new(mock).unwrap().print('R').unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn print_moves_cursor() {
|
|
|
|
let mut mock = MockTerminal::new();
|
|
|
|
mock.expect_position().returning(|| Ok((2, 4)));
|
|
|
|
mock.expect_queue()
|
|
|
|
.times(3)
|
|
|
|
.returning(|_: Print<char>| Ok(()));
|
|
|
|
|
|
|
|
let mut printer = PrinterImpl::new(mock).unwrap();
|
|
|
|
|
|
|
|
printer.print('A').unwrap();
|
|
|
|
printer.print('B').unwrap();
|
|
|
|
printer.print('C').unwrap();
|
|
|
|
printer.move_to(5, 4).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn print_special_char_fails() {
|
|
|
|
let mut mock = MockTerminal::new();
|
|
|
|
mock.expect_position().returning(|| Ok((2, 4)));
|
|
|
|
|
|
|
|
let mut printer = PrinterImpl::new(mock).unwrap();
|
|
|
|
|
|
|
|
assert!(printer.print('\u{0}').is_err());
|
|
|
|
assert!(printer.print('\u{1F}').is_err());
|
|
|
|
assert!(printer.print('\u{7F}').is_err());
|
2022-07-29 06:17:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn move_to_different_position_queues() {
|
|
|
|
let mut mock = MockTerminal::new();
|
|
|
|
mock.expect_position().returning(|| Ok((7, 2)));
|
|
|
|
mock.expect_queue()
|
|
|
|
.with(eq(MoveTo(5, 4)))
|
|
|
|
.once()
|
|
|
|
.returning(|_| Ok(()));
|
|
|
|
|
2022-08-01 05:03:02 -07:00
|
|
|
PrinterImpl::new(mock).unwrap().move_to(5, 4).unwrap();
|
2022-07-29 06:17:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn move_to_same_position_does_not_queue() {
|
|
|
|
let mut mock = MockTerminal::new();
|
|
|
|
mock.expect_position().returning(|| Ok((3, 13)));
|
|
|
|
|
2022-08-01 05:03:02 -07:00
|
|
|
PrinterImpl::new(mock).unwrap().move_to(3, 13).unwrap();
|
2022-07-29 06:17:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn size() {
|
|
|
|
let mut mock = MockTerminal::new();
|
2022-08-01 05:03:02 -07:00
|
|
|
mock.expect_position().returning(|| Ok((0, 0)));
|
2022-07-29 06:17:30 -07:00
|
|
|
mock.expect_size().returning(|| Ok((14, 76)));
|
|
|
|
|
2022-08-01 05:03:02 -07:00
|
|
|
assert_eq!((14, 76), PrinterImpl::new(mock).unwrap().size().unwrap());
|
2022-07-29 06:17:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn clear() {
|
|
|
|
let mut mock = MockTerminal::new();
|
2022-08-01 05:03:02 -07:00
|
|
|
mock.expect_position().returning(|| Ok((0, 0)));
|
2022-07-29 06:17:30 -07:00
|
|
|
mock.expect_queue()
|
|
|
|
.with(eq(Clear(ClearType::Purge)))
|
|
|
|
.once()
|
|
|
|
.returning(|_| Ok(()));
|
|
|
|
|
2022-08-01 05:03:02 -07:00
|
|
|
PrinterImpl::new(mock).unwrap().clear().unwrap();
|
2022-07-29 06:17:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn flush() {
|
|
|
|
let mut mock = MockTerminal::new();
|
2022-08-01 05:03:02 -07:00
|
|
|
mock.expect_position().returning(|| Ok((0, 0)));
|
2022-07-29 06:17:30 -07:00
|
|
|
mock.expect_flush().once().returning(|| Ok(()));
|
|
|
|
|
2022-08-01 05:03:02 -07:00
|
|
|
PrinterImpl::new(mock).unwrap().flush().unwrap();
|
2022-07-29 06:17:30 -07:00
|
|
|
}
|
|
|
|
}
|