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