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