Skip to main content

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