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