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