pub mod ascii;
pub mod capture;
pub mod event;
pub mod iter;
pub mod signature;
use smallvec::SmallVec;
use ascii::AsciiControl;
use event::{CSI, DCS, Esc, EscInvalid, SS2, SS3, VTEvent, VTIntermediate};
const ESC: u8 = AsciiControl::Esc as _;
const BEL: u8 = AsciiControl::Bel as _;
const DEL: u8 = AsciiControl::Del as _;
const CAN: u8 = AsciiControl::Can as _;
const SUB: u8 = AsciiControl::Sub as _;
const CSI: u8 = b'[';
const OSC: u8 = b']';
const SS2: u8 = b'N';
const SS3: u8 = b'O';
const DCS: u8 = b'P';
const APC: u8 = b'_';
const PM: u8 = b'^';
const SOS: u8 = b'X';
const ST_FINAL: u8 = b'\\';
use crate::event::{Param, ParamBuf, Params};
#[allow(private_bounds)]
pub trait VTEventCallback: VTEventCallbackMaybeAbortable<()> {
fn event(&mut self, event: VTEvent<'_>) -> ();
}
impl<T: FnMut(VTEvent<'_>)> VTEventCallback for T {
#[inline(always)]
fn event(&mut self, event: VTEvent<'_>) {
self(event)
}
}
impl<T: VTEventCallback> VTEventCallbackMaybeAbortable<()> for T {
#[inline(always)]
fn event(&mut self, event: VTEvent<'_>) {
VTEventCallback::event(self, event)
}
}
#[allow(private_bounds)]
pub trait VTEventCallbackAbortable: VTEventCallbackMaybeAbortable<bool> {
fn event(&mut self, event: VTEvent<'_>) -> bool;
}
impl<T: VTEventCallbackAbortable> VTEventCallbackMaybeAbortable<bool> for T {
#[inline(always)]
fn event(&mut self, event: VTEvent<'_>) -> bool {
VTEventCallbackAbortable::event(self, event)
}
}
impl<T: FnMut(VTEvent<'_>) -> bool> VTEventCallbackAbortable for T {
#[inline(always)]
fn event(&mut self, event: VTEvent<'_>) -> bool {
self(event)
}
}
trait VTEventCallbackMaybeAbortable<R: MaybeAbortable> {
fn event<'e>(&mut self, event: VTEvent<'e>) -> R;
}
enum VTAction<'a> {
None,
Event(VTEvent<'a>),
End(VTEnd),
Buffer(VTEmit),
Hold(VTEmit),
Cancel(VTEmit),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum VTEmit {
Ground,
Dcs,
Osc,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum VTEnd {
Dcs,
Osc { used_bel: bool },
}
macro_rules! def_pattern {
($name:ident => $pattern:pat) => {
#[inline]
#[allow(unused)]
const fn $name(b: u8) -> bool {
matches!(b, $pattern)
}
#[allow(unused)]
macro_rules! $name {
() => {
$pattern
};
}
};
}
def_pattern!(is_c0 => 0x00..=0x08 | 0x0b..=0x0c | 0x0e..=0x1f);
def_pattern!(is_any_c0 => 0x00..=0x1f);
def_pattern!(is_printable => b' '..=b'~');
def_pattern!(is_intermediate => b' '..=b'/');
def_pattern!(is_final => 0x40..=0x7e);
def_pattern!(is_priv => b'<' | b'=' | b'>' | b'?');
def_pattern!(is_priv_no_q => b'<' | b'=' | b'>');
def_pattern!(is_digit => b'0'..=b'9');
macro_rules! byte_predicate {
(|$p:ident| $body:block) => {{
let mut out: [bool; 256] = [false; 256];
let mut i = 0;
while i < 256 {
let $p: u8 = i as u8;
out[i] = $body;
i += 1;
}
out
}};
}
const ENDS_CSI: [bool; 256] =
byte_predicate!(|b| { is_final(b) || b == ESC || b == CAN || b == SUB });
const ENDS_GROUND: [bool; 256] = byte_predicate!(|b| { is_c0(b) || b == DEL });
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum State {
Ground,
Escape,
EscInt,
EscSs2,
EscSs3,
CsiEntry,
CsiParam,
CsiInt,
CsiIgnore,
DcsEntry,
DcsParam,
DcsInt,
DcsIgnore,
DcsIgnoreEsc,
DcsPassthrough,
DcsEsc,
OscString,
OscEsc,
SosPmApcString,
SpaEsc,
}
pub const VT_PARSER_INTEREST_NONE: u8 = 0;
pub const VT_PARSER_INTEREST_CSI: u8 = 1 << 0;
pub const VT_PARSER_INTEREST_DCS: u8 = 1 << 1;
pub const VT_PARSER_INTEREST_OSC: u8 = 1 << 2;
pub const VT_PARSER_INTEREST_ESCAPE_RECOVERY: u8 = 1 << 4;
pub const VT_PARSER_INTEREST_OTHER: u8 = 1 << 5;
pub const VT_PARSER_INTEREST_ALL: u8 = VT_PARSER_INTEREST_CSI
| VT_PARSER_INTEREST_DCS
| VT_PARSER_INTEREST_OSC
| VT_PARSER_INTEREST_ESCAPE_RECOVERY
| VT_PARSER_INTEREST_OTHER;
pub const VT_PARSER_INTEREST_DEFAULT: u8 = VT_PARSER_INTEREST_CSI
| VT_PARSER_INTEREST_DCS
| VT_PARSER_INTEREST_OSC
| VT_PARSER_INTEREST_OTHER;
#[must_use]
trait MaybeAbortable {
fn abort(self) -> bool;
}
impl MaybeAbortable for bool {
#[inline(always)]
fn abort(self) -> bool {
!self
}
}
impl MaybeAbortable for () {
#[inline(always)]
fn abort(self) -> bool {
false
}
}
pub struct VTPushParser<const INTEREST: u8 = VT_PARSER_INTEREST_DEFAULT> {
st: State,
ints: VTIntermediate,
params: Params,
cur_param: Param,
priv_prefix: Option<u8>,
held_byte: Option<u8>,
}
impl Default for VTPushParser {
fn default() -> Self {
Self::new()
}
}
impl VTPushParser {
pub const fn new() -> Self {
VTPushParser::new_with()
}
pub fn decode_buffer<'a>(input: &'a [u8], mut cb: impl for<'b> FnMut(VTEvent<'b>)) {
let mut parser = VTPushParser::new();
parser.feed_with(input, &mut cb);
}
pub const fn new_with_interest<const INTEREST: u8>() -> VTPushParser<INTEREST> {
VTPushParser::new_with()
}
}
macro_rules! invalid {
($self:ident .priv_prefix, $self_:ident .ints, $b:expr) => {
if let Some(p) = $self.priv_prefix {
if $self.ints.len() == 0 {
VTEvent::EscInvalid(EscInvalid::Two(p, $b))
} else if $self.ints.len() == 1 {
VTEvent::EscInvalid(EscInvalid::Three(p, $self.ints.data[0], $b))
} else {
VTEvent::EscInvalid(EscInvalid::Four(
p,
$self.ints.data[0],
$self.ints.data[1],
$b,
))
}
} else {
if $self.ints.len() == 0 {
VTEvent::EscInvalid(EscInvalid::One($b))
} else if $self.ints.len() == 1 {
VTEvent::EscInvalid(EscInvalid::Two($self.ints.data[0], $b))
} else {
VTEvent::EscInvalid(EscInvalid::Three(
$self.ints.data[0],
$self.ints.data[1],
$b,
))
}
}
};
($self:ident .priv_prefix, $self_:ident .ints) => {
if let Some(p) = $self.priv_prefix {
if $self.ints.len() == 0 {
VTEvent::EscInvalid(EscInvalid::One(p))
} else if $self.ints.len() == 1 {
VTEvent::EscInvalid(EscInvalid::Two(p, $self.ints.data[0]))
} else {
VTEvent::EscInvalid(EscInvalid::Three(p, $self.ints.data[0], $self.ints.data[1]))
}
} else {
if $self.ints.len() == 0 {
VTEvent::C0(0x1b)
} else if $self.ints.len() == 1 {
VTEvent::EscInvalid(EscInvalid::One($self.ints.data[0]))
} else {
VTEvent::EscInvalid(EscInvalid::Two($self.ints.data[0], $self.ints.data[1]))
}
}
};
($a:expr) => {
VTEvent::EscInvalid(EscInvalid::One($a))
};
($a:expr, $b:expr) => {
VTEvent::EscInvalid(EscInvalid::Two($a, $b))
};
}
impl<const INTEREST: u8> VTPushParser<INTEREST> {
const fn new_with() -> Self {
Self {
st: State::Ground,
ints: VTIntermediate::empty(),
params: SmallVec::new_const(),
cur_param: SmallVec::new_const(),
priv_prefix: None,
held_byte: None,
}
}
#[inline]
pub fn feed_with<F: VTEventCallback>(&mut self, input: &[u8], mut cb: F) {
self.feed_with_internal(input, &mut cb);
}
#[inline]
pub fn feed_with_abortable<F: VTEventCallbackAbortable>(
&mut self,
input: &[u8],
mut cb: F,
) -> usize {
self.feed_with_internal(input, &mut cb)
}
#[inline(always)]
fn feed_with_internal<R: MaybeAbortable, F: VTEventCallbackMaybeAbortable<R>>(
&mut self,
input: &[u8],
cb: &mut F,
) -> usize {
if input.is_empty() {
return 0;
}
#[derive(Debug)]
struct FeedState {
buffer_idx: usize,
current_emit: Option<VTEmit>,
hold: bool,
}
let mut state = FeedState {
buffer_idx: 0,
current_emit: None,
hold: self.held_byte.is_some(),
};
let mut held_byte = self.held_byte.take();
let mut i = 0;
while i < input.len() {
if self.st == State::Ground {
let start = i;
loop {
if i >= input.len() {
cb.event(VTEvent::Raw(&input[start..]));
return input.len();
}
if ENDS_GROUND[input[i] as usize] {
break;
}
i += 1;
}
if start != i && cb.event(VTEvent::Raw(&input[start..i])).abort() {
return i;
}
if input[i] == ESC {
self.clear_hdr_collectors();
self.st = State::Escape;
i += 1;
continue;
}
}
if self.st == State::CsiIgnore {
loop {
if i >= input.len() {
return input.len();
}
if ENDS_CSI[input[i] as usize] {
break;
}
i += 1;
}
if input[i] == ESC {
self.st = State::Escape;
} else {
self.st = State::Ground;
}
i += 1;
continue;
}
let action = self.push_with(input[i]);
match action {
VTAction::None => {
if let Some(emit) = state.current_emit {
let range = state.buffer_idx..(i - state.hold as usize);
if !range.is_empty()
&& match emit {
VTEmit::Ground => cb.event(VTEvent::Raw(&input[range])),
VTEmit::Dcs => cb.event(VTEvent::DcsData(&input[range])),
VTEmit::Osc => cb.event(VTEvent::OscData(&input[range])),
}
.abort()
{
if state.hold {
self.held_byte = Some(0x1b);
}
return i + 1;
}
if state.hold {
held_byte = Some(0x1b);
}
state.current_emit = None;
}
}
VTAction::Event(e) => {
if cb.event(e).abort() {
return i + 1;
}
}
VTAction::End(VTEnd::Dcs) => {
state.current_emit = None;
let hold = std::mem::take(&mut state.hold);
let range = state.buffer_idx..(i.saturating_sub(hold as usize));
if cb.event(VTEvent::DcsEnd(&input[range])).abort() {
return i + 1;
}
held_byte = None;
}
VTAction::End(VTEnd::Osc { used_bel }) => {
state.current_emit = None;
let hold = std::mem::take(&mut state.hold);
let range = state.buffer_idx..(i.saturating_sub(hold as usize));
if cb
.event(VTEvent::OscEnd {
data: &input[range],
used_bel,
})
.abort()
{
return i + 1;
}
held_byte = None;
}
VTAction::Buffer(emit) | VTAction::Hold(emit) => {
if state.current_emit.is_none()
&& let Some(h) = held_byte.take()
&& match emit {
VTEmit::Ground => cb.event(VTEvent::Raw(&[h])),
VTEmit::Dcs => cb.event(VTEvent::DcsData(&[h])),
VTEmit::Osc => cb.event(VTEvent::OscData(&[h])),
}
.abort()
{
if matches!(action, VTAction::Hold(_)) {
self.held_byte = Some(0x1b);
return 1;
}
return 0;
}
debug_assert!(state.current_emit.is_none() || state.current_emit == Some(emit));
state.hold = matches!(action, VTAction::Hold(_));
if state.current_emit.is_none() {
state.buffer_idx = i;
state.current_emit = Some(emit);
}
}
VTAction::Cancel(emit) => {
state.current_emit = None;
state.hold = false;
if match emit {
VTEmit::Ground => unreachable!(),
VTEmit::Dcs => cb.event(VTEvent::DcsCancel),
VTEmit::Osc => cb.event(VTEvent::OscCancel),
}
.abort()
{
return i + 1;
}
}
};
i += 1;
}
if state.hold {
self.held_byte = Some(0x1b);
}
if let Some(emit) = state.current_emit.take() {
let range = &input[state.buffer_idx..input.len() - state.hold as usize];
if !range.is_empty() {
match emit {
VTEmit::Ground => cb.event(VTEvent::Raw(range)),
VTEmit::Dcs => cb.event(VTEvent::DcsData(range)),
VTEmit::Osc => cb.event(VTEvent::OscData(range)),
};
}
};
input.len()
}
pub fn is_ground(&self) -> bool {
self.st == State::Ground
}
pub fn idle(&mut self) -> Option<VTEvent<'static>> {
match self.st {
State::Escape => {
self.st = State::Ground;
Some(VTEvent::C0(ESC))
}
State::EscInt => {
self.st = State::Ground;
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
None
} else {
Some(invalid!(self.priv_prefix, self.ints))
}
}
State::EscSs2 | State::EscSs3 => {
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
self.st = State::Ground;
None
} else {
let c = match self.st {
State::EscSs2 => SS2,
State::EscSs3 => SS3,
_ => unreachable!(),
};
self.st = State::Ground;
Some(invalid!(c))
}
}
_ => None,
}
}
pub fn finish<F: FnMut(VTEvent)>(&mut self, mut cb: F) {
let held = self.held_byte.take();
if let Some(b) = held {
let buf = [b];
let event = match self.st {
State::OscString | State::OscEsc => VTEvent::OscData(&buf),
State::DcsPassthrough | State::DcsEsc => VTEvent::DcsData(&buf),
_ => VTEvent::Raw(&buf),
};
cb(event);
}
let event = match self.st {
State::OscString | State::OscEsc => Some(VTEvent::OscCancel),
State::DcsPassthrough | State::DcsEsc => Some(VTEvent::DcsCancel),
State::Escape => Some(VTEvent::C0(ESC)),
State::EscInt => {
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
None
} else {
Some(invalid!(self.priv_prefix, self.ints))
}
}
State::EscSs2 | State::EscSs3 => {
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
None
} else {
let c = match self.st {
State::EscSs2 => SS2,
State::EscSs3 => SS3,
_ => unreachable!(),
};
Some(invalid!(c))
}
}
_ => None,
};
self.reset_collectors();
self.st = State::Ground;
if let Some(event) = event {
cb(event);
}
}
fn clear_hdr_collectors(&mut self) {
self.ints.clear();
self.params.clear();
self.cur_param.clear();
self.priv_prefix = None;
}
fn reset_collectors(&mut self) {
self.clear_hdr_collectors();
}
fn next_param(&mut self) {
self.params.push(std::mem::take(&mut self.cur_param));
}
fn finish_params_if_any(&mut self) {
if !self.cur_param.is_empty() || !self.params.is_empty() {
self.next_param();
}
}
fn emit_csi(&mut self, final_byte: u8) -> VTAction<'_> {
self.finish_params_if_any();
let privp = self.priv_prefix.take();
VTAction::Event(VTEvent::Csi(CSI {
private: privp,
params: ParamBuf {
params: &self.params,
},
intermediates: self.ints,
final_byte,
}))
}
fn dcs_start(&mut self, final_byte: u8) -> VTAction<'_> {
self.finish_params_if_any();
let privp = self.priv_prefix.take();
VTAction::Event(VTEvent::DcsStart(DCS {
private: privp,
params: ParamBuf {
params: &self.params,
},
intermediates: self.ints,
final_byte,
}))
}
fn push_with(&mut self, b: u8) -> VTAction<'_> {
use State::*;
macro_rules! state_machine {
(def $c:ident $(
$state:ident => { $( $p:pat
$(if $( $if_ident:ident $( ($call_ident:ident) )? $( == $literal:literal)? $(||)? )+)?
=> $block:expr $(,)? )* }
)*) => {
#[allow(unused, clippy::redundant_pattern)]
match self.st {
$(
$state => {
match b {
$(
$c @ state_machine!(pattern $p $(if $( ($if_ident ($($call_ident)?) ($($literal)?)) )+ )?) => $block,
)*
}
}
)*
}
};
(pattern $pat:pat if $( ($if_ident:ident ($($call_ident:ident)?) ($($literal:literal)?)) )+ ) => {
$(
state_machine!(pattern_if ($if_ident) ($($call_ident)?) ($($literal)?) )
)|+
};
(pattern $pat:pat) => {
$pat
};
(pattern_if ($any:ident) ($call_ident:ident) ()) => {
$any!()
};
(pattern_if ($if_ident:ident) () ($literal:literal)) => {
$literal
};
}
state_machine! {
def c
Ground => {
ESC => {
self.clear_hdr_collectors();
self.st = State::Escape;
VTAction::None
}
DEL => VTAction::Event(VTEvent::C0(DEL)),
c if is_c0(c) => VTAction::Event(VTEvent::C0(c)),
_ => VTAction::Buffer(VTEmit::Ground),
}
Escape => {
CAN | SUB => {
self.st = Ground;
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
VTAction::None
} else {
VTAction::Event(invalid!(b))
}
}
DEL => {
self.st = Ground;
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
VTAction::None
} else {
VTAction::Event(invalid!(b))
}
}
c if is_intermediate(c) => {
if self.ints.push(c) {
self.st = EscInt;
} else {
self.st = Ground;
}
VTAction::None
}
b'?' => {
self.priv_prefix = Some(b);
self.st = EscInt;
VTAction::None
}
c if is_priv_no_q(c) => {
self.st = Ground;
VTAction::Event(VTEvent::Esc(Esc {
intermediates: VTIntermediate::empty(),
private: None,
final_byte: b,
}))
}
CSI => {
if INTEREST & VT_PARSER_INTEREST_CSI == 0 {
self.st = CsiIgnore;
} else {
self.st = CsiEntry;
}
VTAction::None
}
DCS => {
if INTEREST & VT_PARSER_INTEREST_DCS == 0 {
self.st = DcsIgnore;
} else {
self.st = DcsEntry;
}
VTAction::None
}
OSC => {
self.st = OscString;
VTAction::Event(VTEvent::OscStart)
}
SS2 => {
self.st = EscSs2;
VTAction::None
}
SS3 => {
self.st = EscSs3;
VTAction::None
}
SOS | PM | APC => {
self.st = State::SosPmApcString;
VTAction::None
}
c if is_final(c) || is_digit(c) => {
self.st = Ground;
VTAction::Event(VTEvent::Esc(Esc {
intermediates: self.ints,
private: self.priv_prefix.take(),
final_byte: c,
}))
}
ESC => {
VTAction::Event(VTEvent::C0(ESC))
}
_ => {
self.st = Ground;
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
VTAction::None
} else {
VTAction::Event(invalid!(b))
}
}
}
EscInt => {
CAN | SUB => {
self.st = Ground;
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
VTAction::None
} else {
VTAction::Event(invalid!(self.priv_prefix, self.ints, b))
}
}
DEL => {
self.st = Ground;
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
VTAction::None
} else {
VTAction::Event(invalid!(self.priv_prefix, self.ints, b))
}
}
c if is_intermediate(c) => {
if !self.ints.push(c) {
self.st = Ground;
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
VTAction::None
} else {
VTAction::Event(invalid!(self.priv_prefix, self.ints, b))
}
} else {
VTAction::None
}
}
c if is_final(c) || is_digit(c) => {
self.st = Ground;
VTAction::Event(VTEvent::Esc(Esc {
intermediates: self.ints,
private: self.priv_prefix.take(),
final_byte: c,
}))
}
ESC => {
self.st = Escape;
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
VTAction::None
} else {
VTAction::Event(invalid!(self.priv_prefix, self.ints))
}
}
_ => {
self.st = Ground;
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
VTAction::None
} else {
VTAction::Event(invalid!(self.priv_prefix, self.ints, c))
}
}
}
EscSs2 => {
CAN | SUB => {
self.st = Ground;
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
VTAction::None
} else {
VTAction::Event(invalid!(SS2, b))
}
}
ESC => {
self.st = Escape;
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
VTAction::None
} else {
VTAction::Event(invalid!(SS2))
}
}
_ => {
self.st = Ground;
VTAction::Event(VTEvent::Ss2(SS2 { char: c }))
}
}
EscSs3 => {
CAN | SUB => {
self.st = Ground;
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
VTAction::None
} else {
VTAction::Event(invalid!(SS3, b))
}
}
ESC => {
self.st = Escape;
if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
VTAction::None
} else {
VTAction::Event(invalid!(SS3))
}
}
_ => {
self.st = Ground;
VTAction::Event(VTEvent::Ss3(SS3 { char: c }))
}
}
CsiEntry => {
CAN | SUB => {
self.st = Ground;
VTAction::None
}
DEL => VTAction::None,
ESC => {
self.st = Escape;
VTAction::None
}
c if is_any_c0(c) => VTAction::Event(VTEvent::C0(c)),
c if is_priv(c) => {
self.priv_prefix = Some(c);
self.st = CsiParam;
VTAction::None
}
c if is_digit(c) => {
self.cur_param.push(c);
self.st = CsiParam;
VTAction::None
}
b';' => {
self.next_param();
self.st = CsiParam;
VTAction::None
}
b':' => {
self.cur_param.push(b':');
self.st = CsiParam;
VTAction::None
}
c if is_intermediate(c) => {
if self.ints.push(c) {
self.st = CsiInt;
} else {
self.st = Ground;
}
VTAction::None
}
c if is_final(c) => {
self.st = Ground;
self.emit_csi(c)
}
_ => {
self.st = CsiIgnore;
VTAction::None
}
}
CsiParam => {
CAN | SUB => {
self.st = Ground;
VTAction::None
}
DEL => VTAction::None,
ESC => {
self.st = Escape;
VTAction::None
}
c if is_any_c0(c) => VTAction::Event(VTEvent::C0(c)),
c if is_digit(c) => {
self.cur_param.push(c);
VTAction::None
}
b';' => {
self.next_param();
VTAction::None
}
b':' => {
self.cur_param.push(b':');
VTAction::None
}
c if is_intermediate(c) => {
if self.ints.push(c) {
self.st = CsiInt;
} else {
self.st = Ground;
}
VTAction::None
}
c if is_final(c) => {
self.st = Ground;
self.emit_csi(c)
}
_ => {
self.st = CsiIgnore;
VTAction::None
}
}
CsiInt => {
CAN | SUB => {
self.st = Ground;
VTAction::None
}
DEL => VTAction::None,
ESC => {
self.st = Escape;
VTAction::None
}
c if is_any_c0(c) => VTAction::Event(VTEvent::C0(c)),
c if is_intermediate(c) => {
if self.ints.push(c) {
self.st = CsiInt;
} else {
self.st = Ground;
}
VTAction::None
}
c if is_final(c) => {
self.st = Ground;
self.emit_csi(c)
}
_ => {
self.st = CsiIgnore;
VTAction::None
}
}
CsiIgnore => {
CAN | SUB => {
self.st = Ground;
VTAction::None
}
DEL => VTAction::None,
ESC => {
self.st = Escape;
VTAction::None
}
c if is_final(c) => {
self.st = Ground;
VTAction::None
}
_ => VTAction::None,
}
DcsEntry => {
CAN | SUB => {
self.st = Ground;
VTAction::None
}
DEL => VTAction::None,
ESC => {
self.st = Escape;
VTAction::None
}
c if is_priv(c) => {
self.priv_prefix = Some(c);
self.st = DcsParam;
VTAction::None
}
c if is_digit(c) => {
self.cur_param.push(c);
self.st = DcsParam;
VTAction::None
}
b';' => {
self.next_param();
self.st = DcsParam;
VTAction::None
}
b':' => {
self.st = DcsIgnore;
VTAction::None
}
c if is_intermediate(c) => {
if self.ints.push(c) {
self.st = DcsInt;
} else {
self.st = Ground;
}
VTAction::None
}
c if is_final(c) => {
self.st = DcsPassthrough;
self.dcs_start(c)
}
_ => {
self.st = DcsIgnore;
VTAction::None
}
}
DcsParam => {
CAN | SUB => {
self.st = Ground;
VTAction::None
}
DEL => VTAction::None,
ESC => {
self.st = Escape;
VTAction::None
}
c if is_digit(c) => {
self.cur_param.push(c);
VTAction::None
}
b';' => {
self.next_param();
VTAction::None
}
b':' => {
self.st = DcsIgnore;
VTAction::None
}
c if is_intermediate(c) => {
if self.ints.push(c) {
self.st = DcsInt;
} else {
self.st = Ground;
}
self.st = DcsInt;
VTAction::None
}
c if is_final(c) => {
self.st = DcsPassthrough;
self.dcs_start(c)
}
_ => {
self.st = DcsIgnore;
VTAction::None
}
}
DcsInt => {
CAN | SUB => {
self.st = Ground;
VTAction::None
}
DEL => VTAction::None,
ESC => {
self.st = Escape;
VTAction::None
}
c if is_intermediate(c) => {
if self.ints.push(c) {
self.st = DcsInt;
} else {
self.st = Ground;
}
VTAction::None
}
c if is_final(c) || is_digit(c) || c == b':' || c == b';' => {
self.st = DcsPassthrough;
self.dcs_start(c)
}
_ => {
self.st = DcsIgnore;
VTAction::None
}
}
DcsIgnore => {
CAN | SUB => {
self.st = Ground;
VTAction::None
}
DEL => VTAction::None,
ESC => {
self.st = DcsIgnoreEsc;
VTAction::None
}
_ => VTAction::None,
}
DcsIgnoreEsc => {
CAN | SUB => {
self.st = Ground;
VTAction::None
}
ST_FINAL => {
self.st = Ground;
VTAction::None
}
DEL => VTAction::None,
ESC => VTAction::None,
_ => {
self.st = DcsIgnore;
VTAction::None
}
}
DcsPassthrough => {
CAN | SUB => {
self.st = Ground;
VTAction::Cancel(VTEmit::Dcs)
}
DEL => VTAction::None,
ESC => {
self.st = DcsEsc;
VTAction::Hold(VTEmit::Dcs)
}
_ => VTAction::Buffer(VTEmit::Dcs),
}
DcsEsc => {
ST_FINAL => {
self.st = Ground;
VTAction::End(VTEnd::Dcs)
}
DEL => VTAction::None,
ESC => {
VTAction::Hold(VTEmit::Dcs)
}
_ => {
self.st = DcsPassthrough;
VTAction::Buffer(VTEmit::Dcs)
}
}
OscString => {
CAN | SUB => {
self.st = Ground;
VTAction::Cancel(VTEmit::Osc)
}
DEL => VTAction::None,
BEL => {
self.st = Ground;
VTAction::End(VTEnd::Osc { used_bel: true })
}
ESC => {
self.st = OscEsc;
VTAction::Hold(VTEmit::Osc)
}
p if is_printable(p) => VTAction::Buffer(VTEmit::Osc),
_ => VTAction::None, }
OscEsc => {
ST_FINAL => {
self.st = Ground;
VTAction::End(VTEnd::Osc { used_bel: false })
} ESC => VTAction::Hold(VTEmit::Osc),
DEL => VTAction::None,
_ => {
self.st = OscString;
VTAction::Buffer(VTEmit::Osc)
}
}
SosPmApcString => {
CAN | SUB => {
self.st = Ground;
VTAction::None
}
DEL => VTAction::None,
ESC => {
self.st = SpaEsc;
VTAction::None
}
_ => VTAction::None,
}
SpaEsc => {
ST_FINAL => {
self.st = Ground;
VTAction::None
}
DEL => VTAction::None,
ESC => {
VTAction::None
}
_ => {
self.st = State::SosPmApcString;
VTAction::None
}
}
}
}
}
#[cfg(test)]
mod tests {
use crate::event::VTOwnedEvent;
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_edge_cases() {
let mut result = String::new();
VTPushParser::decode_buffer(&[], |e| result.push_str(&format!("{e:?}\n")));
assert_eq!(result.trim(), "");
let mut result = String::new();
VTPushParser::decode_buffer(b"\x1b", |e| result.push_str(&format!("{e:?}\n")));
assert_eq!(result.trim(), "");
let mut result = String::new();
VTPushParser::decode_buffer(b"\x1b[", |e| result.push_str(&format!("{e:?}\n")));
assert_eq!(result.trim(), "");
let mut result = String::new();
VTPushParser::decode_buffer(b"\x1bP", |e| result.push_str(&format!("{e:?}\n")));
assert_eq!(result.trim(), "");
let mut result = String::new();
VTPushParser::decode_buffer(b"\x1b]", |e| result.push_str(&format!("{e:?}\n")));
assert_eq!(result.trim(), "OscStart");
}
#[test]
fn test_streaming_behavior() {
let mut parser = VTPushParser::new(); let mut result = String::new();
let mut callback = |vt_input: VTEvent<'_>| {
result.push_str(&format!("{vt_input:?}\n"));
};
parser.feed_with(b"\x1bP1;2;3 |", &mut callback);
parser.feed_with(b"data", &mut callback);
parser.feed_with(b" more", &mut callback);
parser.feed_with(b"\x1b\\", &mut callback);
assert_eq!(
result.trim(),
"DcsStart('1', '2', '3', ' ', |)\nDcsData('data')\nDcsData(' more')\nDcsEnd('')"
);
}
#[test]
fn test_dcs_payload_passthrough() {
let dcs_cases: &[(&[u8], &str)] = &[
(b"\x1bPq\x1b[38:2:12:34:56m\x1b\\", "<ESC>[38:2:12:34:56m"),
(b"\x1bPq\x1b[48:2:0:0:0m;xyz\x1b\\", "<ESC>[48:2:0:0:0m;xyz"),
(
b"\x1bP1$r\x1b[38:2:10:20:30;58:2::200:100:0m\x1b\\",
"<ESC>[38:2:10:20:30;58:2::200:100:0m",
),
(
b"\x1bPqABC\x1b\x1bDEF\x1bXG\x1b\\",
"ABC<ESC><ESC>DEF<ESC>XG",
),
(b"\x1bPqDATA\x07MORE\x1b\\", "DATA<BEL>MORE"),
(b"\x1bP!|\x1b[38:5:208m\x1b\\", "<ESC>[38:5:208m"),
(b"\x1bP>|Hello world\x1b\\", "Hello world"),
(
b"\x1bPq\x1b[38:2:1:2:3m\x1b[48:5:17m\x1b\\",
"<ESC>[38:2:1:2:3m<ESC>[48:5:17m",
),
(
b"\x1bPq\x1b[58:2::000:007:042m\x1b\\",
"<ESC>[58:2::000:007:042m",
),
];
for (input, expected_body) in dcs_cases {
let events = collect_owned_events(input);
let mut actual_body = Vec::new();
for event in &events {
match event {
VTOwnedEvent::DcsData(data) | VTOwnedEvent::DcsEnd(data) => {
actual_body.extend(data);
}
_ => {}
}
}
let actual_body = String::from_utf8(actual_body).unwrap();
let actual_body = actual_body
.replace("\x1b", "<ESC>")
.replace("\x07", "<BEL>");
assert_eq!(
actual_body, *expected_body,
"DCS payload mismatch for input {:?}. Full events: {:#?}",
input, events
);
}
}
fn collect_events(input: &[u8]) -> Vec<String> {
let mut out = Vec::new();
let mut p = VTPushParser::new();
p.feed_with(input, |ev: VTEvent| out.push(format!("{ev:?}")));
out
}
fn collect_owned_events(input: &[u8]) -> Vec<VTOwnedEvent> {
let mut out = Vec::new();
let mut p = VTPushParser::new();
p.feed_with(input, &mut |ev: VTEvent| out.push(ev.to_owned()));
out
}
#[test]
fn dcs_esc_esc_del() {
let ev = collect_events(b"\x1bP1;2;3|\x1b\x1b\x7fdata\x1b\\");
eprintln!("{ev:?}");
}
#[test]
fn osc_bel() {
let mut parser = VTPushParser::new();
let mut output = String::new();
for b in b"\x1b]X\x07" {
parser.feed_with(&[*b], &mut |ev: VTEvent| {
output.push_str(&format!("{ev:?}\n"));
});
}
assert_eq!(output.trim(), "OscStart\nOscData('X')\nOscEnd('')");
}
#[test]
fn dcs_header_with_colon_is_ignored_case1() {
let ev = collect_events(b"\x1bP1:2qHELLO\x1b\\");
assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
}
#[test]
fn dcs_header_with_colon_is_ignored_case2() {
let ev = collect_events(b"\x1bP:1qDATA\x1b\\");
assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
}
#[test]
fn dcs_header_with_colon_is_ignored_case3() {
let ev = collect_events(b"\x1bP12:34!qPAYLOAD\x1b\\");
assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
}
#[test]
fn osc_aborted_by_can_mid_body() {
let mut s = Vec::new();
s.extend_from_slice(b"\x1b]0;Title");
s.push(CAN);
s.extend_from_slice(b"more\x07");
let ev = collect_debug(&s);
assert!(ev.iter().any(|e| e.starts_with("OscStart")), "{ev:#?}");
assert!(!ev.iter().any(|e| e.starts_with("OscData")), "{ev:#?}");
assert!(!ev.iter().any(|e| e.starts_with("OscEnd")), "{ev:#?}");
}
#[test]
fn osc_aborted_by_sub_before_terminator() {
let mut s = Vec::new();
s.extend_from_slice(b"\x1b]52;c;YWJjZA==");
s.push(SUB); s.extend_from_slice(b"\x1b\\");
let ev = collect_debug(&s);
assert!(ev.iter().any(|e| e.starts_with("OscStart")), "{ev:#?}");
assert!(!ev.iter().any(|e| e.starts_with("OscData")), "{ev:#?}");
assert!(!ev.iter().any(|e| e.starts_with("OscEnd")), "{ev:#?}");
}
fn collect_debug(input: &[u8]) -> Vec<String> {
let mut out = Vec::new();
let mut p = VTPushParser::new();
p.feed_with(input, |ev: VTEvent| out.push(format!("{ev:?}")));
out
}
#[test]
fn dcs_aborted_by_can_before_body() {
let mut s = Vec::new();
s.extend_from_slice(b"\x1bPq"); s.push(CAN);
s.extend_from_slice(b"IGNORED\x1b\\");
let ev = collect_debug(&s);
assert_eq!(ev.len(), 4, "{ev:#?}");
assert_eq!(ev[0], "DcsStart('', q)");
assert_eq!(ev[1], "DcsCancel");
assert_eq!(ev[2], "Raw('IGNORED')");
assert_eq!(ev[3], "Esc('', \\)");
}
#[test]
fn dcs_aborted_by_can_mid_body() {
let mut s = Vec::new();
s.extend_from_slice(b"\x1bPqABC");
s.push(CAN);
s.extend_from_slice(b"MORE\x1b\\");
let ev = collect_debug(&s);
assert_eq!(ev.len(), 4, "{ev:#?}");
assert_eq!(ev[0], "DcsStart('', q)");
assert_eq!(ev[1], "DcsCancel");
assert_eq!(ev[2], "Raw('MORE')");
assert_eq!(ev[3], "Esc('', \\)");
}
#[test]
fn spa_aborted_by_can_is_ignored() {
let mut s = Vec::new();
s.extend_from_slice(b"\x1b_hello");
s.push(CAN);
s.extend_from_slice(b"world\x1b\\");
let ev = collect_debug(&s);
assert_eq!(ev.len(), 2, "{ev:#?}");
assert_eq!(ev[0], "Raw('world')");
assert_eq!(ev[1], "Esc('', \\)");
}
#[test]
fn spa_sub_aborts_too() {
let mut s = Vec::new();
s.extend_from_slice(b"\x1bXhello");
s.push(SUB);
s.extend_from_slice(b"world\x1b\\");
let ev = collect_debug(&s);
assert_eq!(ev.len(), 2, "{ev:#?}");
assert_eq!(ev[0], "Raw('world')");
assert_eq!(ev[1], "Esc('', \\)");
}
#[test]
fn can_in_ground_is_c0() {
let mut s = Vec::new();
s.extend_from_slice(b"abc");
s.push(CAN);
s.extend_from_slice(b"def");
let ev = collect_debug(&s);
assert_eq!(ev.len(), 3, "{ev:#?}");
assert_eq!(ev[0], "Raw('abc')");
assert_eq!(ev[1], "C0(18)");
assert_eq!(ev[2], "Raw('def')");
}
#[test]
fn three_byte_sequences_capturable() {
let mut bytes = vec![];
for i in 0..=0xFFFFFF_u32 {
bytes.clear();
let test_bytes = i.to_le_bytes();
let test_bytes = &test_bytes[..3];
if test_bytes.iter().any(|b| b == &0) {
continue;
}
if test_bytes[0] == 0x1b && matches!(test_bytes[1], CSI | DCS | OSC | APC | PM | SOS) {
continue;
}
if test_bytes[1] == 0x1b && matches!(test_bytes[2], CSI | DCS | OSC | APC | PM | SOS) {
continue;
}
let mut parser = VTPushParser::<VT_PARSER_INTEREST_ALL>::new_with();
parser.feed_with(test_bytes, |event: VTEvent| {
let mut chunk = [0_u8; 3];
let b = event.encode(&mut chunk).unwrap_or_else(|_| {
panic!("Failed to encode event {test_bytes:02X?} -> {event:?}")
});
bytes.extend_from_slice(&chunk[..b]);
});
if let Some(event) = parser.idle() {
let mut chunk = [0_u8; 3];
let b = event.encode(&mut chunk).unwrap_or_else(|_| {
panic!("Failed to encode event {test_bytes:02X?} -> {event:?}")
});
bytes.extend_from_slice(&chunk[..b]);
}
if bytes.len() != 3 || bytes != test_bytes {
eprintln!("Failed to parse:");
parser.feed_with(test_bytes, |event: VTEvent| {
eprintln!("{event:?}");
});
if let Some(event) = parser.idle() {
eprintln!("{event:?}");
}
assert_eq!(bytes, test_bytes, "{test_bytes:02X?} -> {bytes:02X?}");
}
}
}
#[test]
fn finish_after_esc_emits_c0_esc() {
let mut parser = VTPushParser::new();
let mut out = Vec::new();
parser.feed_with(b"abc\x1b", |ev: VTEvent| out.push(format!("{ev:?}")));
assert_eq!(out, ["Raw('abc')"]);
parser.finish(|ev: VTEvent| out.push(format!("{ev:?}")));
assert_eq!(out, ["Raw('abc')", "C0(1b)"]);
assert!(parser.is_ground());
}
#[test]
fn finish_mid_dcs_cancels() {
let mut parser = VTPushParser::new();
let mut out = Vec::new();
parser.feed_with(b"\x1bPqdata\x1b", |ev: VTEvent| out.push(format!("{ev:?}")));
parser.finish(|ev: VTEvent| out.push(format!("{ev:?}")));
assert_eq!(
out,
[
"DcsStart('', q)",
"DcsData('data')",
"DcsData('<ESC>')",
"DcsCancel"
]
);
assert!(parser.is_ground());
}
#[test]
fn finish_mid_osc_cancels() {
let mut parser = VTPushParser::new();
let mut out = Vec::new();
parser.feed_with(b"\x1b]0;title\x1b", |ev: VTEvent| {
out.push(format!("{ev:?}"))
});
parser.finish(|ev: VTEvent| out.push(format!("{ev:?}")));
assert_eq!(
out,
[
"OscStart",
"OscData('0;title')",
"OscData('<ESC>')",
"OscCancel"
]
);
assert!(parser.is_ground());
}
#[test]
fn finish_in_csi_header_drops_params() {
let mut parser = VTPushParser::new();
let mut out = Vec::new();
parser.feed_with(b"\x1b[1;2;3", |ev: VTEvent| out.push(format!("{ev:?}")));
parser.finish(|ev: VTEvent| out.push(format!("{ev:?}")));
assert!(out.is_empty(), "{out:#?}");
assert!(parser.is_ground());
parser.feed_with(b"\x1b[0m", |ev: VTEvent| out.push(format!("{ev:?}")));
assert_eq!(out, ["Csi('0', '', 'm')"], "{out:#?}");
}
#[test]
fn finish_is_idempotent() {
let mut parser = VTPushParser::new();
let mut count = 0;
parser.feed_with(b"\x1b]0;x\x1b", |_ev: VTEvent| {});
parser.finish(|_ev: VTEvent| count += 1);
parser.finish(|_ev: VTEvent| count += 1);
assert_eq!(count, 2);
}
}