vt_push_parser/
lib.rs

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