mod cell;
mod colour;
mod commands;
mod csi_helpers;
mod dispatch;
pub mod mode;
mod params;
mod passthrough;
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;
use crate::terminal_passthrough::MAX_TERMINAL_PASSTHROUGH_PAYLOAD_BYTES;
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;
#[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_buf_max: usize,
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>,
terminal_passthrough_dropped_count: u64,
}
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_buf_max: INPUT_BUF_MAX,
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(),
terminal_passthrough_dropped_count: 0,
}
}
pub fn set_input_buffer_limit(&mut self, limit: usize) {
self.input_buf_max = limit.max(INPUT_BUF_START);
}
pub(crate) const fn configured_input_buffer_limit(&self) -> usize {
self.input_buf_max
}
#[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(crate) fn take_terminal_passthrough_dropped_count(&mut self) -> u64 {
let dropped = self.terminal_passthrough_dropped_count;
self.terminal_passthrough_dropped_count = 0;
dropped
}
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) {
let mut index = 0;
while index < buf.len() {
if self.state == InputState::Ground && !self.utf8_started {
let printable_end = buf[index..]
.iter()
.position(|byte| !byte.is_ascii_graphic() && *byte != b' ')
.map_or(buf.len(), |offset| index + offset);
if printable_end > index {
self.handle_printable_ascii_run(&buf[index..printable_end], writer);
index = printable_end;
continue;
}
if self.handle_ground_c0_fast_path(buf[index], writer) {
index += 1;
continue;
}
}
self.ch = buf[index];
let transition = self.find_transition();
self.execute_transition(transition, writer);
index += 1;
}
}
fn handle_printable_ascii_run(&mut self, bytes: &[u8], writer: &mut dyn ScreenWriter) {
debug_assert_eq!(self.state, InputState::Ground);
let set = if self.cell.set == 0 {
self.cell.g0set
} else {
self.cell.g1set
};
let acs = set != 0;
writer.collect_add_ascii_run(bytes, &self.cell, acs);
if let Some(&last) = bytes.last() {
self.last_char = Some(char::from(last));
}
self.flags |= INPUT_LAST;
}
fn handle_ground_c0_fast_path(&mut self, byte: u8, writer: &mut dyn ScreenWriter) -> bool {
match byte {
0x0a..=0x0c => {
writer.collect_end();
writer.linefeed(false, self.cell.bg());
if writer.current_mode() & mode::MODE_CRLF != 0 {
writer.carriage_return();
}
}
0x0d => {
writer.collect_end();
writer.carriage_return();
}
_ => return false,
}
self.flags &= !INPUT_LAST;
true
}
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;
}
if passthrough::is_kitty_graphics_apc(&self.input_buf) {
writer.apc_passthrough(&self.input_buf);
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 {
let escaped_dcs_byte = self.state == InputState::DcsEscape;
let bytes_to_push = if escaped_dcs_byte && self.ch != 0x1b {
2
} else {
1
};
let input_limit = self.input_buffer_limit();
if self.input_buf.len() + bytes_to_push >= input_limit {
if self.flags & INPUT_DISCARD == 0 && self.is_terminal_passthrough_string() {
self.terminal_passthrough_dropped_count =
self.terminal_passthrough_dropped_count.saturating_add(1);
}
self.flags |= INPUT_DISCARD;
} else if escaped_dcs_byte && self.ch == 0x1b {
self.input_buf.push(0x1b);
} else if escaped_dcs_byte {
self.input_buf.push(0x1b);
self.input_buf.push(self.ch);
} else {
self.input_buf.push(self.ch);
}
false
}
fn input_buffer_limit(&self) -> usize {
if self.is_terminal_passthrough_string() {
return MAX_TERMINAL_PASSTHROUGH_PAYLOAD_BYTES;
}
self.input_buf_max
}
fn is_terminal_passthrough_string(&self) -> bool {
(self.state == InputState::ApcString && passthrough::is_kitty_graphics_apc(&self.input_buf))
|| (matches!(self.state, InputState::DcsHandler | InputState::DcsEscape)
&& self.interm_len == 0
&& (self.input_buf.first() == Some(&b'q') || self.input_buf.starts_with(b"tmux;")))
}
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()
}
}