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