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