use crate::buffer::DiffOp;
use crate::event::{Event, Key, KeyEvent, Modifiers, MouseEvent, MouseKind};
use crate::geom::Size;
use crate::Result;
pub trait TerminalBackend {
fn enter(&mut self) -> Result<()>;
fn leave(&mut self) -> Result<()>;
fn size(&self) -> Result<Size>;
fn read_event(&mut self) -> Result<Event>;
fn poll_event(&mut self, timeout: std::time::Duration) -> Result<Option<Event>>;
fn flush(&mut self, ops: &[DiffOp]) -> Result<()>;
}
pub struct TerminalGuard;
impl TerminalGuard {
pub fn new() -> Self {
Self
}
}
impl Drop for TerminalGuard {
fn drop(&mut self) {
let _ = crossterm::terminal::disable_raw_mode();
let _ = crossterm::execute!(
std::io::stdout(),
crossterm::terminal::LeaveAlternateScreen,
crossterm::cursor::Show
);
}
}
pub struct CrosstermBackend {
stdout: std::io::Stdout,
_guard: TerminalGuard,
}
impl CrosstermBackend {
pub fn new() -> Result<Self> {
Ok(Self {
stdout: std::io::stdout(),
_guard: TerminalGuard::new(),
})
}
}
impl TerminalBackend for CrosstermBackend {
fn enter(&mut self) -> Result<()> {
crossterm::terminal::enable_raw_mode()?;
crossterm::execute!(
self.stdout,
crossterm::terminal::EnterAlternateScreen,
crossterm::cursor::Hide,
crossterm::event::EnableMouseCapture,
)?;
Ok(())
}
fn leave(&mut self) -> Result<()> {
crossterm::execute!(
self.stdout,
crossterm::terminal::LeaveAlternateScreen,
crossterm::cursor::Show,
crossterm::event::DisableMouseCapture,
)?;
crossterm::terminal::disable_raw_mode()?;
Ok(())
}
fn size(&self) -> Result<Size> {
let (width, height) = crossterm::terminal::size()?;
Ok(Size { width, height })
}
fn flush(&mut self, ops: &[DiffOp]) -> Result<()> {
use crossterm::style::{Attribute, SetAttribute};
for op in ops {
crossterm::execute!(
self.stdout,
crossterm::cursor::MoveTo(op.pos.x, op.pos.y)
)?;
if let Some(fg) = op.cell.style.fg {
crossterm::execute!(self.stdout, crossterm::style::SetForegroundColor(fg.into()))?;
}
if let Some(bg) = op.cell.style.bg {
crossterm::execute!(self.stdout, crossterm::style::SetBackgroundColor(bg.into()))?;
}
if op.cell.style.bold {
crossterm::execute!(self.stdout, SetAttribute(Attribute::Bold))?;
}
if op.cell.style.italic {
crossterm::execute!(self.stdout, SetAttribute(Attribute::Italic))?;
}
if op.cell.style.underline {
crossterm::execute!(self.stdout, SetAttribute(Attribute::Underlined))?;
}
crossterm::execute!(self.stdout, crossterm::style::Print(&op.cell.symbol))?;
crossterm::execute!(
self.stdout,
crossterm::style::ResetColor,
SetAttribute(Attribute::Reset)
)?;
}
use std::io::Write;
self.stdout.flush()?;
Ok(())
}
fn poll_event(&mut self, timeout: std::time::Duration) -> Result<Option<Event>> {
if !crossterm::event::poll(timeout)? {
return Ok(None);
}
self.read_event().map(Some)
}
fn read_event(&mut self) -> Result<Event> {
use crossterm::event::{self, Event as CEvent, KeyCode, KeyModifiers};
loop {
match event::read()? {
CEvent::Key(key_event) => {
let key = match key_event.code {
KeyCode::Char(c) => Key::Char(c),
KeyCode::Enter => Key::Enter,
KeyCode::Esc => Key::Esc,
KeyCode::Backspace => Key::Backspace,
KeyCode::Tab => Key::Tab,
KeyCode::Up => Key::Up,
KeyCode::Down => Key::Down,
KeyCode::Left => Key::Left,
KeyCode::Right => Key::Right,
KeyCode::Delete => Key::Delete,
KeyCode::Home => Key::Home,
KeyCode::End => Key::End,
KeyCode::PageUp => Key::PageUp,
KeyCode::PageDown => Key::PageDown,
_ => continue, };
let modifiers = Modifiers {
ctrl: key_event.modifiers.contains(KeyModifiers::CONTROL),
alt: key_event.modifiers.contains(KeyModifiers::ALT),
shift: key_event.modifiers.contains(KeyModifiers::SHIFT),
};
return Ok(Event::Key(KeyEvent { key, modifiers }));
}
CEvent::Resize(w, h) => {
return Ok(Event::Resize(Size {
width: w,
height: h,
}));
}
CEvent::Mouse(mouse_event) => {
use crossterm::event::MouseEventKind;
let kind = match mouse_event.kind {
MouseEventKind::Down(_) => MouseKind::Down,
MouseEventKind::Up(_) => MouseKind::Up,
MouseEventKind::ScrollDown => MouseKind::ScrollDown,
MouseEventKind::ScrollUp => MouseKind::ScrollUp,
_ => continue,
};
return Ok(Event::Mouse(MouseEvent {
x: mouse_event.column,
y: mouse_event.row,
kind,
}));
}
_ => continue,
}
}
}
}