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