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