vt_push_parser/
lib.rs

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