mod cell;
mod colour;
mod commands;
mod csi_helpers;
mod dispatch;
mod params;
mod sgr;
mod states;
mod tables;
#[cfg(test)]
mod tests;
mod writer;
pub use cell::{CellState, GridAttr, SavedState};
pub use colour::{
colour_join_rgb, Colour, COLOUR_DEFAULT, COLOUR_FLAG_256, COLOUR_FLAG_RGB, COLOUR_NONE,
COLOUR_TERMINAL,
};
pub use dispatch::{CsiCommand, DcsPayload, EscCommand, InputAction, OscCommand, ScreenWriter};
pub use params::{InputParam, ParamType};
pub use states::InputState;
use params::ParamList;
use states::Transition;
const PARAM_LIST_MAX: usize = 24;
const INTERM_BUF_MAX: usize = 4;
const INPUT_BUF_START: usize = 32;
const INPUT_BUF_MAX: usize = 1_048_576;
const PARAM_BUF_MAX: usize = 64;
const INPUT_DISCARD: u32 = 0x1;
const INPUT_LAST: u32 = 0x2;
pub mod mode {
pub const MODE_CURSOR: u32 = 0x1;
pub const MODE_INSERT: u32 = 0x2;
pub const MODE_KCURSOR: u32 = 0x4;
pub const MODE_KKEYPAD: u32 = 0x8;
pub const MODE_WRAP: u32 = 0x10;
pub const MODE_MOUSE_STANDARD: u32 = 0x20;
pub const MODE_MOUSE_BUTTON: u32 = 0x40;
pub const MODE_CURSOR_BLINKING: u32 = 0x80;
pub const MODE_MOUSE_UTF8: u32 = 0x100;
pub const MODE_MOUSE_SGR: u32 = 0x200;
pub const MODE_BRACKETPASTE: u32 = 0x400;
pub const MODE_FOCUSON: u32 = 0x800;
pub const MODE_MOUSE_ALL: u32 = 0x1000;
pub const MODE_ORIGIN: u32 = 0x2000;
pub const MODE_CRLF: u32 = 0x4000;
pub const MODE_KEYS_EXTENDED: u32 = 0x8000;
pub const MODE_CURSOR_VERY_VISIBLE: u32 = 0x1_0000;
pub const MODE_CURSOR_BLINKING_SET: u32 = 0x2_0000;
pub const MODE_KEYS_EXTENDED_2: u32 = 0x4_0000;
pub const MODE_THEME_UPDATES: u32 = 0x8_0000;
pub const MODE_SYNC: u32 = 0x10_0000;
pub const ALL_MOUSE_MODES: u32 = MODE_MOUSE_STANDARD | MODE_MOUSE_BUTTON | MODE_MOUSE_ALL;
pub const EXTENDED_KEY_MODES: u32 = MODE_KEYS_EXTENDED | MODE_KEYS_EXTENDED_2;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputEndType {
St,
Bel,
}
pub struct InputParser {
state: InputState,
flags: u32,
ch: u8,
interm_buf: [u8; INTERM_BUF_MAX],
interm_len: usize,
param_buf: [u8; PARAM_BUF_MAX],
param_len: usize,
input_buf: Vec<u8>,
input_end: InputEndType,
param_list: ParamList,
cell: CellState,
saved: SavedState,
utf8_buf: [u8; 4],
utf8_len: u8,
utf8_expected: u8,
utf8_started: bool,
last_char: Option<char>,
since_ground: Vec<u8>,
ground_timer_active: bool,
reply_buf: Vec<u8>,
}
impl InputParser {
#[must_use]
pub fn new() -> Self {
Self {
state: InputState::Ground,
flags: 0,
ch: 0,
interm_buf: [0; INTERM_BUF_MAX],
interm_len: 0,
param_buf: [0; PARAM_BUF_MAX],
param_len: 0,
input_buf: Vec::with_capacity(INPUT_BUF_START),
input_end: InputEndType::St,
param_list: ParamList::new(),
cell: CellState::default(),
saved: SavedState::default(),
utf8_buf: [0; 4],
utf8_len: 0,
utf8_expected: 0,
utf8_started: false,
last_char: None,
since_ground: Vec::new(),
ground_timer_active: false,
reply_buf: Vec::new(),
}
}
#[must_use]
pub fn state(&self) -> InputState {
self.state
}
pub fn take_replies(&mut self) -> Vec<u8> {
std::mem::take(&mut self.reply_buf)
}
pub fn take_since_ground(&mut self) -> Vec<u8> {
std::mem::take(&mut self.since_ground)
}
#[must_use]
pub fn pending_bytes(&self) -> Vec<u8> {
if self.state != InputState::Ground {
return self.since_ground.clone();
}
if self.utf8_started {
return self.utf8_buf[..usize::from(self.utf8_len)].to_vec();
}
Vec::new()
}
#[must_use]
pub fn ground_timer_active(&self) -> bool {
self.ground_timer_active
}
pub fn ground_timer_expired(&mut self) {
self.reset_to_ground();
}
pub fn reset_to_ground(&mut self) {
self.clear();
self.state = InputState::Ground;
self.flags = 0;
self.enter_ground();
}
#[must_use]
pub fn cell_state(&self) -> &CellState {
&self.cell
}
pub fn parse(&mut self, buf: &[u8], writer: &mut dyn ScreenWriter) {
for &byte in buf {
self.ch = byte;
let transition = self.find_transition();
self.execute_transition(transition, writer);
}
}
fn find_transition(&self) -> Transition {
let table = self.state.transition_table();
for entry in table {
if self.ch >= entry.first && self.ch <= entry.last {
return Transition {
handler: entry.handler,
next_state: entry.next_state,
};
}
}
Transition {
handler: states::Handler::None,
next_state: None,
}
}
fn execute_transition(&mut self, trans: Transition, writer: &mut dyn ScreenWriter) {
if !matches!(
trans.handler,
states::Handler::Print | states::Handler::TopBitSet
) {
writer.collect_end();
}
let skip_state = match trans.handler {
states::Handler::None => false,
states::Handler::Print => self.handle_print(writer),
states::Handler::C0Dispatch => self.handle_c0_dispatch(writer),
states::Handler::EscDispatch => self.handle_esc_dispatch(writer),
states::Handler::CsiDispatch => self.handle_csi_dispatch(writer),
states::Handler::DcsDispatch => self.handle_dcs_dispatch(writer),
states::Handler::Intermediate => self.handle_intermediate(),
states::Handler::Parameter => self.handle_parameter(),
states::Handler::Input => self.handle_input(),
states::Handler::TopBitSet => self.handle_top_bit_set(writer),
states::Handler::EndBel => self.handle_end_bel(),
};
if skip_state {
return;
}
if let Some(next) = trans.next_state {
self.set_state(next, writer);
}
if self.state != InputState::Ground {
self.since_ground.push(self.ch);
}
}
fn set_state(&mut self, next: InputState, writer: &mut dyn ScreenWriter) {
self.exit_state(writer);
self.state = next;
self.enter_state(writer);
}
fn enter_state(&mut self, writer: &mut dyn ScreenWriter) {
match self.state {
InputState::Ground => self.enter_ground(),
InputState::EscEnter => self.clear(),
InputState::CsiEnter => self.clear(),
InputState::DcsEnter => self.enter_dcs(),
InputState::OscString => self.enter_osc(),
InputState::ApcString => self.enter_apc(),
InputState::RenameString => self.enter_rename(),
InputState::ConsumeSt => self.enter_rename(), _ => {}
}
let _ = writer; }
fn exit_state(&mut self, writer: &mut dyn ScreenWriter) {
match self.state {
InputState::OscString => self.exit_osc(writer),
InputState::ApcString => self.exit_apc(writer),
InputState::RenameString => self.exit_rename(writer),
_ => {}
}
}
fn clear(&mut self) {
self.ground_timer_active = false;
self.interm_buf = [0; INTERM_BUF_MAX];
self.interm_len = 0;
self.param_buf = [0; PARAM_BUF_MAX];
self.param_len = 0;
self.input_buf.clear();
self.input_end = InputEndType::St;
self.flags &= !INPUT_DISCARD;
}
fn enter_ground(&mut self) {
self.ground_timer_active = false;
self.since_ground.clear();
if self.input_buf.capacity() > INPUT_BUF_START {
self.input_buf = Vec::with_capacity(INPUT_BUF_START);
}
}
fn enter_dcs(&mut self) {
self.clear();
self.ground_timer_active = true;
self.flags &= !INPUT_LAST;
}
fn enter_osc(&mut self) {
self.clear();
self.ground_timer_active = true;
self.flags &= !INPUT_LAST;
}
fn enter_apc(&mut self) {
self.clear();
self.ground_timer_active = true;
self.flags &= !INPUT_LAST;
}
fn enter_rename(&mut self) {
self.clear();
self.ground_timer_active = true;
self.flags &= !INPUT_LAST;
}
fn exit_osc(&mut self, writer: &mut dyn ScreenWriter) {
if self.flags & INPUT_DISCARD != 0 {
return;
}
dispatch::dispatch_osc(self, writer);
}
fn exit_apc(&mut self, writer: &mut dyn ScreenWriter) {
if self.flags & INPUT_DISCARD != 0 {
return;
}
let buf = String::from_utf8_lossy(&self.input_buf).into_owned();
writer.set_title(&buf);
}
fn exit_rename(&mut self, writer: &mut dyn ScreenWriter) {
if self.flags & INPUT_DISCARD != 0 {
return;
}
let buf = String::from_utf8_lossy(&self.input_buf).into_owned();
writer.set_window_name(&buf);
}
fn stop_utf8(&mut self, writer: &mut dyn ScreenWriter) {
if self.utf8_started {
writer.collect_add('\u{FFFD}', &self.cell);
self.utf8_started = false;
self.utf8_len = 0;
self.utf8_expected = 0;
}
}
fn handle_print(&mut self, writer: &mut dyn ScreenWriter) -> bool {
self.stop_utf8(writer);
let ch = self.ch as char;
let set = if self.cell.set == 0 {
self.cell.g0set
} else {
self.cell.g1set
};
writer.collect_add_with_charset(ch, &self.cell, set != 0);
self.last_char = Some(ch);
self.flags |= INPUT_LAST;
false
}
fn handle_intermediate(&mut self) -> bool {
if self.interm_len >= INTERM_BUF_MAX - 1 {
self.flags |= INPUT_DISCARD;
} else {
self.interm_buf[self.interm_len] = self.ch;
self.interm_len += 1;
}
false
}
fn handle_parameter(&mut self) -> bool {
if self.param_len >= PARAM_BUF_MAX - 1 {
self.flags |= INPUT_DISCARD;
} else {
self.param_buf[self.param_len] = self.ch;
self.param_len += 1;
}
false
}
fn handle_input(&mut self) -> bool {
if self.input_buf.len() + 1 >= INPUT_BUF_MAX {
self.flags |= INPUT_DISCARD;
} else {
self.input_buf.push(self.ch);
}
false
}
fn handle_end_bel(&mut self) -> bool {
self.input_end = InputEndType::Bel;
false
}
fn handle_c0_dispatch(&mut self, writer: &mut dyn ScreenWriter) -> bool {
self.stop_utf8(writer);
dispatch::dispatch_c0(self, writer);
self.flags &= !INPUT_LAST;
false
}
fn handle_esc_dispatch(&mut self, writer: &mut dyn ScreenWriter) -> bool {
if self.flags & INPUT_DISCARD != 0 {
return false;
}
dispatch::dispatch_esc(self, writer);
self.flags &= !INPUT_LAST;
false
}
fn handle_csi_dispatch(&mut self, writer: &mut dyn ScreenWriter) -> bool {
if self.flags & INPUT_DISCARD != 0 {
return false;
}
dispatch::dispatch_csi(self, writer);
self.flags &= !INPUT_LAST;
false
}
fn handle_dcs_dispatch(&mut self, writer: &mut dyn ScreenWriter) -> bool {
if self.flags & INPUT_DISCARD != 0 {
return false;
}
dispatch::dispatch_dcs(self, writer);
false
}
fn handle_top_bit_set(&mut self, writer: &mut dyn ScreenWriter) -> bool {
self.flags &= !INPUT_LAST;
if !self.utf8_started {
self.utf8_started = true;
self.utf8_len = 0;
let expected = if self.ch & 0xE0 == 0xC0 {
2
} else if self.ch & 0xF0 == 0xE0 {
3
} else if self.ch & 0xF8 == 0xF0 {
4
} else {
self.stop_utf8(writer);
return false;
};
self.utf8_expected = expected;
self.utf8_buf[0] = self.ch;
self.utf8_len = 1;
return false;
}
if self.ch & 0xC0 != 0x80 {
self.stop_utf8(writer);
if self.ch >= 0x80 {
return self.handle_top_bit_set(writer);
}
return false;
}
self.utf8_buf[self.utf8_len as usize] = self.ch;
self.utf8_len += 1;
if self.utf8_len < self.utf8_expected {
return false; }
self.utf8_started = false;
let bytes = &self.utf8_buf[..self.utf8_len as usize];
let s = match std::str::from_utf8(bytes) {
Ok(s) => s,
Err(_) => {
writer.collect_add('\u{FFFD}', &self.cell);
return false;
}
};
let c = match s.chars().next() {
Some(c) => c,
None => {
writer.collect_add('\u{FFFD}', &self.cell);
return false;
}
};
writer.collect_add(c, &self.cell);
self.last_char = Some(c);
self.flags |= INPUT_LAST;
false
}
fn reply(&mut self, s: &str) {
self.reply_buf.extend_from_slice(s.as_bytes());
}
fn interm_str(&self) -> &[u8] {
&self.interm_buf[..self.interm_len]
}
}
impl Default for InputParser {
fn default() -> Self {
Self::new()
}
}