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