use crate::{
error::Error,
render::{TerminalRenderer, TerminalSurface, TerminalSurfaceExt},
Face, Image, Key, KeyMod, KeyName, RGBA,
};
use std::{
collections::{BTreeMap, BTreeSet},
fmt,
io::Write,
sync::Arc,
time::Duration,
};
const TERMINAL_FRAMES_DROP: usize = 32;
pub trait Terminal: Write {
fn execute(&mut self, cmd: TerminalCommand) -> Result<(), Error>;
fn waker(&self) -> TerminalWaker;
fn poll(&mut self, timeout: Option<Duration>) -> Result<Option<TerminalEvent>, Error>;
fn size(&self) -> Result<TerminalSize, Error>;
fn run<H, R, E>(&mut self, mut timeout: Option<Duration>, mut handler: H) -> Result<R, E>
where
H: FnMut(&mut Self, Option<TerminalEvent>) -> Result<TerminalAction<R>, E>,
E: From<Error>,
Self: Sized,
{
loop {
let event = self.poll(timeout)?;
timeout = match handler(self, event)? {
TerminalAction::Quit(result) => return Ok(result),
TerminalAction::Wait => None,
TerminalAction::Sleep(timeout) => Some(timeout),
};
}
}
fn run_render<H, R, E>(&mut self, mut handler: H) -> Result<R, E>
where
H: for<'a> FnMut(
&'a mut Self,
Option<TerminalEvent>,
TerminalSurface<'a>,
) -> Result<TerminalAction<R>, E>,
E: From<Error>,
Self: Sized,
{
let mut renderer = TerminalRenderer::new(self, false)?;
let mut timeout = Some(Duration::new(0, 0));
loop {
let event = self.poll(timeout);
match event {
Err(error) => {
renderer.view().erase(None);
renderer.frame(self)?;
let _ = self.poll(Some(Duration::new(0, 0)));
return Err(error.into());
}
Ok(event) => {
if let Some(TerminalEvent::Resize(_)) = event {
renderer = TerminalRenderer::new(self, true)?;
}
let action = handler(self, event, renderer.view())?;
if self.frames_pending() > TERMINAL_FRAMES_DROP {
self.frames_drop();
renderer.clear();
}
renderer.frame(self)?;
timeout = match action {
TerminalAction::Quit(result) => return Ok(result),
TerminalAction::Wait => None,
TerminalAction::Sleep(timeout) => Some(timeout),
};
}
}
}
}
fn frames_pending(&self) -> usize;
fn frames_drop(&mut self);
}
#[derive(Clone)]
pub struct TerminalWaker {
wake: Arc<dyn Fn() -> Result<(), Error> + Sync + Send + 'static>,
}
impl TerminalWaker {
pub fn new(wake: impl Fn() -> Result<(), Error> + Sync + Send + 'static) -> Self {
Self {
wake: Arc::new(wake),
}
}
pub fn wake(&self) -> Result<(), Error> {
(self.wake)()
}
}
pub enum TerminalAction<R> {
Quit(R),
Wait,
Sleep(Duration),
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TerminalCommand {
Char(char),
Face(Face),
DecModeSet {
enable: bool,
mode: DecMode,
},
DecModeGet(DecMode),
CursorGet,
CursorTo(Position),
CursorSave,
CursorRestore,
EraseLineLeft,
EraseLineRight,
EraseLine,
EraseChars(usize),
Scroll(i32),
ScrollRegion {
start: usize,
end: usize,
},
Reset,
Image(Image),
ImageErase(Position),
Termcap(Vec<String>),
Color {
name: TerminalColor,
color: Option<RGBA>,
},
Title(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TerminalColor {
Background,
Foreground,
Palette(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DecMode {
VisibleCursor = 25,
AutoWrap = 7,
MouseReport = 1000,
MouseMotions = 1003,
MouseSGR = 1006,
AltScreen = 1049,
KittyKeyboard = 2017,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Position {
pub row: usize,
pub col: usize,
}
impl Position {
pub fn new(row: usize, col: usize) -> Self {
Self { row, col }
}
}
impl DecMode {
pub fn from_usize(code: usize) -> Option<Self> {
use DecMode::*;
for mode in [
VisibleCursor,
AutoWrap,
MouseReport,
MouseMotions,
MouseSGR,
AltScreen,
KittyKeyboard,
]
.iter()
{
if code == *mode as usize {
return Some(*mode);
}
}
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DecModeStatus {
NotRecognized = 0,
Enabled = 1,
Disabled = 2,
PermanentlyEnabled = 3,
PermanentlyDisabled = 4,
}
impl DecModeStatus {
pub fn from_usize(code: usize) -> Option<Self> {
use DecModeStatus::*;
for status in [
NotRecognized,
Enabled,
Disabled,
PermanentlyEnabled,
PermanentlyDisabled,
]
.iter()
{
if code == *status as usize {
return Some(*status);
}
}
None
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TerminalEvent {
Key(Key),
Mouse(Mouse),
CursorPosition { row: usize, col: usize },
Resize(TerminalSize),
Size(TerminalSize),
DecMode {
mode: DecMode,
status: DecModeStatus,
},
KittyImage { id: u64, error: Option<String> },
Wake,
Termcap(BTreeMap<String, Option<String>>),
DeviceAttrs(BTreeSet<usize>),
Raw(Vec<u8>),
Color { name: TerminalColor, color: RGBA },
Command(TerminalCommand),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Size {
pub height: usize,
pub width: usize,
}
impl Size {
pub fn empty() -> Self {
Self::new(0, 0)
}
pub fn new(height: usize, width: usize) -> Self {
Self { height, width }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TerminalSize {
pub cells: Size,
pub pixels: Size,
}
impl TerminalSize {
pub fn cell_size(&self) -> Size {
Size {
height: self.pixels.height / self.cells.height,
width: self.pixels.width / self.cells.width,
}
}
pub fn cells_in_pixels(&self, cells: Size) -> Size {
let cell_size = self.cell_size();
Size {
height: cells.height * cell_size.height,
width: cells.width * cell_size.width,
}
}
}
#[derive(Clone, Debug)]
pub struct TerminalStats {
pub send: usize,
pub recv: usize,
}
impl Default for TerminalStats {
fn default() -> Self {
Self::new()
}
}
impl TerminalStats {
pub fn new() -> Self {
Self { send: 0, recv: 0 }
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Mouse {
pub name: KeyName,
pub mode: KeyMod,
pub row: usize,
pub col: usize,
}
impl fmt::Debug for Mouse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.mode.is_empty() {
write!(f, "{:?} [{},{}]", self.name, self.row, self.col)?;
} else {
write!(
f,
"{:?}+{:?} [{},{}]",
self.name, self.mode, self.row, self.col
)?;
}
Ok(())
}
}