use alloc::string::String;
use core::convert::TryFrom;
use vte::{Params, ParamsIter, Perform};
use crate::cell::Cell;
use crate::color::{Color, NamedColor, Rgb888};
#[allow(clippy::enum_variant_names)]
#[derive(Debug, Eq, PartialEq)]
pub enum Mode {
CursorKeys = 1,
ColumnMode = 3,
Insert = 4,
Origin = 6,
LineWrap = 7,
BlinkingCursor = 12,
LineFeedNewLine = 20,
ShowCursor = 25,
ReportMouseClicks = 1000,
ReportCellMouseMotion = 1002,
ReportAllMouseMotion = 1003,
ReportFocusInOut = 1004,
Utf8Mouse = 1005,
SgrMouse = 1006,
AlternateScroll = 1007,
UrgencyHints = 1042,
SwapScreenAndSetRestoreCursor = 1049,
BracketedPaste = 2004,
}
impl Mode {
pub fn from_primitive(intermediate: Option<&u8>, num: u16) -> Option<Mode> {
let private = match intermediate {
Some(b'?') => true,
None => false,
_ => return None,
};
if private {
Some(match num {
1 => Mode::CursorKeys,
3 => Mode::ColumnMode,
6 => Mode::Origin,
7 => Mode::LineWrap,
12 => Mode::BlinkingCursor,
25 => Mode::ShowCursor,
1000 => Mode::ReportMouseClicks,
1002 => Mode::ReportCellMouseMotion,
1003 => Mode::ReportAllMouseMotion,
1004 => Mode::ReportFocusInOut,
1005 => Mode::Utf8Mouse,
1006 => Mode::SgrMouse,
1007 => Mode::AlternateScroll,
1042 => Mode::UrgencyHints,
1049 => Mode::SwapScreenAndSetRestoreCursor,
2004 => Mode::BracketedPaste,
_ => {
trace!("[unimplemented] primitive mode: {}", num);
return None;
}
})
} else {
Some(match num {
4 => Mode::Insert,
20 => Mode::LineFeedNewLine,
_ => return None,
})
}
}
}
#[derive(Debug)]
pub enum LineClearMode {
Right,
Left,
All,
}
#[derive(Debug)]
pub enum ClearMode {
Below,
Above,
All,
Saved,
}
#[derive(Debug, Eq, PartialEq)]
pub enum Attr {
Reset,
Bold,
Dim,
Italic,
Underline,
DoubleUnderline,
BlinkSlow,
BlinkFast,
Reverse,
Hidden,
Strike,
CancelBold,
CancelBoldDim,
CancelItalic,
CancelUnderline,
CancelBlink,
CancelReverse,
CancelHidden,
CancelStrike,
Foreground(Color),
Background(Color),
}
pub trait Handler {
fn input(&mut self, _c: char) {}
fn goto(&mut self, _row: usize, _col: usize) {}
fn goto_line(&mut self, _row: usize) {}
fn goto_col(&mut self, _col: usize) {}
fn move_up(&mut self, _rows: usize) {}
fn move_down(&mut self, _rows: usize) {}
fn move_forward(&mut self, _cols: usize) {}
fn move_backward(&mut self, _cols: usize) {}
fn move_down_and_cr(&mut self, _rows: usize) {}
fn move_up_and_cr(&mut self, _rows: usize) {}
fn put_tab(&mut self, _count: u16) {}
fn backspace(&mut self) {}
fn carriage_return(&mut self) {}
fn linefeed(&mut self) {}
fn scroll_up(&mut self, _rows: usize) {}
fn scroll_down(&mut self, _rows: usize) {}
fn erase_chars(&mut self, _count: usize) {}
fn delete_chars(&mut self, _count: usize) {}
fn save_cursor_position(&mut self) {}
fn restore_cursor_position(&mut self) {}
fn clear_line(&mut self, _mode: LineClearMode) {}
fn clear_screen(&mut self, _mode: ClearMode) {}
fn terminal_attribute(&mut self, _attr: Attr) {}
fn set_mode(&mut self, _mode: Mode) {}
fn unset_mode(&mut self, _mode: Mode) {}
fn set_scrolling_region(&mut self, _top: usize, _bottom: Option<usize>) {}
fn device_status(&mut self, _arg: usize) {}
}
pub struct Performer<'a, H: Handler> {
handler: &'a mut H,
}
impl<'a, H: Handler> Performer<'a, H> {
pub fn new(handler: &'a mut H) -> Self {
Self { handler }
}
}
impl<'a, H: Handler> Perform for Performer<'a, H> {
#[inline]
fn print(&mut self, c: char) {
self.handler.input(c);
}
#[inline]
fn execute(&mut self, byte: u8) {
match byte {
C0::HT => self.handler.put_tab(1),
C0::BS => self.handler.backspace(),
C0::CR => self.handler.carriage_return(),
C0::LF | C0::VT | C0::FF => self.handler.linefeed(),
_ => debug!("[unhandled] execute byte={:02x}", byte),
}
}
#[inline]
fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, action: char) {
debug!(
"[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}, action: {:?}",
params, intermediates, ignore, action
);
}
#[inline]
fn put(&mut self, byte: u8) {
debug!("[unhandled put] byte={:?}", byte);
}
#[inline]
fn unhook(&mut self) {
debug!("[unhandled unhook]");
}
#[inline]
fn osc_dispatch(&mut self, params: &[&[u8]], _bell_terminated: bool) {
fn unhandled(params: &[&[u8]]) {
let mut buf = String::new();
for items in params {
buf.push('[');
for item in *items {
buf.push_str(&format!("{:?},", *item as char));
}
buf.push_str("],");
}
debug!("[unhandled osc_dispatch]: [{}] at line {}", &buf, line!());
}
unhandled(params);
}
#[inline]
fn csi_dispatch(
&mut self,
params: &Params,
intermediates: &[u8],
has_ignored_intermediates: bool,
action: char,
) {
macro_rules! unhandled {
() => {{
warn!(
"[Unhandled CSI] action={:?}, params={:?}, intermediates={:?}",
action, params, intermediates
);
}};
}
if has_ignored_intermediates || intermediates.len() > 1 {
unhandled!();
return;
}
let handler = &mut self.handler;
let mut params_iter = params.iter();
let mut next_param_or = |default: u16| {
params_iter
.next()
.map(|param| param[0])
.filter(|¶m| param != 0)
.unwrap_or(default)
};
match (action, intermediates) {
('A', []) => handler.move_up(next_param_or(1) as usize),
('B', []) | ('e', []) => handler.move_down(next_param_or(1) as usize),
('C', []) | ('a', []) => handler.move_forward(next_param_or(1) as usize),
('D', []) => handler.move_backward(next_param_or(1) as usize),
('E', []) => handler.move_down_and_cr(next_param_or(1) as usize),
('F', []) => handler.move_up_and_cr(next_param_or(1) as usize),
('G', []) | ('`', []) => handler.goto_col(next_param_or(1) as usize - 1),
('H', []) | ('f', []) => {
let y = next_param_or(1) as usize;
let x = next_param_or(1) as usize;
handler.goto(y - 1, x - 1);
}
('J', []) => {
let mode = match next_param_or(0) {
0 => ClearMode::Below,
1 => ClearMode::Above,
2 => ClearMode::All,
3 => ClearMode::Saved,
_ => {
unhandled!();
return;
}
};
handler.clear_screen(mode);
}
('K', []) => {
let mode = match next_param_or(0) {
0 => LineClearMode::Right,
1 => LineClearMode::Left,
2 => LineClearMode::All,
_ => {
unhandled!();
return;
}
};
handler.clear_line(mode);
}
('P', []) => handler.delete_chars(next_param_or(1) as usize),
('S', []) => handler.scroll_up(next_param_or(1) as usize),
('T', []) => handler.scroll_down(next_param_or(1) as usize),
('X', []) => handler.erase_chars(next_param_or(1) as usize),
('d', []) => handler.goto_line(next_param_or(1) as usize - 1),
('h', intermediates) => {
for param in params_iter.map(|param| param[0]) {
match Mode::from_primitive(intermediates.get(0), param) {
Some(mode) => handler.set_mode(mode),
None => unhandled!(),
}
}
}
('l', intermediates) => {
for param in params_iter.map(|param| param[0]) {
match Mode::from_primitive(intermediates.get(0), param) {
Some(mode) => handler.unset_mode(mode),
None => unhandled!(),
}
}
}
('m', _) => {
if params.is_empty() {
handler.terminal_attribute(Attr::Reset);
} else {
attrs_from_sgr_parameters(&mut params_iter, |attr| match attr {
Some(attr) => handler.terminal_attribute(attr),
None => unhandled!(),
});
}
}
('n', []) => handler.device_status(next_param_or(0) as usize),
('r', []) => {
let top = next_param_or(1) as usize;
let bottom = params_iter
.next()
.map(|param| param[0] as usize)
.filter(|¶m| param != 0);
handler.set_scrolling_region(top, bottom);
}
_ => unhandled!(),
}
}
#[inline]
fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) {
macro_rules! unhandled {
() => {{
debug!(
"[unhandled] esc_dispatch ints={:?}, byte={:?} ({:02x})",
intermediates, byte as char, byte
);
}};
}
match (byte, intermediates) {
(b'7', []) => self.handler.save_cursor_position(),
(b'8', []) => self.handler.restore_cursor_position(),
_ => unhandled!(),
}
}
}
#[inline]
fn attrs_from_sgr_parameters<F>(params: &mut ParamsIter<'_>, mut handler: F)
where
F: FnMut(Option<Attr>),
{
while let Some(param) = params.next() {
let attr = match param {
[0] => Some(Attr::Reset),
[1] => Some(Attr::Bold),
[2] => Some(Attr::Dim),
[3] => Some(Attr::Italic),
[4, 0] => Some(Attr::CancelUnderline),
[4, 2] => Some(Attr::DoubleUnderline),
[4, ..] => Some(Attr::Underline),
[5] => Some(Attr::BlinkSlow),
[6] => Some(Attr::BlinkFast),
[7] => Some(Attr::Reverse),
[8] => Some(Attr::Hidden),
[9] => Some(Attr::Strike),
[21] => Some(Attr::CancelBold),
[22] => Some(Attr::CancelBoldDim),
[23] => Some(Attr::CancelItalic),
[24] => Some(Attr::CancelUnderline),
[25] => Some(Attr::CancelBlink),
[27] => Some(Attr::CancelReverse),
[28] => Some(Attr::CancelHidden),
[29] => Some(Attr::CancelStrike),
[30] => Some(Attr::Foreground(Color::Named(NamedColor::Black))),
[31] => Some(Attr::Foreground(Color::Named(NamedColor::Red))),
[32] => Some(Attr::Foreground(Color::Named(NamedColor::Green))),
[33] => Some(Attr::Foreground(Color::Named(NamedColor::Yellow))),
[34] => Some(Attr::Foreground(Color::Named(NamedColor::Blue))),
[35] => Some(Attr::Foreground(Color::Named(NamedColor::Magenta))),
[36] => Some(Attr::Foreground(Color::Named(NamedColor::Cyan))),
[37] => Some(Attr::Foreground(Color::Named(NamedColor::White))),
[38] => {
let mut iter = params.map(|param| param[0]);
parse_sgr_color(&mut iter).map(Attr::Foreground)
}
[38, params @ ..] => {
let rgb_start = if params.len() > 4 { 2 } else { 1 };
let rgb_iter = params[rgb_start..].iter().copied();
let mut iter = core::iter::once(params[0]).chain(rgb_iter);
parse_sgr_color(&mut iter).map(Attr::Foreground)
}
[39] => Some(Attr::Foreground(Cell::default().fg)),
[40] => Some(Attr::Background(Color::Named(NamedColor::Black))),
[41] => Some(Attr::Background(Color::Named(NamedColor::Red))),
[42] => Some(Attr::Background(Color::Named(NamedColor::Green))),
[43] => Some(Attr::Background(Color::Named(NamedColor::Yellow))),
[44] => Some(Attr::Background(Color::Named(NamedColor::Blue))),
[45] => Some(Attr::Background(Color::Named(NamedColor::Magenta))),
[46] => Some(Attr::Background(Color::Named(NamedColor::Cyan))),
[47] => Some(Attr::Background(Color::Named(NamedColor::White))),
[48] => {
let mut iter = params.map(|param| param[0]);
parse_sgr_color(&mut iter).map(Attr::Background)
}
[48, params @ ..] => {
let rgb_start = if params.len() > 4 { 2 } else { 1 };
let rgb_iter = params[rgb_start..].iter().copied();
let mut iter = core::iter::once(params[0]).chain(rgb_iter);
parse_sgr_color(&mut iter).map(Attr::Background)
}
[49] => Some(Attr::Background(Cell::default().bg)),
[90] => Some(Attr::Foreground(Color::Named(NamedColor::BrightBlack))),
[91] => Some(Attr::Foreground(Color::Named(NamedColor::BrightRed))),
[92] => Some(Attr::Foreground(Color::Named(NamedColor::BrightGreen))),
[93] => Some(Attr::Foreground(Color::Named(NamedColor::BrightYellow))),
[94] => Some(Attr::Foreground(Color::Named(NamedColor::BrightBlue))),
[95] => Some(Attr::Foreground(Color::Named(NamedColor::BrightMagenta))),
[96] => Some(Attr::Foreground(Color::Named(NamedColor::BrightCyan))),
[97] => Some(Attr::Foreground(Color::Named(NamedColor::BrightWhite))),
[100] => Some(Attr::Background(Color::Named(NamedColor::BrightBlack))),
[101] => Some(Attr::Background(Color::Named(NamedColor::BrightRed))),
[102] => Some(Attr::Background(Color::Named(NamedColor::BrightGreen))),
[103] => Some(Attr::Background(Color::Named(NamedColor::BrightYellow))),
[104] => Some(Attr::Background(Color::Named(NamedColor::BrightBlue))),
[105] => Some(Attr::Background(Color::Named(NamedColor::BrightMagenta))),
[106] => Some(Attr::Background(Color::Named(NamedColor::BrightCyan))),
[107] => Some(Attr::Background(Color::Named(NamedColor::BrightWhite))),
_ => None,
};
handler(attr);
}
}
fn parse_sgr_color(params: &mut dyn Iterator<Item = u16>) -> Option<Color> {
match params.next() {
Some(2) => Some(Color::Spec(Rgb888::new(
u8::try_from(params.next()?).ok()?,
u8::try_from(params.next()?).ok()?,
u8::try_from(params.next()?).ok()?,
))),
Some(5) => Some(Color::Indexed(u8::try_from(params.next()?).ok()?)),
_ => None,
}
}
#[allow(dead_code)]
#[allow(non_snake_case)]
pub mod C0 {
pub const NUL: u8 = 0x00;
pub const SOH: u8 = 0x01;
pub const STX: u8 = 0x02;
pub const ETX: u8 = 0x03;
pub const EOT: u8 = 0x04;
pub const ENQ: u8 = 0x05;
pub const ACK: u8 = 0x06;
pub const BEL: u8 = 0x07;
pub const BS: u8 = 0x08;
pub const HT: u8 = 0x09;
pub const LF: u8 = 0x0A;
pub const VT: u8 = 0x0B;
pub const FF: u8 = 0x0C;
pub const CR: u8 = 0x0D;
pub const SO: u8 = 0x0E;
pub const SI: u8 = 0x0F;
pub const DLE: u8 = 0x10;
pub const XON: u8 = 0x11;
pub const DC2: u8 = 0x12;
pub const XOFF: u8 = 0x13;
pub const DC4: u8 = 0x14;
pub const NAK: u8 = 0x15;
pub const SYN: u8 = 0x16;
pub const ETB: u8 = 0x17;
pub const CAN: u8 = 0x18;
pub const EM: u8 = 0x19;
pub const SUB: u8 = 0x1A;
pub const ESC: u8 = 0x1B;
pub const FS: u8 = 0x1C;
pub const GS: u8 = 0x1D;
pub const RS: u8 = 0x1E;
pub const US: u8 = 0x1F;
pub const DEL: u8 = 0x7f;
}