vt_push_parser/
lib.rs

1//! A streaming push parser for the VT/xterm protocol.
2//!
3//! Use [`VTPushParser::feed_with`] to feed bytes into the parser, handling the
4//! [`VTEvent`]s as they are emitted.
5//!
6//! ```rust
7//! use vt_push_parser::VTPushParser;
8//!
9//! let mut parser = VTPushParser::new();
10//! let mut output = String::new();
11//! parser.feed_with(b"\x1b[32mHello, world!\x1b[0m", &mut |event| {
12//!     output.push_str(&format!("{:?}", event));
13//! });
14//! assert_eq!(output, "Csi('32', '', 'm')Raw('Hello, world!')Csi('0', '', 'm')");
15//! ```
16//!
17//! ## Interest
18//!
19//! The parser can be configured to only emit certain types of events by setting
20//! the `INTEREST` parameter. Other event types will be parsed and discarded.
21//!
22//! For example, to only emit CSI (and Raw) events:
23//!
24//! ```rust
25//! use vt_push_parser::{VTPushParser, VT_PARSER_INTEREST_CSI};
26//!
27//! let mut parser = VTPushParser::new_with_interest::<VT_PARSER_INTEREST_CSI>();
28//! ```
29//!
30//! ## Input parsing
31//!
32//! This crate is designed to be used for parsing terminal output, but it can
33//! also be used for parsing input. Input is not always well-formed, however and
34//! may contain mode-switching escapes that require the parser to turn off its
35//! normal parsing behaviours (ie: bracketed-paste mode, xterm mouse events,
36//! etc).
37//!
38//! The [`capture::VTCapturePushParser`] is useful for parsing input that may
39//! work in this way.
40pub mod ascii;
41pub mod capture;
42pub mod event;
43pub mod iter;
44pub mod signature;
45
46use smallvec::SmallVec;
47
48use ascii::AsciiControl;
49use event::{CSI, DCS, Esc, EscInvalid, SS2, SS3, VTEvent, VTIntermediate};
50
51const ESC: u8 = AsciiControl::Esc as _;
52const BEL: u8 = AsciiControl::Bel as _;
53const DEL: u8 = AsciiControl::Del as _;
54const CAN: u8 = AsciiControl::Can as _;
55const SUB: u8 = AsciiControl::Sub as _;
56const CSI: u8 = b'[';
57const OSC: u8 = b']';
58const SS2: u8 = b'N';
59const SS3: u8 = b'O';
60const DCS: u8 = b'P';
61const APC: u8 = b'_';
62const PM: u8 = b'^';
63const SOS: u8 = b'X';
64const ST_FINAL: u8 = b'\\';
65
66use crate::event::{Param, ParamBuf, Params};
67
68/// The action to take with the most recently accumulated byte.
69enum VTAction<'a> {
70    /// The parser will accumulate the byte and continue processing. If
71    /// currently buffered, emit the buffered bytes.
72    None,
73    /// The parser emitted an event. If currently buffered, emit the buffered
74    /// bytes.
75    Event(VTEvent<'a>),
76    /// The parser ended a region.
77    End(VTEnd),
78    /// Start or continue buffering bytes. Include the current byte in the
79    /// buffer.
80    Buffer(VTEmit),
81    /// Hold this byte until the next byte is received. If another byte is
82    /// already held, emit the previous byte.
83    Hold(VTEmit),
84    /// Cancel the current buffer.
85    Cancel(VTEmit),
86}
87
88#[derive(Debug, Copy, Clone, PartialEq, Eq)]
89enum VTEmit {
90    /// Emit this byte as a ground-state character.
91    Ground,
92    /// Emit this byte into the current DCS stream.
93    Dcs,
94    /// Emit this byte into the current OSC stream.
95    Osc,
96}
97
98#[derive(Debug, Copy, Clone, PartialEq, Eq)]
99enum VTEnd {
100    /// Emit this byte into the current DCS stream.
101    Dcs,
102    /// Emit this byte into the current OSC stream.
103    Osc { used_bel: bool },
104}
105
106#[inline]
107const fn is_c0(b: u8) -> bool {
108    // Control characters, with the exception of the common whitespace controls.
109    b <= 0x1F && b != b'\r' && b != b'\n' && b != b'\t'
110}
111#[inline]
112fn is_printable(b: u8) -> bool {
113    (0x20..=0x7E).contains(&b)
114}
115#[inline]
116fn is_intermediate(b: u8) -> bool {
117    (0x20..=0x2F).contains(&b)
118}
119#[inline]
120const fn is_final(b: u8) -> bool {
121    b >= 0x40 && b <= 0x7E
122}
123#[inline]
124fn is_digit(b: u8) -> bool {
125    b.is_ascii_digit()
126}
127#[inline]
128fn is_priv(b: u8) -> bool {
129    matches!(b, b'<' | b'=' | b'>' | b'?')
130}
131
132macro_rules! byte_predicate {
133    (|$p:ident| $body:block) => {{
134        let mut out: [bool; 256] = [false; 256];
135        let mut i = 0;
136        while i < 256 {
137            let $p: u8 = i as u8;
138            out[i] = $body;
139            i += 1;
140        }
141        out
142    }};
143}
144
145const ENDS_CSI: [bool; 256] =
146    byte_predicate!(|b| { is_final(b) || b == ESC || b == CAN || b == SUB });
147
148const ENDS_GROUND: [bool; 256] = byte_predicate!(|b| { is_c0(b) || b == DEL });
149
150#[derive(Debug, Copy, Clone, PartialEq, Eq)]
151enum State {
152    Ground,
153    Escape,
154    EscInt,
155    EscSs2,
156    EscSs3,
157    CsiEntry,
158    CsiParam,
159    CsiInt,
160    CsiIgnore,
161    DcsEntry,
162    DcsParam,
163    DcsInt,
164    DcsIgnore,
165    DcsIgnoreEsc,
166    DcsPassthrough,
167    DcsEsc,
168    OscString,
169    OscEsc,
170    SosPmApcString,
171    SpaEsc,
172}
173
174/// No events from parser (ie, only emits [`VTEvent::Raw`] events)
175pub const VT_PARSER_INTEREST_NONE: u8 = 0;
176/// Request CSI events from parser.
177pub const VT_PARSER_INTEREST_CSI: u8 = 1 << 0;
178/// Request DCS events from parser.
179pub const VT_PARSER_INTEREST_DCS: u8 = 1 << 1;
180/// Request OSC events from parser.
181pub const VT_PARSER_INTEREST_OSC: u8 = 1 << 2;
182/// Request escape recovery events from parser.
183pub const VT_PARSER_INTEREST_ESCAPE_RECOVERY: u8 = 1 << 4;
184/// Request other events from parser.
185pub const VT_PARSER_INTEREST_OTHER: u8 = 1 << 5;
186
187/// Request all events from parser.
188pub const VT_PARSER_INTEREST_ALL: u8 = VT_PARSER_INTEREST_CSI
189    | VT_PARSER_INTEREST_DCS
190    | VT_PARSER_INTEREST_OSC
191    | VT_PARSER_INTEREST_ESCAPE_RECOVERY
192    | VT_PARSER_INTEREST_OTHER;
193
194/// Default interest level.
195pub const VT_PARSER_INTEREST_DEFAULT: u8 = VT_PARSER_INTEREST_CSI
196    | VT_PARSER_INTEREST_DCS
197    | VT_PARSER_INTEREST_OSC
198    | VT_PARSER_INTEREST_OTHER;
199
200#[must_use]
201trait MaybeAbortable {
202    fn abort(self) -> bool;
203}
204
205impl MaybeAbortable for bool {
206    #[inline(always)]
207    fn abort(self) -> bool {
208        !self
209    }
210}
211
212impl MaybeAbortable for () {
213    #[inline(always)]
214    fn abort(self) -> bool {
215        false
216    }
217}
218
219/// A push parser for the VT/xterm protocol.
220///
221/// The parser can be configured to only emit certain types of events by setting
222/// the `INTEREST` parameter.
223pub struct VTPushParser<const INTEREST: u8 = VT_PARSER_INTEREST_DEFAULT> {
224    st: State,
225
226    // Header collectors for short escapes (we borrow from these in callbacks)
227    ints: VTIntermediate,
228    params: Params,
229    cur_param: Param,
230    priv_prefix: Option<u8>,
231    held_byte: Option<u8>,
232}
233
234impl Default for VTPushParser {
235    fn default() -> Self {
236        Self::new()
237    }
238}
239
240impl VTPushParser {
241    pub const fn new() -> Self {
242        VTPushParser::new_with()
243    }
244
245    /// Decode a buffer of bytes into a series of events.
246    pub fn decode_buffer<'a>(input: &'a [u8], mut cb: impl for<'b> FnMut(VTEvent<'b>)) {
247        let mut parser = VTPushParser::new();
248        parser.feed_with(input, &mut cb);
249    }
250
251    pub const fn new_with_interest<const INTEREST: u8>() -> VTPushParser<INTEREST> {
252        VTPushParser::new_with()
253    }
254}
255
256/// Emit the EscInvalid event
257macro_rules! invalid {
258    ($self:ident .priv_prefix, $self_:ident .ints, $b:expr) => {
259        if let Some(p) = $self.priv_prefix {
260            if $self.ints.len() == 0 {
261                VTEvent::EscInvalid(EscInvalid::Two(p, $b))
262            } else if $self.ints.len() == 1 {
263                VTEvent::EscInvalid(EscInvalid::Three(p, $self.ints.data[0], $b))
264            } else {
265                VTEvent::EscInvalid(EscInvalid::Four(
266                    p,
267                    $self.ints.data[0],
268                    $self.ints.data[1],
269                    $b,
270                ))
271            }
272        } else {
273            if $self.ints.len() == 0 {
274                VTEvent::EscInvalid(EscInvalid::One($b))
275            } else if $self.ints.len() == 1 {
276                VTEvent::EscInvalid(EscInvalid::Two($self.ints.data[0], $b))
277            } else {
278                VTEvent::EscInvalid(EscInvalid::Three(
279                    $self.ints.data[0],
280                    $self.ints.data[1],
281                    $b,
282                ))
283            }
284        }
285    };
286    ($self:ident .priv_prefix, $self_:ident .ints) => {
287        if let Some(p) = $self.priv_prefix {
288            if $self.ints.len() == 0 {
289                VTEvent::EscInvalid(EscInvalid::One(p))
290            } else if $self.ints.len() == 1 {
291                VTEvent::EscInvalid(EscInvalid::Two(p, $self.ints.data[0]))
292            } else {
293                VTEvent::EscInvalid(EscInvalid::Three(p, $self.ints.data[0], $self.ints.data[1]))
294            }
295        } else {
296            if $self.ints.len() == 0 {
297                // I don't think this can happen
298                VTEvent::C0(0x1b)
299            } else if $self.ints.len() == 1 {
300                VTEvent::EscInvalid(EscInvalid::One($self.ints.data[0]))
301            } else {
302                VTEvent::EscInvalid(EscInvalid::Two($self.ints.data[0], $self.ints.data[1]))
303            }
304        }
305    };
306    ($a:expr) => {
307        VTEvent::EscInvalid(EscInvalid::One($a))
308    };
309    ($a:expr, $b:expr) => {
310        VTEvent::EscInvalid(EscInvalid::Two($a, $b))
311    };
312}
313
314impl<const INTEREST: u8> VTPushParser<INTEREST> {
315    const fn new_with() -> Self {
316        Self {
317            st: State::Ground,
318            ints: VTIntermediate::empty(),
319            params: SmallVec::new_const(),
320            cur_param: SmallVec::new_const(),
321            priv_prefix: None,
322            held_byte: None,
323        }
324    }
325
326    // =====================
327    // Callback-driven API
328    // =====================
329
330    /// Feed bytes into the parser. This is the main entry point for the parser.
331    /// It will call the callback with events as they are emitted.
332    ///
333    /// The callback must be valid for the lifetime of the `feed_with` call.
334    ///
335    /// The callback may emit any number of events (including zero), depending
336    /// on the state of the internal parser.
337    #[inline]
338    pub fn feed_with<'this, 'input, F: for<'any> FnMut(VTEvent<'any>)>(
339        &'this mut self,
340        input: &'input [u8],
341        cb: &mut F,
342    ) {
343        self.feed_with_internal(input, cb);
344    }
345
346    /// Feed bytes into the parser. This is the main entry point for the parser.
347    /// It will call the callback with events as they are emitted.
348    ///
349    /// The callback must be valid for the lifetime of the `feed_with` call.
350    /// Returning `true` will continue parsing, while returning `false` will
351    /// stop.
352    ///
353    /// The callback may emit any number of events (including zero), depending
354    /// on the state of the internal parser.
355    ///
356    /// This function returns the number of bytes processed. Note that some
357    /// bytes may have been processed any not emitted.
358    #[inline]
359    pub fn feed_with_abortable<'this, 'input, F: for<'any> FnMut(VTEvent<'any>) -> bool>(
360        &'this mut self,
361        input: &'input [u8],
362        cb: &mut F,
363    ) -> usize {
364        self.feed_with_internal(input, cb)
365    }
366
367    #[inline(always)]
368    fn feed_with_internal<
369        'this,
370        'input,
371        R: MaybeAbortable,
372        F: for<'any> FnMut(VTEvent<'any>) -> R,
373    >(
374        &'this mut self,
375        input: &'input [u8],
376        cb: &mut F,
377    ) -> usize {
378        if input.is_empty() {
379            return 0;
380        }
381
382        #[derive(Debug)]
383        struct FeedState {
384            buffer_idx: usize,
385            current_emit: Option<VTEmit>,
386            hold: bool,
387        }
388
389        let mut state = FeedState {
390            buffer_idx: 0,
391            current_emit: None,
392            hold: self.held_byte.is_some(),
393        };
394
395        macro_rules! emit {
396            ($state:ident, $i:expr, $cb:expr, $end:expr, $used_bel:expr) => {
397                let hold = std::mem::take(&mut $state.hold);
398                if let Some(emit) = $state.current_emit.take() {
399                    let i = $i;
400                    let range = $state.buffer_idx..(i - hold as usize);
401                    if $end {
402                        if match emit {
403                            VTEmit::Ground => unreachable!(),
404                            VTEmit::Dcs => $cb(VTEvent::DcsEnd(&input[range])),
405                            VTEmit::Osc => $cb(VTEvent::OscEnd {
406                                data: &input[range],
407                                used_bel: $used_bel,
408                            }),
409                        }
410                        .abort()
411                        {
412                            return i + 1;
413                        }
414                    } else if range.len() > 0 {
415                        if match emit {
416                            VTEmit::Ground => $cb(VTEvent::Raw(&input[range])),
417                            VTEmit::Dcs => $cb(VTEvent::DcsData(&input[range])),
418                            VTEmit::Osc => $cb(VTEvent::OscData(&input[range])),
419                        }
420                        .abort()
421                        {
422                            return i + 1;
423                        }
424                    }
425                }
426            };
427        }
428
429        let mut held_byte = self.held_byte.take();
430        let mut i = 0;
431
432        while i < input.len() {
433            // Fast path for the common case of no ANSI escape sequences.
434            if self.st == State::Ground {
435                let start = i;
436                loop {
437                    if i >= input.len() {
438                        cb(VTEvent::Raw(&input[start..]));
439                        return input.len();
440                    }
441                    if ENDS_GROUND[input[i] as usize] {
442                        break;
443                    }
444                    i += 1;
445                }
446
447                if start != i && cb(VTEvent::Raw(&input[start..i])).abort() {
448                    return i;
449                }
450
451                if input[i] == ESC {
452                    self.clear_hdr_collectors();
453                    self.st = State::Escape;
454                    i += 1;
455                    continue;
456                }
457            }
458
459            // Fast path: search for the CSI final
460            if self.st == State::CsiIgnore {
461                loop {
462                    if i >= input.len() {
463                        return input.len();
464                    }
465                    if ENDS_CSI[input[i] as usize] {
466                        break;
467                    }
468                    i += 1;
469                }
470
471                if input[i] == ESC {
472                    self.st = State::Escape;
473                } else {
474                    self.st = State::Ground;
475                }
476                i += 1;
477                continue;
478            }
479
480            let action = self.push_with(input[i]);
481
482            match action {
483                VTAction::None => {
484                    if let Some(emit) = state.current_emit {
485                        // We received a DEL during an emit, so we need to partially emit our buffer
486                        let range = state.buffer_idx..(i - state.hold as usize);
487                        if !range.is_empty()
488                            && match emit {
489                                VTEmit::Ground => cb(VTEvent::Raw(&input[range])),
490                                VTEmit::Dcs => cb(VTEvent::DcsData(&input[range])),
491                                VTEmit::Osc => cb(VTEvent::OscData(&input[range])),
492                            }
493                            .abort()
494                        {
495                            if state.hold {
496                                self.held_byte = Some(0x1b);
497                            }
498                            return i + 1;
499                        }
500                        if state.hold {
501                            held_byte = Some(0x1b);
502                        }
503                        state.current_emit = None;
504                    }
505                }
506                VTAction::Event(e) => {
507                    if cb(e).abort() {
508                        return i + 1;
509                    }
510                }
511                VTAction::End(VTEnd::Dcs) => {
512                    held_byte = None;
513                    emit!(state, i, cb, true, false);
514                }
515                VTAction::End(VTEnd::Osc { used_bel }) => {
516                    held_byte = None;
517                    emit!(state, i, cb, true, used_bel);
518                }
519                VTAction::Buffer(emit) | VTAction::Hold(emit) => {
520                    if state.current_emit.is_none()
521                        && let Some(h) = held_byte.take()
522                        && match emit {
523                            VTEmit::Ground => cb(VTEvent::Raw(&[h])),
524                            VTEmit::Dcs => cb(VTEvent::DcsData(&[h])),
525                            VTEmit::Osc => cb(VTEvent::OscData(&[h])),
526                        }
527                        .abort()
528                    {
529                        if matches!(action, VTAction::Hold(_)) {
530                            self.held_byte = Some(0x1b);
531                            return 1;
532                        }
533                        return 0;
534                    }
535
536                    debug_assert!(state.current_emit.is_none() || state.current_emit == Some(emit));
537
538                    state.hold = matches!(action, VTAction::Hold(_));
539                    if state.current_emit.is_none() {
540                        state.buffer_idx = i;
541                        state.current_emit = Some(emit);
542                    }
543                }
544                VTAction::Cancel(emit) => {
545                    state.current_emit = None;
546                    state.hold = false;
547                    if match emit {
548                        VTEmit::Ground => unreachable!(),
549                        VTEmit::Dcs => cb(VTEvent::DcsCancel),
550                        VTEmit::Osc => cb(VTEvent::OscCancel),
551                    }
552                    .abort()
553                    {
554                        return i + 1;
555                    }
556                }
557            };
558            i += 1;
559        }
560
561        // Is there more to emit?
562        if state.hold {
563            self.held_byte = Some(0x1b);
564        }
565
566        if let Some(emit) = state.current_emit.take() {
567            let range = &input[state.buffer_idx..input.len() - state.hold as usize];
568            if !range.is_empty() {
569                match emit {
570                    VTEmit::Ground => cb(VTEvent::Raw(range)),
571                    VTEmit::Dcs => cb(VTEvent::DcsData(range)),
572                    VTEmit::Osc => cb(VTEvent::OscData(range)),
573                };
574            }
575        };
576
577        // If we get this far, we processed the whole buffer
578        input.len()
579    }
580
581    /// Returns true if the parser is in the ground state.
582    pub fn is_ground(&self) -> bool {
583        self.st == State::Ground
584    }
585
586    /// Feed an idle event into the parser. This will emit a C0(ESC) event if
587    /// the parser is in the Escape state, and will silently cancel any EscInt
588    /// state.
589    pub fn idle(&mut self) -> Option<VTEvent<'static>> {
590        match self.st {
591            State::Escape => {
592                self.st = State::Ground;
593                Some(VTEvent::C0(ESC))
594            }
595            State::EscInt => {
596                self.st = State::Ground;
597                if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
598                    None
599                } else {
600                    Some(invalid!(self.priv_prefix, self.ints))
601                }
602            }
603            State::EscSs2 | State::EscSs3 => {
604                if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
605                    self.st = State::Ground;
606                    None
607                } else {
608                    let c = match self.st {
609                        State::EscSs2 => SS2,
610                        State::EscSs3 => SS3,
611                        _ => unreachable!(),
612                    };
613                    self.st = State::Ground;
614                    Some(invalid!(c))
615                }
616            }
617            _ => None,
618        }
619    }
620
621    fn push_with(&mut self, b: u8) -> VTAction {
622        use State::*;
623        match self.st {
624            Ground => self.on_ground(b),
625            Escape => self.on_escape(b),
626            EscInt => self.on_esc_int(b),
627            EscSs2 => self.on_esc_ss2(b),
628            EscSs3 => self.on_esc_ss3(b),
629
630            CsiEntry => self.on_csi_entry(b),
631            CsiParam => self.on_csi_param(b),
632            CsiInt => self.on_csi_int(b),
633            CsiIgnore => self.on_csi_ignore(b),
634
635            DcsEntry => self.on_dcs_entry(b),
636            DcsParam => self.on_dcs_param(b),
637            DcsInt => self.on_dcs_int(b),
638            DcsIgnore => self.on_dcs_ignore(b),
639            DcsIgnoreEsc => self.on_dcs_ignore_esc(b),
640            DcsPassthrough => self.on_dcs_pass(b),
641            DcsEsc => self.on_dcs_esc(b),
642
643            OscString => self.on_osc_string(b),
644            OscEsc => self.on_osc_esc(b),
645
646            SosPmApcString => self.on_spa_string(b),
647            SpaEsc => self.on_spa_esc(b),
648        }
649    }
650
651    pub fn finish<F: FnMut(VTEvent)>(&mut self, _cb: &mut F) {
652        self.reset_collectors();
653        self.st = State::Ground;
654
655        // TODO
656    }
657
658    // =====================
659    // Emit helpers (borrowed)
660    // =====================
661
662    fn clear_hdr_collectors(&mut self) {
663        self.ints.clear();
664        self.params.clear();
665        self.cur_param.clear();
666        self.priv_prefix = None;
667    }
668
669    fn reset_collectors(&mut self) {
670        self.clear_hdr_collectors();
671    }
672
673    fn next_param(&mut self) {
674        self.params.push(std::mem::take(&mut self.cur_param));
675    }
676
677    fn finish_params_if_any(&mut self) {
678        if !self.cur_param.is_empty() || !self.params.is_empty() {
679            self.next_param();
680        }
681    }
682
683    fn emit_csi(&mut self, final_byte: u8) -> VTAction {
684        self.finish_params_if_any();
685
686        // Build borrowed views into self.params
687        let mut borrowed: SmallVec<[&[u8]; 4]> = SmallVec::new();
688        borrowed.extend(self.params.iter().map(|v| v.as_slice()));
689
690        let privp = self.priv_prefix.take();
691        VTAction::Event(VTEvent::Csi(CSI {
692            private: privp,
693            params: ParamBuf {
694                params: &self.params,
695            },
696            intermediates: self.ints,
697            final_byte,
698        }))
699    }
700
701    fn dcs_start(&mut self, final_byte: u8) -> VTAction {
702        self.finish_params_if_any();
703
704        let privp = self.priv_prefix.take();
705        VTAction::Event(VTEvent::DcsStart(DCS {
706            private: privp,
707            params: ParamBuf {
708                params: &self.params,
709            },
710            intermediates: self.ints,
711            final_byte,
712        }))
713    }
714
715    // =====================
716    // State handlers
717    // =====================
718
719    fn on_ground(&mut self, b: u8) -> VTAction {
720        match b {
721            ESC => {
722                self.clear_hdr_collectors();
723                self.st = State::Escape;
724                VTAction::None
725            }
726            DEL => VTAction::Event(VTEvent::C0(DEL)),
727            c if is_c0(c) => VTAction::Event(VTEvent::C0(c)),
728            p if is_printable(p) => VTAction::Buffer(VTEmit::Ground),
729            _ => VTAction::Buffer(VTEmit::Ground), // safe fallback
730        }
731    }
732
733    fn on_escape(&mut self, b: u8) -> VTAction {
734        use State::*;
735        match b {
736            CAN | SUB => {
737                self.st = Ground;
738                if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
739                    VTAction::None
740                } else {
741                    VTAction::Event(invalid!(b))
742                }
743            }
744            // NOTE: DEL should be ignored normally, but for better recovery,
745            // we move to ground state here instead.
746            DEL => {
747                self.st = Ground;
748                if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
749                    VTAction::None
750                } else {
751                    VTAction::Event(invalid!(b))
752                }
753            }
754            c if is_intermediate(c) => {
755                if self.ints.push(c) {
756                    self.st = EscInt;
757                } else {
758                    self.st = Ground;
759                }
760                VTAction::None
761            }
762            c if is_priv(c) => {
763                self.priv_prefix = Some(c);
764                self.st = EscInt;
765                VTAction::None
766            }
767            CSI => {
768                if INTEREST & VT_PARSER_INTEREST_CSI == 0 {
769                    self.st = CsiIgnore;
770                } else {
771                    self.st = CsiEntry;
772                }
773                VTAction::None
774            }
775            DCS => {
776                if INTEREST & VT_PARSER_INTEREST_DCS == 0 {
777                    self.st = DcsIgnore;
778                } else {
779                    self.st = DcsEntry;
780                }
781                VTAction::None
782            }
783            OSC => {
784                self.st = OscString;
785                VTAction::Event(VTEvent::OscStart)
786            }
787            SS2 => {
788                self.st = EscSs2;
789                VTAction::None
790            }
791            SS3 => {
792                self.st = EscSs3;
793                VTAction::None
794            }
795            SOS | PM | APC => {
796                self.st = State::SosPmApcString;
797                VTAction::None
798            }
799            c if is_final(c) || is_digit(c) => {
800                self.st = Ground;
801                VTAction::Event(VTEvent::Esc(Esc {
802                    intermediates: self.ints,
803                    private: self.priv_prefix.take(),
804                    final_byte: c,
805                }))
806            }
807            ESC => {
808                // ESC ESC allowed, but we stay in the current state
809                VTAction::Event(VTEvent::C0(ESC))
810            }
811            _ => {
812                self.st = Ground;
813                if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
814                    VTAction::None
815                } else {
816                    VTAction::Event(invalid!(b))
817                }
818            }
819        }
820    }
821
822    fn on_esc_int(&mut self, b: u8) -> VTAction {
823        use State::*;
824        match b {
825            CAN | SUB => {
826                self.st = Ground;
827                if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
828                    VTAction::None
829                } else {
830                    VTAction::Event(invalid!(self.priv_prefix, self.ints, b))
831                }
832            }
833            // NOTE: DEL should be ignored normally, but for better recovery,
834            // we move to ground state here instead.
835            DEL => {
836                self.st = Ground;
837                if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
838                    VTAction::None
839                } else {
840                    VTAction::Event(invalid!(self.priv_prefix, self.ints, b))
841                }
842            }
843            c if is_intermediate(c) => {
844                if !self.ints.push(c) {
845                    self.st = Ground;
846                    if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
847                        VTAction::None
848                    } else {
849                        VTAction::Event(invalid!(self.priv_prefix, self.ints, b))
850                    }
851                } else {
852                    VTAction::None
853                }
854            }
855            c if is_final(c) || is_digit(c) => {
856                self.st = Ground;
857                VTAction::Event(VTEvent::Esc(Esc {
858                    intermediates: self.ints,
859                    private: self.priv_prefix.take(),
860                    final_byte: c,
861                }))
862            }
863            // NOTE: We assume that we want to stay in the escape state
864            // to recover from this state.
865            ESC => {
866                self.st = Escape;
867                if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
868                    VTAction::None
869                } else {
870                    VTAction::Event(invalid!(self.priv_prefix, self.ints))
871                }
872            }
873            c => {
874                self.st = Ground;
875                if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
876                    VTAction::None
877                } else {
878                    VTAction::Event(invalid!(self.priv_prefix, self.ints, c))
879                }
880            }
881        }
882    }
883
884    fn on_esc_ss2(&mut self, b: u8) -> VTAction {
885        use State::*;
886        self.st = Ground;
887        match b {
888            CAN | SUB => {
889                if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
890                    VTAction::None
891                } else {
892                    VTAction::Event(invalid!(SS2, b))
893                }
894            }
895            // NOTE: We assume that we want to stay in the escape state
896            // to recover from this state.
897            ESC => {
898                self.st = Escape;
899                if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
900                    VTAction::None
901                } else {
902                    VTAction::Event(invalid!(SS2))
903                }
904            }
905            c => VTAction::Event(VTEvent::Ss2(SS2 { char: c })),
906        }
907    }
908
909    fn on_esc_ss3(&mut self, b: u8) -> VTAction {
910        use State::*;
911        self.st = Ground;
912        match b {
913            CAN | SUB => {
914                if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
915                    VTAction::None
916                } else {
917                    VTAction::Event(invalid!(SS3, b))
918                }
919            }
920            // NOTE: We assume that we want to stay in the escape state
921            // to recover from this state.
922            ESC => {
923                self.st = Escape;
924                if INTEREST & VT_PARSER_INTEREST_ESCAPE_RECOVERY == 0 {
925                    VTAction::None
926                } else {
927                    VTAction::Event(invalid!(SS3))
928                }
929            }
930            c => VTAction::Event(VTEvent::Ss3(SS3 { char: c })),
931        }
932    }
933
934    // ---- CSI
935    fn on_csi_entry(&mut self, b: u8) -> VTAction {
936        use State::*;
937        match b {
938            CAN | SUB => {
939                self.st = Ground;
940                VTAction::None
941            }
942            DEL => VTAction::None,
943            ESC => {
944                self.st = Escape;
945                VTAction::None
946            }
947            c if is_priv(c) => {
948                self.priv_prefix = Some(c);
949                self.st = CsiParam;
950                VTAction::None
951            }
952            d if is_digit(d) => {
953                self.cur_param.push(d);
954                self.st = CsiParam;
955                VTAction::None
956            }
957            b';' => {
958                self.next_param();
959                self.st = CsiParam;
960                VTAction::None
961            }
962            b':' => {
963                self.cur_param.push(b':');
964                self.st = CsiParam;
965                VTAction::None
966            }
967            c if is_intermediate(c) => {
968                if self.ints.push(c) {
969                    self.st = CsiInt;
970                } else {
971                    self.st = Ground;
972                }
973                VTAction::None
974            }
975            c if is_final(c) => {
976                self.st = Ground;
977                self.emit_csi(c)
978            }
979            _ => {
980                self.st = CsiIgnore;
981                VTAction::None
982            }
983        }
984    }
985
986    fn on_csi_param(&mut self, b: u8) -> VTAction {
987        use State::*;
988        match b {
989            CAN | SUB => {
990                self.st = Ground;
991                VTAction::None
992            }
993            DEL => VTAction::None,
994            ESC => {
995                self.st = Escape;
996                VTAction::None
997            }
998            d if is_digit(d) => {
999                self.cur_param.push(d);
1000                VTAction::None
1001            }
1002            b';' => {
1003                self.next_param();
1004                VTAction::None
1005            }
1006            b':' => {
1007                self.cur_param.push(b':');
1008                VTAction::None
1009            }
1010            c if is_intermediate(c) => {
1011                if self.ints.push(c) {
1012                    self.st = CsiInt;
1013                } else {
1014                    self.st = Ground;
1015                }
1016                VTAction::None
1017            }
1018            c if is_final(c) => {
1019                self.st = Ground;
1020                self.emit_csi(c)
1021            }
1022            _ => {
1023                self.st = CsiIgnore;
1024                VTAction::None
1025            }
1026        }
1027    }
1028
1029    fn on_csi_int(&mut self, b: u8) -> VTAction {
1030        use State::*;
1031        match b {
1032            CAN | SUB => {
1033                self.st = Ground;
1034                VTAction::None
1035            }
1036            DEL => VTAction::None,
1037            ESC => {
1038                self.st = Escape;
1039                VTAction::None
1040            }
1041            c if is_intermediate(c) => {
1042                if self.ints.push(c) {
1043                    self.st = CsiInt;
1044                } else {
1045                    self.st = Ground;
1046                }
1047                VTAction::None
1048            }
1049            c if is_final(c) => {
1050                self.st = Ground;
1051                self.emit_csi(c)
1052            }
1053            _ => {
1054                self.st = CsiIgnore;
1055                VTAction::None
1056            }
1057        }
1058    }
1059
1060    fn on_csi_ignore(&mut self, b: u8) -> VTAction {
1061        use State::*;
1062        match b {
1063            CAN | SUB => {
1064                self.st = Ground;
1065                VTAction::None
1066            }
1067            DEL => VTAction::None,
1068            ESC => {
1069                self.st = Escape;
1070                VTAction::None
1071            }
1072            c if is_final(c) => {
1073                self.st = Ground;
1074                VTAction::None
1075            }
1076            _ => VTAction::None,
1077        }
1078    }
1079
1080    // ---- DCS
1081    fn on_dcs_entry(&mut self, b: u8) -> VTAction {
1082        use State::*;
1083        match b {
1084            CAN | SUB => {
1085                self.st = Ground;
1086                VTAction::None
1087            }
1088            DEL => VTAction::None,
1089            ESC => {
1090                self.st = Escape;
1091                VTAction::None
1092            }
1093            c if is_priv(c) => {
1094                self.priv_prefix = Some(c);
1095                self.st = DcsParam;
1096                VTAction::None
1097            }
1098            d if is_digit(d) => {
1099                self.cur_param.push(d);
1100                self.st = DcsParam;
1101                VTAction::None
1102            }
1103            b';' => {
1104                self.next_param();
1105                self.st = DcsParam;
1106                VTAction::None
1107            }
1108            b':' => {
1109                self.st = DcsIgnore;
1110                VTAction::None
1111            }
1112            c if is_intermediate(c) => {
1113                if self.ints.push(c) {
1114                    self.st = DcsInt;
1115                } else {
1116                    self.st = Ground;
1117                }
1118                VTAction::None
1119            }
1120            c if is_final(c) => {
1121                self.st = DcsPassthrough;
1122                self.dcs_start(c)
1123            }
1124            _ => {
1125                self.st = DcsIgnore;
1126                VTAction::None
1127            }
1128        }
1129    }
1130
1131    fn on_dcs_param(&mut self, b: u8) -> VTAction {
1132        use State::*;
1133        match b {
1134            CAN | SUB => {
1135                self.st = Ground;
1136                VTAction::None
1137            }
1138            DEL => VTAction::None,
1139            ESC => {
1140                self.st = Escape;
1141                VTAction::None
1142            }
1143            d if is_digit(d) => {
1144                self.cur_param.push(d);
1145                VTAction::None
1146            }
1147            b';' => {
1148                self.next_param();
1149                VTAction::None
1150            }
1151            b':' => {
1152                self.st = DcsIgnore;
1153                VTAction::None
1154            }
1155            c if is_intermediate(c) => {
1156                if self.ints.push(c) {
1157                    self.st = DcsInt;
1158                } else {
1159                    self.st = Ground;
1160                }
1161                self.st = DcsInt;
1162                VTAction::None
1163            }
1164            c if is_final(c) => {
1165                self.st = DcsPassthrough;
1166                self.dcs_start(c)
1167            }
1168            _ => {
1169                self.st = DcsIgnore;
1170                VTAction::None
1171            }
1172        }
1173    }
1174
1175    fn on_dcs_int(&mut self, b: u8) -> VTAction {
1176        use State::*;
1177        match b {
1178            CAN | SUB => {
1179                self.st = Ground;
1180                VTAction::None
1181            }
1182            DEL => VTAction::None,
1183            ESC => {
1184                self.st = Escape;
1185                VTAction::None
1186            }
1187            c if is_intermediate(c) => {
1188                if self.ints.push(c) {
1189                    self.st = DcsInt;
1190                } else {
1191                    self.st = Ground;
1192                }
1193                VTAction::None
1194            }
1195            c if is_final(c) || is_digit(c) || c == b':' || c == b';' => {
1196                self.st = DcsPassthrough;
1197                self.dcs_start(c)
1198            }
1199            _ => {
1200                self.st = DcsIgnore;
1201                VTAction::None
1202            }
1203        }
1204    }
1205
1206    fn on_dcs_ignore(&mut self, b: u8) -> VTAction {
1207        use State::*;
1208        match b {
1209            CAN | SUB => {
1210                self.st = Ground;
1211                VTAction::None
1212            }
1213            DEL => VTAction::None,
1214            ESC => {
1215                self.st = DcsIgnoreEsc;
1216                VTAction::None
1217            }
1218            _ => VTAction::None,
1219        }
1220    }
1221
1222    fn on_dcs_ignore_esc(&mut self, b: u8) -> VTAction {
1223        use State::*;
1224        match b {
1225            CAN | SUB => {
1226                self.st = Ground;
1227                VTAction::None
1228            }
1229            ST_FINAL => {
1230                self.st = Ground;
1231                VTAction::None
1232            }
1233            DEL => VTAction::None,
1234            ESC => VTAction::None,
1235            _ => {
1236                self.st = DcsIgnore;
1237                VTAction::None
1238            }
1239        }
1240    }
1241
1242    fn on_dcs_pass(&mut self, b: u8) -> VTAction {
1243        use State::*;
1244        match b {
1245            CAN | SUB => {
1246                self.st = Ground;
1247                VTAction::Cancel(VTEmit::Dcs)
1248            }
1249            DEL => VTAction::None,
1250            ESC => {
1251                self.st = DcsEsc;
1252                VTAction::Hold(VTEmit::Dcs)
1253            }
1254            _ => VTAction::Buffer(VTEmit::Dcs),
1255        }
1256    }
1257
1258    fn on_dcs_esc(&mut self, b: u8) -> VTAction {
1259        use State::*;
1260        match b {
1261            ST_FINAL => {
1262                self.st = Ground;
1263                VTAction::End(VTEnd::Dcs)
1264            }
1265            DEL => VTAction::None,
1266            ESC => {
1267                // If we get ESC ESC, we need to yield the previous ESC as well.
1268                VTAction::Hold(VTEmit::Dcs)
1269            }
1270            _ => {
1271                // If we get ESC !ST, we need to yield the previous ESC as well.
1272                self.st = DcsPassthrough;
1273                VTAction::Buffer(VTEmit::Dcs)
1274            }
1275        }
1276    }
1277
1278    // ---- OSC
1279    fn on_osc_string(&mut self, b: u8) -> VTAction {
1280        use State::*;
1281        match b {
1282            CAN | SUB => {
1283                self.st = Ground;
1284                VTAction::Cancel(VTEmit::Osc)
1285            }
1286            DEL => VTAction::None,
1287            BEL => {
1288                self.st = Ground;
1289                VTAction::End(VTEnd::Osc { used_bel: true })
1290            }
1291            ESC => {
1292                self.st = OscEsc;
1293                VTAction::Hold(VTEmit::Osc)
1294            }
1295            p if is_printable(p) => VTAction::Buffer(VTEmit::Osc),
1296            _ => VTAction::None, // ignore other C0
1297        }
1298    }
1299
1300    fn on_osc_esc(&mut self, b: u8) -> VTAction {
1301        use State::*;
1302        match b {
1303            ST_FINAL => {
1304                self.st = Ground;
1305                VTAction::End(VTEnd::Osc { used_bel: false })
1306            } // ST
1307            ESC => VTAction::Hold(VTEmit::Osc),
1308            DEL => VTAction::None,
1309            _ => {
1310                self.st = OscString;
1311                VTAction::Buffer(VTEmit::Osc)
1312            }
1313        }
1314    }
1315
1316    // ---- SOS/PM/APC (ignored payload)
1317    fn on_spa_string(&mut self, b: u8) -> VTAction {
1318        use State::*;
1319        match b {
1320            CAN | SUB => {
1321                self.st = Ground;
1322                VTAction::None
1323            }
1324            DEL => VTAction::None,
1325            ESC => {
1326                self.st = SpaEsc;
1327                VTAction::None
1328            }
1329            _ => VTAction::None,
1330        }
1331    }
1332
1333    fn on_spa_esc(&mut self, b: u8) -> VTAction {
1334        use State::*;
1335        match b {
1336            ST_FINAL => {
1337                self.st = Ground;
1338                VTAction::None
1339            }
1340            DEL => VTAction::None,
1341            ESC => {
1342                /* remain */
1343                VTAction::None
1344            }
1345            _ => {
1346                self.st = State::SosPmApcString;
1347                VTAction::None
1348            }
1349        }
1350    }
1351}
1352
1353#[cfg(test)]
1354mod tests {
1355    use crate::event::VTOwnedEvent;
1356
1357    use super::*;
1358    use pretty_assertions::assert_eq;
1359
1360    #[test]
1361    fn test_edge_cases() {
1362        // Test empty input
1363        let mut result = String::new();
1364        VTPushParser::decode_buffer(&[], |e| result.push_str(&format!("{e:?}\n")));
1365        assert_eq!(result.trim(), "");
1366
1367        // Test single ESC
1368        let mut result = String::new();
1369        VTPushParser::decode_buffer(b"\x1b", |e| result.push_str(&format!("{e:?}\n")));
1370        assert_eq!(result.trim(), "");
1371
1372        // Test incomplete CSI
1373        let mut result = String::new();
1374        VTPushParser::decode_buffer(b"\x1b[", |e| result.push_str(&format!("{e:?}\n")));
1375        assert_eq!(result.trim(), "");
1376
1377        // Test incomplete DCS
1378        let mut result = String::new();
1379        VTPushParser::decode_buffer(b"\x1bP", |e| result.push_str(&format!("{e:?}\n")));
1380        assert_eq!(result.trim(), "");
1381
1382        // Test incomplete OSC
1383        let mut result = String::new();
1384        VTPushParser::decode_buffer(b"\x1b]", |e| result.push_str(&format!("{e:?}\n")));
1385        assert_eq!(result.trim(), "OscStart");
1386    }
1387
1388    #[test]
1389    fn test_streaming_behavior() {
1390        // Test streaming DCS data
1391        let mut parser = VTPushParser::new(); // Small flush size
1392        let mut result = String::new();
1393        let mut callback = |vt_input: VTEvent<'_>| {
1394            result.push_str(&format!("{vt_input:?}\n"));
1395        };
1396
1397        // Feed DCS data in chunks
1398        parser.feed_with(b"\x1bP1;2;3 |", &mut callback);
1399        parser.feed_with(b"data", &mut callback);
1400        parser.feed_with(b" more", &mut callback);
1401        parser.feed_with(b"\x1b\\", &mut callback);
1402
1403        assert_eq!(
1404            result.trim(),
1405            "DcsStart('1', '2', '3', ' ', |)\nDcsData('data')\nDcsData(' more')\nDcsEnd('')"
1406        );
1407    }
1408
1409    #[test]
1410    fn test_dcs_payload_passthrough() {
1411        // Test cases for DCS payload passthrough behavior
1412        // Notes: body must be passed through verbatim.
1413        // - ESC '\' (ST) ends the string.
1414        // - ESC ESC stays as two bytes in the body.
1415        // - ESC X (X!='\') is data: both ESC and the following byte are payload.
1416        // - BEL (0x07) is data in DCS (not a terminator).
1417
1418        let dcs_cases: &[(&[u8], &str)] = &[
1419            // 1) Minimal: embedded CSI SGR truecolor (colon params)
1420            (b"\x1bPq\x1b[38:2:12:34:56m\x1b\\", "<ESC>[38:2:12:34:56m"),
1421            // 2) Mixed payload: CSI + literal text
1422            (b"\x1bPq\x1b[48:2:0:0:0m;xyz\x1b\\", "<ESC>[48:2:0:0:0m;xyz"),
1423            // 3) DECRQSS-style reply payload (DCS 1$r ... ST) containing colon-CSI
1424            (
1425                b"\x1bP1$r\x1b[38:2:10:20:30;58:2::200:100:0m\x1b\\",
1426                "<ESC>[38:2:10:20:30;58:2::200:100:0m",
1427            ),
1428            // 4) ESC ESC and ESC X inside body (all data)
1429            (
1430                b"\x1bPqABC\x1b\x1bDEF\x1bXG\x1b\\",
1431                "ABC<ESC><ESC>DEF<ESC>XG",
1432            ),
1433            // 5) BEL in body (data, not a terminator)
1434            (b"\x1bPqDATA\x07MORE\x1b\\", "DATA<BEL>MORE"),
1435            // 6) iTerm2-style header (!|) with embedded CSI 256-color
1436            (b"\x1bP!|\x1b[38:5:208m\x1b\\", "<ESC>[38:5:208m"),
1437            // 7) Private prefix + final '|' (>|) with plain text payload
1438            (b"\x1bP>|Hello world\x1b\\", "Hello world"),
1439            // 8) Multiple embedded CSIs back-to-back
1440            (
1441                b"\x1bPq\x1b[38:2:1:2:3m\x1b[48:5:17m\x1b\\",
1442                "<ESC>[38:2:1:2:3m<ESC>[48:5:17m",
1443            ),
1444            // 9) Long colon param with leading zeros
1445            (
1446                b"\x1bPq\x1b[58:2::000:007:042m\x1b\\",
1447                "<ESC>[58:2::000:007:042m",
1448            ),
1449        ];
1450
1451        for (input, expected_body) in dcs_cases {
1452            let events = collect_owned_events(input);
1453
1454            // Find DcsData events and concatenate their payloads
1455            let mut actual_body = Vec::new();
1456            for event in &events {
1457                match event {
1458                    VTOwnedEvent::DcsData(data) | VTOwnedEvent::DcsEnd(data) => {
1459                        actual_body.extend(data);
1460                    }
1461                    _ => {}
1462                }
1463            }
1464
1465            let actual_body = String::from_utf8(actual_body).unwrap();
1466            let actual_body = actual_body
1467                .replace("\x1b", "<ESC>")
1468                .replace("\x07", "<BEL>");
1469
1470            assert_eq!(
1471                actual_body, *expected_body,
1472                "DCS payload mismatch for input {:?}. Full events: {:#?}",
1473                input, events
1474            );
1475        }
1476    }
1477
1478    fn collect_events(input: &[u8]) -> Vec<String> {
1479        let mut out = Vec::new();
1480        let mut p = VTPushParser::new();
1481        p.feed_with(input, &mut |ev| out.push(format!("{ev:?}")));
1482        out
1483    }
1484
1485    fn collect_owned_events(input: &[u8]) -> Vec<VTOwnedEvent> {
1486        let mut out = Vec::new();
1487        let mut p = VTPushParser::new();
1488        p.feed_with(input, &mut |ev| out.push(ev.to_owned()));
1489        out
1490    }
1491
1492    #[test]
1493    fn dcs_esc_esc_del() {
1494        // ESC P 1:2 q ... ST   -> colon inside header params (invalid)
1495        let ev = collect_events(b"\x1bP1;2;3|\x1b\x1b\x7fdata\x1b\\");
1496        // Expect: no DcsStart; the whole thing is ignored until ST
1497        eprintln!("{ev:?}");
1498    }
1499
1500    #[test]
1501    fn dcs_header_with_colon_is_ignored_case1() {
1502        // ESC P 1:2 q ... ST   -> colon inside header params (invalid)
1503        let ev = collect_events(b"\x1bP1:2qHELLO\x1b\\");
1504        // Expect: no DcsStart; the whole thing is ignored until ST
1505        assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
1506    }
1507
1508    #[test]
1509    fn dcs_header_with_colon_is_ignored_case2() {
1510        // Colon immediately after ESC P, before any digit
1511        let ev = collect_events(b"\x1bP:1qDATA\x1b\\");
1512        assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
1513    }
1514
1515    #[test]
1516    fn dcs_header_with_colon_is_ignored_case3() {
1517        // Mixed: digits;colon;digits then intermediates/final
1518        let ev = collect_events(b"\x1bP12:34!qPAYLOAD\x1b\\");
1519        assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
1520    }
1521
1522    #[test]
1523    fn osc_aborted_by_can_mid_body() {
1524        // ESC ] 0;Title <CAN> more <BEL>
1525        let mut s = Vec::new();
1526        s.extend_from_slice(b"\x1b]0;Title");
1527        s.push(CAN);
1528        s.extend_from_slice(b"more\x07");
1529
1530        let ev = collect_debug(&s);
1531
1532        // EXPECT_SPEC_STRICT: no events at all (no Start/Data/End)
1533        // assert!(ev.is_empty(), "{ev:#?}");
1534
1535        // EXPECT_PUSH_PARSER: Start emitted, but NO Data, NO End
1536        assert!(ev.iter().any(|e| e.starts_with("OscStart")), "{ev:#?}");
1537        assert!(!ev.iter().any(|e| e.starts_with("OscData")), "{ev:#?}");
1538        assert!(!ev.iter().any(|e| e.starts_with("OscEnd")), "{ev:#?}");
1539    }
1540
1541    #[test]
1542    fn osc_aborted_by_sub_before_terminator() {
1543        let mut s = Vec::new();
1544        s.extend_from_slice(b"\x1b]52;c;YWJjZA==");
1545        s.push(SUB); // abort
1546        s.extend_from_slice(b"\x1b\\"); // would have been ST, but must be ignored after abort
1547
1548        let ev = collect_debug(&s);
1549        // SPEC-STRICT:
1550        // assert!(ev.is_empty(), "{ev:#?}");
1551        // PUSH-PARSER:
1552        assert!(ev.iter().any(|e| e.starts_with("OscStart")), "{ev:#?}");
1553        assert!(!ev.iter().any(|e| e.starts_with("OscData")), "{ev:#?}");
1554        assert!(!ev.iter().any(|e| e.starts_with("OscEnd")), "{ev:#?}");
1555    }
1556
1557    /// Collect raw VTEvent debug lines for quick assertions.
1558    fn collect_debug(input: &[u8]) -> Vec<String> {
1559        let mut out = Vec::new();
1560        let mut p = VTPushParser::new();
1561        p.feed_with(input, &mut |ev| out.push(format!("{ev:?}")));
1562        out
1563    }
1564
1565    #[test]
1566    fn dcs_aborted_by_can_before_body() {
1567        // ESC P q <CAN> ... ST
1568        let mut s = Vec::new();
1569        s.extend_from_slice(b"\x1bPq"); // header (valid: final 'q')
1570        s.push(CAN);
1571        s.extend_from_slice(b"IGNORED\x1b\\"); // should be raw
1572
1573        let ev = collect_debug(&s);
1574
1575        assert_eq!(ev.len(), 4, "{ev:#?}");
1576        assert_eq!(ev[0], "DcsStart('', q)");
1577        assert_eq!(ev[1], "DcsCancel");
1578        assert_eq!(ev[2], "Raw('IGNORED')");
1579        assert_eq!(ev[3], "Esc('', \\)");
1580    }
1581
1582    #[test]
1583    fn dcs_aborted_by_can_mid_body() {
1584        // ESC P q ABC <CAN> more ST
1585        let mut s = Vec::new();
1586        s.extend_from_slice(b"\x1bPqABC");
1587        s.push(CAN);
1588        s.extend_from_slice(b"MORE\x1b\\"); // ignored after abort
1589
1590        let ev = collect_debug(&s);
1591
1592        assert_eq!(ev.len(), 4, "{ev:#?}");
1593        assert_eq!(ev[0], "DcsStart('', q)");
1594        assert_eq!(ev[1], "DcsCancel");
1595        assert_eq!(ev[2], "Raw('MORE')");
1596        assert_eq!(ev[3], "Esc('', \\)");
1597    }
1598
1599    /* ========= SOS / PM / APC (ESC X, ESC ^, ESC _) ========= */
1600
1601    #[test]
1602    fn spa_aborted_by_can_is_ignored() {
1603        // ESC _ data <CAN> more ST
1604        let mut s = Vec::new();
1605        s.extend_from_slice(b"\x1b_hello");
1606        s.push(CAN);
1607        s.extend_from_slice(b"world\x1b\\");
1608
1609        let ev = collect_debug(&s);
1610        assert_eq!(ev.len(), 2, "{ev:#?}");
1611        assert_eq!(ev[0], "Raw('world')");
1612        assert_eq!(ev[1], "Esc('', \\)");
1613    }
1614
1615    #[test]
1616    fn spa_sub_aborts_too() {
1617        let mut s = Vec::new();
1618        s.extend_from_slice(b"\x1bXhello");
1619        s.push(SUB);
1620        s.extend_from_slice(b"world\x1b\\");
1621        let ev = collect_debug(&s);
1622        assert_eq!(ev.len(), 2, "{ev:#?}");
1623        assert_eq!(ev[0], "Raw('world')");
1624        assert_eq!(ev[1], "Esc('', \\)");
1625    }
1626
1627    /* ========= Sanity: CAN outside strings is a C0 EXECUTE ========= */
1628
1629    #[test]
1630    fn can_in_ground_is_c0() {
1631        let mut s = Vec::new();
1632        s.extend_from_slice(b"abc");
1633        s.push(CAN);
1634        s.extend_from_slice(b"def");
1635        let ev = collect_debug(&s);
1636        // Expect Raw("abc"), C0(0x18), Raw("def")
1637        assert_eq!(ev.len(), 3, "{ev:#?}");
1638        assert_eq!(ev[0], "Raw('abc')");
1639        assert_eq!(ev[1], "C0(18)");
1640        assert_eq!(ev[2], "Raw('def')");
1641    }
1642
1643    /// Brute force sweep of all three-byte sequences to ensure we can recover
1644    /// from all invalid escape sequences (unless CSI/OSC/DCS/SOS/PM/APC).
1645    #[test]
1646    fn three_byte_sequences_capturable() {
1647        let mut bytes = vec![];
1648        for i in 0..=0xFFFFFF_u32 {
1649            bytes.clear();
1650            let test_bytes = i.to_le_bytes();
1651            let test_bytes = &test_bytes[..3];
1652            if test_bytes.iter().any(|b| b == &0) {
1653                continue;
1654            }
1655            if test_bytes[0] == 0x1b && matches!(test_bytes[1], CSI | DCS | OSC | APC | PM | SOS) {
1656                continue;
1657            }
1658            if test_bytes[1] == 0x1b && matches!(test_bytes[2], CSI | DCS | OSC | APC | PM | SOS) {
1659                continue;
1660            }
1661
1662            let mut parser = VTPushParser::<VT_PARSER_INTEREST_ALL>::new_with();
1663            parser.feed_with(test_bytes, &mut |event| {
1664                let mut chunk = [0_u8; 3];
1665                let b = event.encode(&mut chunk).unwrap_or_else(|_| {
1666                    panic!("Failed to encode event {test_bytes:X?} -> {event:?}")
1667                });
1668                bytes.extend_from_slice(&chunk[..b]);
1669            });
1670            if let Some(event) = parser.idle() {
1671                let mut chunk = [0_u8; 3];
1672                let b = event.encode(&mut chunk).unwrap_or_else(|_| {
1673                    panic!("Failed to encode event {test_bytes:X?} -> {event:?}")
1674                });
1675                bytes.extend_from_slice(&chunk[..b]);
1676            }
1677
1678            if bytes.len() != 3 || bytes != test_bytes {
1679                eprintln!("Failed to parse:");
1680                parser.feed_with(test_bytes, &mut |event| {
1681                    eprintln!("{event:?}");
1682                });
1683                if let Some(event) = parser.idle() {
1684                    eprintln!("{event:?}");
1685                }
1686                assert_eq!(bytes, test_bytes, "{test_bytes:X?} -> {bytes:X?}");
1687            }
1688        }
1689    }
1690}