use crossterm::
{
terminal::{ self, ClearType },
cursor,
execute,
event::{ self, Event, KeyEvent },
};
use std::io::{ self, Write, IsTerminal };
use std::time::Duration;
use crate::error::Error;
pub trait TerminalOps: Write
{
fn is_tty( &self ) -> bool;
fn size( &self ) -> io::Result< ( u16, u16 ) >;
fn enable_raw_mode( &mut self ) -> Result< (), Error >;
fn disable_raw_mode( &mut self ) -> Result< (), Error >;
fn clear_screen( &mut self ) -> io::Result< () >;
fn clear_line( &mut self ) -> io::Result< () >;
fn move_cursor( &mut self, col: u16, row: u16 ) -> io::Result< () >;
fn hide_cursor( &mut self ) -> io::Result< () >;
fn show_cursor( &mut self ) -> io::Result< () >;
fn write_str( &mut self, text: &str ) -> io::Result< () >;
fn read_key( &mut self, timeout: Option< Duration > ) -> io::Result< KeyEvent >;
}
pub fn is_tty() -> bool
{
io::stdin().is_terminal()
}
pub fn size() -> io::Result< ( u16, u16 ) >
{
terminal::size()
}
pub struct RawModeGuard
{
was_enabled: bool,
}
impl RawModeGuard
{
pub fn enable() -> Result< Self, Error >
{
if !is_tty()
{
return Err( Error::NoTty );
}
terminal::enable_raw_mode()?;
Ok( Self { was_enabled: true } )
}
#[ allow( dead_code ) ]
pub fn is_enabled( &self ) -> bool
{
self.was_enabled
}
}
impl Drop for RawModeGuard
{
fn drop( &mut self )
{
if self.was_enabled
{
let _ = terminal::disable_raw_mode();
}
}
}
pub struct RealTerminal
{
stdout: io::Stdout,
raw_mode_enabled: bool,
}
impl RealTerminal
{
pub fn new() -> Self
{
Self
{
stdout: io::stdout(),
raw_mode_enabled: false,
}
}
#[ allow( dead_code ) ]
pub fn save_cursor( &mut self ) -> io::Result< () >
{
execute!( self.stdout, cursor::SavePosition )?;
Ok( () )
}
#[ allow( dead_code ) ]
pub fn restore_cursor( &mut self ) -> io::Result< () >
{
execute!( self.stdout, cursor::RestorePosition )?;
Ok( () )
}
}
impl TerminalOps for RealTerminal
{
fn is_tty( &self ) -> bool
{
is_tty()
}
fn size( &self ) -> io::Result< ( u16, u16 ) >
{
size()
}
fn enable_raw_mode( &mut self ) -> Result< (), Error >
{
if !self.is_tty()
{
return Err( Error::NoTty );
}
terminal::enable_raw_mode()?;
self.raw_mode_enabled = true;
Ok( () )
}
fn disable_raw_mode( &mut self ) -> Result< (), Error >
{
if self.raw_mode_enabled
{
terminal::disable_raw_mode()?;
self.raw_mode_enabled = false;
}
Ok( () )
}
fn clear_screen( &mut self ) -> io::Result< () >
{
execute!( self.stdout, terminal::Clear( ClearType::All ) )?;
Ok( () )
}
fn clear_line( &mut self ) -> io::Result< () >
{
execute!( self.stdout, terminal::Clear( ClearType::CurrentLine ) )?;
Ok( () )
}
fn move_cursor( &mut self, col: u16, row: u16 ) -> io::Result< () >
{
execute!( self.stdout, cursor::MoveTo( col, row ) )?;
Ok( () )
}
fn hide_cursor( &mut self ) -> io::Result< () >
{
execute!( self.stdout, cursor::Hide )?;
Ok( () )
}
fn show_cursor( &mut self ) -> io::Result< () >
{
execute!( self.stdout, cursor::Show )?;
Ok( () )
}
fn write_str( &mut self, text: &str ) -> io::Result< () >
{
self.stdout.write_all( text.as_bytes() )?;
Ok( () )
}
fn read_key( &mut self, timeout: Option< Duration > ) -> io::Result< KeyEvent >
{
loop
{
let has_event = if let Some( timeout ) = timeout
{
event::poll( timeout )?
}
else
{
event::poll( Duration::from_secs( 86400 ) )?
};
if has_event
{
if let Event::Key( key_event ) = event::read()?
{
if key_event.kind == event::KeyEventKind::Press
{
return Ok( key_event );
}
}
}
else if timeout.is_some()
{
return Err( io::Error::new( io::ErrorKind::TimedOut, "No key event" ) );
}
}
}
}
impl Drop for RealTerminal
{
fn drop( &mut self )
{
let _ = self.disable_raw_mode();
}
}
impl Default for RealTerminal
{
fn default() -> Self
{
Self::new()
}
}
impl io::Write for RealTerminal
{
fn write( &mut self, buf: &[ u8 ] ) -> io::Result< usize >
{
self.stdout.write( buf )
}
fn flush( &mut self ) -> io::Result< () >
{
self.stdout.flush()
}
}