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