use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers};
use crossterm::style::Print;
use crossterm::terminal::{self, disable_raw_mode, enable_raw_mode, ClearType};
use crossterm::{cursor, Command, QueueableCommand};
use crate::Error;
#[derive(Debug)]
pub struct TermSize {
pub width: u16,
pub height: u16,
}
impl TermSize {
pub fn new(width: u16, height: u16) -> Self {
Self { width, height }
}
}
#[derive(Debug)]
pub struct CursorPosition {
pub col: u16,
pub row: u16,
}
impl CursorPosition {
pub fn new(col: u16, row: u16) -> Self {
Self { col, row }
}
}
pub trait Terminal<T: std::io::Write> {
fn writer(&mut self) -> &mut T;
fn size(&self) -> Result<TermSize, Error>;
fn enable_raw(&mut self) -> Result<(), Error>;
fn disable_raw(&mut self) -> Result<(), Error>;
fn cursor_show(&mut self) -> Result<(), Error>;
fn cursor_hide(&mut self) -> Result<(), Error>;
fn cursor_pos(&self) -> Result<CursorPosition, Error>;
fn move_to(&mut self, col: u16, row: u16) -> Result<(), Error>;
fn move_column(&mut self, to: u16) -> Result<(), Error>;
fn move_next_line(&mut self, to: u16) -> Result<(), Error>;
fn move_previous_line(&mut self, to: u16) -> Result<(), Error>;
fn scroll_up(&mut self, row: u16) -> Result<(), Error>;
fn scroll_down(&mut self, row: u16) -> Result<(), Error>;
fn clear(&mut self) -> Result<(), Error>;
fn clear_purge(&mut self) -> Result<(), Error>;
fn clear_current_line(&mut self) -> Result<(), Error>;
fn clear_cursor_up(&mut self) -> Result<(), Error>;
fn clear_cursor_down(&mut self) -> Result<(), Error>;
fn write(&mut self, value: &str) -> Result<(), Error>;
fn writeln(&mut self, value: &str) -> Result<(), Error>;
fn flush(&mut self) -> Result<(), Error>;
fn read_key(&mut self) -> Result<(KeyCode, KeyModifiers), Error>;
}
pub struct Term<T: std::io::Write> {
writer: T,
}
impl<T: std::io::Write> Term<T> {
pub fn new(writer: T) -> Self {
Self { writer }
}
fn cmd(&mut self, command: impl Command) -> Result<(), Error> {
self.writer.queue(command)?;
Ok(())
}
}
impl Term<std::io::Stderr> {
pub fn stderr() -> Self {
Self::new(std::io::stderr())
}
}
impl Term<std::io::Stdout> {
pub fn stdout() -> Self {
Self::new(std::io::stdout())
}
}
impl Default for Term<std::io::Stderr> {
fn default() -> Self {
Self::stderr()
}
}
impl<T: std::io::Write> Terminal<T> for Term<T> {
fn writer(&mut self) -> &mut T {
&mut self.writer
}
fn size(&self) -> Result<TermSize, Error> {
Ok(terminal::size().map(|(w, h)| TermSize::new(w, h))?)
}
fn enable_raw(&mut self) -> Result<(), Error> {
enable_raw_mode()?;
Ok(())
}
fn disable_raw(&mut self) -> Result<(), Error> {
disable_raw_mode()?;
Ok(())
}
fn cursor_show(&mut self) -> Result<(), Error> {
self.cmd(cursor::Show)
}
fn cursor_hide(&mut self) -> Result<(), Error> {
self.cmd(cursor::Hide)
}
fn cursor_pos(&self) -> Result<CursorPosition, Error> {
cursor::position()
.map(|(col, row)| CursorPosition::new(col, row))
.map_err(|err| err.into())
}
fn move_to(&mut self, col: u16, row: u16) -> Result<(), Error> {
self.cmd(cursor::MoveTo(col, row))
}
fn move_column(&mut self, to: u16) -> Result<(), Error> {
self.cmd(cursor::MoveToColumn(to))
}
fn move_next_line(&mut self, to: u16) -> Result<(), Error> {
self.cmd(cursor::MoveToNextLine(to))
}
fn move_previous_line(&mut self, to: u16) -> Result<(), Error> {
self.cmd(cursor::MoveToPreviousLine(to))
}
fn scroll_up(&mut self, row: u16) -> Result<(), Error> {
self.cmd(terminal::ScrollUp(row))
}
fn scroll_down(&mut self, row: u16) -> Result<(), Error> {
self.cmd(terminal::ScrollDown(row))
}
fn clear(&mut self) -> Result<(), Error> {
self.cmd(terminal::Clear(ClearType::All))?;
self.move_to(0, 0)?;
Ok(())
}
fn clear_purge(&mut self) -> Result<(), Error> {
self.cmd(terminal::Clear(ClearType::Purge))
}
fn clear_current_line(&mut self) -> Result<(), Error> {
self.cmd(terminal::Clear(ClearType::CurrentLine))
}
fn clear_cursor_up(&mut self) -> Result<(), Error> {
self.cmd(terminal::Clear(ClearType::FromCursorUp))
}
fn clear_cursor_down(&mut self) -> Result<(), Error> {
self.cmd(terminal::Clear(ClearType::FromCursorDown))
}
fn write(&mut self, value: &str) -> Result<(), Error> {
self.cmd(Print(value))?;
Ok(())
}
fn writeln(&mut self, value: &str) -> Result<(), Error> {
for line in value.to_string().lines() {
self.write(&format!("{}\n", line))?;
self.move_column(0)?;
}
Ok(())
}
fn flush(&mut self) -> Result<(), Error> {
self.writer.flush()?;
Ok(())
}
fn read_key(&mut self) -> Result<(KeyCode, KeyModifiers), Error> {
loop {
if let Event::Key(KeyEvent {
code,
modifiers,
kind,
..
}) = event::read()?
{
if kind == event::KeyEventKind::Press {
return Ok((code, modifiers));
}
}
}
}
}