vt_push_parser/
lib.rs

1use std::ops::Range;
2
3use smallvec::SmallVec;
4
5const ESC: u8 = 0x1B;
6const BEL: u8 = 0x07;
7const DEL: u8 = 0x7F;
8const CAN: u8 = 0x18;
9const SUB: u8 = 0x1A;
10const CSI: u8 = b'[';
11const OSC: u8 = b']';
12const SS3: u8 = b'O';
13const DCS: u8 = b'P';
14const ST_FINAL: u8 = b'\\';
15
16#[derive(Default, Clone, Copy, PartialEq, Eq)]
17pub struct VTIntermediate {
18    data: [u8; 2],
19}
20
21impl VTIntermediate {
22    pub const fn empty() -> Self {
23        Self { data: [0, 0] }
24    }
25
26    pub const fn one(c: u8) -> Self {
27        assert!(c >= 0x20 && c <= 0x2F);
28        Self { data: [c, 0] }
29    }
30
31    pub const fn two(c1: u8, c2: u8) -> Self {
32        assert!(c1 >= 0x20 && c1 <= 0x2F);
33        assert!(c2 >= 0x20 && c2 <= 0x2F);
34        Self { data: [c1, c2] }
35    }
36
37    pub fn has(&self, c: u8) -> bool {
38        self.data[0] == c || self.data[1] == c
39    }
40
41    pub fn clear(&mut self) {
42        self.data[0] = 0;
43        self.data[1] = 0;
44    }
45
46    pub fn is_empty(&self) -> bool {
47        self.data[0] == 0 && self.data[1] == 0
48    }
49
50    pub fn len(&self) -> usize {
51        self.data.iter().filter(|&&c| c != 0).count()
52    }
53
54    #[must_use]
55    pub fn push(&mut self, c: u8) -> bool {
56        if c < 0x20 || c > 0x2F {
57            return false;
58        }
59
60        if self.data[0] == 0 {
61            self.data[0] = c;
62            true
63        } else if self.data[1] == 0 {
64            self.data[1] = c;
65            true
66        } else {
67            false
68        }
69    }
70
71    const fn const_eq(&self, other: &Self) -> bool {
72        self.data[0] == other.data[0] && self.data[1] == other.data[1]
73    }
74}
75
76impl std::fmt::Debug for VTIntermediate {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        // Inefficient
79        write!(f, "'")?;
80        for c in self.data.iter() {
81            if *c == 0 {
82                break;
83            }
84            write!(f, "{}", *c as char)?;
85        }
86        write!(f, "'")?;
87        Ok(())
88    }
89}
90
91/// A signature for an escape sequence.
92pub struct VTEscapeSignature {
93    pub prefix: u8,
94    pub private: Option<u8>,
95    pub intermediates: VTIntermediate,
96    pub final_byte: u8,
97    pub param_count: Range<u8>,
98}
99
100impl VTEscapeSignature {
101    pub const fn csi(
102        private: Option<u8>,
103        param_count: Range<u8>,
104        intermediates: VTIntermediate,
105        final_byte: u8,
106    ) -> Self {
107        Self {
108            prefix: CSI,
109            private,
110            intermediates,
111            final_byte,
112            param_count,
113        }
114    }
115
116    pub const fn ss3(intermediates: VTIntermediate, final_byte: u8) -> Self {
117        Self {
118            prefix: SS3,
119            private: None,
120            intermediates,
121            final_byte,
122            param_count: u8::MIN..u8::MAX,
123        }
124    }
125
126    pub const fn dcs(
127        priv_prefix: Option<u8>,
128        param_count: Range<u8>,
129        intermediates: VTIntermediate,
130        final_byte: u8,
131    ) -> Self {
132        Self {
133            prefix: DCS,
134            private: priv_prefix,
135            intermediates,
136            final_byte,
137            param_count,
138        }
139    }
140
141    pub const fn osc(intermediates: VTIntermediate, final_byte: u8) -> Self {
142        Self {
143            prefix: OSC,
144            private: None,
145            intermediates,
146            final_byte,
147            param_count: u8::MIN..u8::MAX,
148        }
149    }
150
151    pub fn matches(&self, entry: &VTEvent) -> bool {
152        // TODO: const
153        match entry {
154            VTEvent::Esc {
155                intermediates,
156                final_byte,
157            } => self.final_byte == *final_byte && self.intermediates.const_eq(intermediates),
158            VTEvent::Csi {
159                private,
160                params,
161                intermediates,
162                final_byte,
163            } => {
164                self.prefix == CSI
165                    && self.final_byte == *final_byte
166                    && self.intermediates.const_eq(intermediates)
167                    && self.const_private_eq(private)
168                    && self.const_contains(params.len())
169            }
170            VTEvent::Ss3 {
171                intermediates,
172                final_byte,
173            } => {
174                self.prefix == SS3
175                    && self.final_byte == *final_byte
176                    && self.intermediates.const_eq(intermediates)
177            }
178            VTEvent::DcsStart {
179                priv_prefix,
180                params,
181                intermediates,
182                final_byte,
183            } => {
184                self.prefix == DCS
185                    && self.final_byte == *final_byte
186                    && self.intermediates.const_eq(intermediates)
187                    && self.private == *priv_prefix
188                    && self.const_contains(params.len())
189            }
190            _ => false,
191        }
192    }
193
194    const fn const_private_eq(&self, other: &Option<u8>) -> bool {
195        match (self.private, other) {
196            (Some(a), Some(b)) => a == *b,
197            (None, None) => true,
198            _ => false,
199        }
200    }
201
202    fn const_contains(&self, len: usize) -> bool {
203        // TODO: const
204        self.param_count.contains(&(len as u8))
205    }
206}
207
208pub enum VTEvent<'a> {
209    // Plain printable text from GROUND (coalesced)
210    Raw(&'a [u8]),
211
212    // C0 control (EXECUTE)
213    C0(u8),
214
215    // ESC final (with intermediates)
216    Esc {
217        intermediates: VTIntermediate,
218        final_byte: u8,
219    },
220
221    // CSI short escape
222    Csi {
223        private: Option<u8>,
224        params: smallvec::SmallVec<[&'a [u8]; 4]>,
225        intermediates: VTIntermediate,
226        final_byte: u8,
227    },
228
229    // SS3 (ESC O …)
230    Ss3 {
231        intermediates: VTIntermediate,
232        final_byte: u8,
233    },
234
235    // DCS stream
236    DcsStart {
237        priv_prefix: Option<u8>,
238        params: smallvec::SmallVec<[&'a [u8]; 4]>,
239        intermediates: VTIntermediate,
240        final_byte: u8,
241    },
242    DcsData(&'a [u8]),
243    DcsEnd,
244    DcsCancel,
245
246    // OSC stream
247    OscStart,
248    OscData(&'a [u8]),
249    OscEnd {
250        used_bel: bool,
251    },
252    OscCancel,
253}
254
255impl<'a> std::fmt::Debug for VTEvent<'a> {
256    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257        match self {
258            VTEvent::Raw(s) => {
259                write!(f, "Raw('")?;
260                for chunk in s.utf8_chunks() {
261                    write!(f, "{}", chunk.valid())?;
262                    if !chunk.invalid().is_empty() {
263                        write!(f, "<{}>", hex::encode(chunk.invalid()))?;
264                    }
265                }
266                write!(f, "')")?;
267                Ok(())
268            }
269            VTEvent::C0(b) => write!(f, "C0({:02x})", b),
270            VTEvent::Esc {
271                intermediates,
272                final_byte,
273            } => {
274                write!(f, "Esc({:?}", intermediates)?;
275                write!(f, ", {})", *final_byte as char)?;
276                Ok(())
277            }
278            VTEvent::Csi {
279                private,
280                params,
281                intermediates,
282                final_byte,
283            } => {
284                write!(f, "Csi(")?;
285                if let Some(p) = private {
286                    write!(f, "{:?}", *p as char)?;
287                }
288                for param in params {
289                    write!(f, ", '")?;
290                    for chunk in param.utf8_chunks() {
291                        write!(f, "{}", chunk.valid())?;
292                        if !chunk.invalid().is_empty() {
293                            write!(f, "<{}>", hex::encode(chunk.invalid()))?;
294                        }
295                    }
296                    write!(f, "'")?;
297                }
298                write!(f, ", {:?}", intermediates)?;
299                write!(f, ", {:?})", *final_byte as char)?;
300                Ok(())
301            }
302            VTEvent::Ss3 {
303                intermediates,
304                final_byte,
305            } => {
306                write!(f, "Ss3(")?;
307                write!(f, "{:?}", intermediates)?;
308                write!(f, ", {})", *final_byte as char)?;
309                Ok(())
310            }
311            VTEvent::DcsStart {
312                priv_prefix,
313                params,
314                intermediates,
315                final_byte,
316            } => {
317                write!(f, "DcsStart(")?;
318                if let Some(p) = priv_prefix {
319                    write!(f, "{:?}", *p as char)?;
320                }
321                for param in params {
322                    write!(f, ", '")?;
323                    for chunk in param.utf8_chunks() {
324                        write!(f, "{}", chunk.valid())?;
325                        if !chunk.invalid().is_empty() {
326                            write!(f, "<{}>", hex::encode(chunk.invalid()))?;
327                        }
328                    }
329                    write!(f, "'")?;
330                }
331                write!(f, ", {:?}", intermediates)?;
332                write!(f, ", {})", *final_byte as char)?;
333                Ok(())
334            }
335            VTEvent::DcsData(s) => {
336                write!(f, "DcsData('")?;
337                for chunk in s.utf8_chunks() {
338                    write!(f, "{}", chunk.valid())?;
339                    if !chunk.invalid().is_empty() {
340                        write!(f, "<{}>", hex::encode(chunk.invalid()))?;
341                    }
342                }
343                write!(f, "')")?;
344                Ok(())
345            }
346            VTEvent::DcsEnd => write!(f, "DcsEnd"),
347            VTEvent::DcsCancel => write!(f, "DcsCancel"),
348            VTEvent::OscStart => write!(f, "OscStart"),
349            VTEvent::OscData(s) => {
350                write!(f, "OscData('")?;
351                for chunk in s.utf8_chunks() {
352                    write!(f, "{}", chunk.valid())?;
353                    if !chunk.invalid().is_empty() {
354                        write!(f, "<{}>", hex::encode(chunk.invalid()))?;
355                    }
356                }
357                write!(f, "')")?;
358                Ok(())
359            }
360            VTEvent::OscEnd { .. } => {
361                write!(f, "OscEnd")?;
362                Ok(())
363            }
364            VTEvent::OscCancel => write!(f, "OscCancel"),
365        }
366    }
367}
368
369#[inline]
370fn is_c0(b: u8) -> bool {
371    b <= 0x1F
372}
373#[inline]
374fn is_printable(b: u8) -> bool {
375    (0x20..=0x7E).contains(&b)
376}
377#[inline]
378fn is_intermediate(b: u8) -> bool {
379    (0x20..=0x2F).contains(&b)
380}
381#[inline]
382fn is_final(b: u8) -> bool {
383    (0x30..=0x7E).contains(&b)
384}
385#[inline]
386fn is_digit(b: u8) -> bool {
387    (b'0'..=b'9').contains(&b)
388}
389#[inline]
390fn is_priv(b: u8) -> bool {
391    matches!(b, b'<' | b'=' | b'>' | b'?')
392}
393
394#[derive(Debug, Copy, Clone, PartialEq, Eq)]
395enum State {
396    Ground,
397    Escape,
398    EscInt,
399    CsiEntry,
400    CsiParam,
401    CsiInt,
402    CsiIgnore,
403    DcsEntry,
404    DcsParam,
405    DcsInt,
406    DcsIgnore,
407    DcsIgnoreEsc,
408    DcsPassthrough,
409    DcsEsc,
410    OscString,
411    OscEsc,
412    SosPmApcString,
413    SpaEsc,
414}
415
416pub struct VTPushParser {
417    st: State,
418
419    // GROUND raw coalescing
420    raw_buf: Vec<u8>,
421
422    // Header collectors for short escapes (we borrow from these in callbacks)
423    ints: VTIntermediate,
424    params: Vec<Vec<u8>>,
425    cur_param: Vec<u8>,
426    priv_prefix: Option<u8>,
427
428    // Streaming buffer (DCS/OSC bodies)
429    stream_buf: Vec<u8>,
430    used_bel: bool,
431
432    // Limits
433    stream_flush: usize,
434}
435
436impl VTPushParser {
437    pub fn new() -> Self {
438        Self {
439            st: State::Ground,
440            raw_buf: Vec::with_capacity(256),
441            ints: VTIntermediate::default(),
442            params: Vec::with_capacity(8),
443            cur_param: Vec::with_capacity(8),
444            priv_prefix: None,
445            stream_buf: Vec::with_capacity(8192),
446            used_bel: false,
447            stream_flush: 8192,
448        }
449    }
450
451    pub fn with_stream_flush_max(mut self, stream_flush: usize) -> Self {
452        self.stream_flush = stream_flush.max(1);
453        self
454    }
455
456    /* =====================
457    Callback-driven API
458    ===================== */
459
460    pub fn feed_with<F: FnMut(VTEvent)>(&mut self, input: &[u8], mut cb: F) {
461        for &b in input {
462            self.push_with(b, &mut cb);
463        }
464        self.flush_raw_if_any(&mut cb);
465    }
466
467    pub fn push_with<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
468        use State::*;
469        match self.st {
470            Ground => self.on_ground(b, cb),
471            Escape => self.on_escape(b, cb),
472            EscInt => self.on_esc_int(b, cb),
473
474            CsiEntry => self.on_csi_entry(b, cb),
475            CsiParam => self.on_csi_param(b, cb),
476            CsiInt => self.on_csi_int(b, cb),
477            CsiIgnore => self.on_csi_ignore(b, cb),
478
479            DcsEntry => self.on_dcs_entry(b, cb),
480            DcsParam => self.on_dcs_param(b, cb),
481            DcsInt => self.on_dcs_int(b, cb),
482            DcsIgnore => self.on_dcs_ignore(b, cb),
483            DcsIgnoreEsc => self.on_dcs_ignore_esc(b, cb),
484            DcsPassthrough => self.on_dcs_pass(b, cb),
485            DcsEsc => self.on_dcs_esc(b, cb),
486
487            OscString => self.on_osc_string(b, cb),
488            OscEsc => self.on_osc_esc(b, cb),
489
490            SosPmApcString => self.on_spa_string(b, cb),
491            SpaEsc => self.on_spa_esc(b, cb),
492        }
493    }
494
495    pub fn finish<F: FnMut(VTEvent)>(&mut self, cb: &mut F) {
496        // Abort unterminated strings and flush raw.
497        self.reset_collectors();
498        self.st = State::Ground;
499        self.flush_raw_if_any(cb);
500    }
501
502    /* =====================
503    Emit helpers (borrowed)
504    ===================== */
505
506    fn flush_raw_if_any<F: FnMut(VTEvent)>(&mut self, cb: &mut F) {
507        if !self.raw_buf.is_empty() {
508            let slice = &self.raw_buf[..];
509            cb(VTEvent::Raw(slice));
510            self.raw_buf.clear(); // borrow ended with callback return
511        }
512    }
513
514    fn clear_hdr_collectors(&mut self) {
515        self.ints.clear();
516        self.params.clear();
517        self.cur_param.clear();
518        self.priv_prefix = None;
519    }
520
521    fn reset_collectors(&mut self) {
522        self.clear_hdr_collectors();
523        self.stream_buf.clear();
524        self.used_bel = false;
525    }
526
527    fn next_param(&mut self) {
528        self.params.push(std::mem::take(&mut self.cur_param));
529    }
530
531    fn finish_params_if_any(&mut self) {
532        if !self.cur_param.is_empty() || !self.params.is_empty() {
533            self.next_param();
534        }
535    }
536
537    fn emit_esc<F: FnMut(VTEvent)>(&mut self, final_byte: u8, cb: &mut F) {
538        self.flush_raw_if_any(cb);
539        cb(VTEvent::Esc {
540            intermediates: self.ints,
541            final_byte,
542        });
543        self.clear_hdr_collectors();
544    }
545
546    fn emit_csi<F: FnMut(VTEvent)>(&mut self, final_byte: u8, cb: &mut F) {
547        self.flush_raw_if_any(cb);
548        self.finish_params_if_any();
549
550        // Build borrowed views into self.params
551        let mut borrowed: SmallVec<[&[u8]; 4]> = SmallVec::new();
552        borrowed.extend(self.params.iter().map(|v| v.as_slice()));
553
554        let privp = self.priv_prefix.take();
555        cb(VTEvent::Csi {
556            private: privp,
557            params: borrowed,
558            intermediates: self.ints,
559            final_byte,
560        });
561        self.clear_hdr_collectors();
562    }
563
564    fn dcs_start<F: FnMut(VTEvent)>(&mut self, final_byte: u8, cb: &mut F) {
565        self.flush_raw_if_any(cb);
566        self.finish_params_if_any();
567
568        let mut borrowed: SmallVec<[&[u8]; 4]> = SmallVec::new();
569        borrowed.extend(self.params.iter().map(|v| v.as_slice()));
570
571        let privp = self.priv_prefix.take();
572        cb(VTEvent::DcsStart {
573            priv_prefix: privp,
574            params: borrowed,
575            intermediates: self.ints,
576            final_byte,
577        });
578        self.stream_buf.clear();
579        // keep header buffers intact until after callback; already done
580        self.clear_hdr_collectors();
581    }
582
583    fn dcs_put<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
584        self.stream_buf.push(b);
585        if self.stream_buf.len() >= self.stream_flush {
586            let slice = &self.stream_buf[..];
587            cb(VTEvent::DcsData(slice));
588            self.stream_buf.clear();
589        }
590    }
591
592    fn dcs_end<F: FnMut(VTEvent)>(&mut self, cb: &mut F) {
593        if !self.stream_buf.is_empty() {
594            let slice = &self.stream_buf[..];
595            cb(VTEvent::DcsData(slice));
596            self.stream_buf.clear();
597        }
598        cb(VTEvent::DcsEnd);
599        self.reset_collectors();
600    }
601
602    fn osc_start<F: FnMut(VTEvent)>(&mut self, cb: &mut F) {
603        self.flush_raw_if_any(cb);
604        self.used_bel = false;
605        self.stream_buf.clear();
606        cb(VTEvent::OscStart);
607    }
608
609    fn osc_put<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
610        self.stream_buf.push(b);
611        if self.stream_buf.len() >= self.stream_flush {
612            let slice = &self.stream_buf[..];
613            cb(VTEvent::OscData(slice));
614            self.stream_buf.clear();
615        }
616    }
617
618    fn osc_end<F: FnMut(VTEvent)>(&mut self, cb: &mut F) {
619        if !self.stream_buf.is_empty() {
620            let slice = &self.stream_buf[..];
621            cb(VTEvent::OscData(slice));
622            self.stream_buf.clear();
623        }
624        let used_bel = self.used_bel;
625        cb(VTEvent::OscEnd { used_bel });
626        self.reset_collectors();
627    }
628
629    /* =====================
630    State handlers
631    ===================== */
632
633    fn on_ground<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
634        match b {
635            ESC => {
636                self.clear_hdr_collectors();
637                self.flush_raw_if_any(cb);
638                self.st = State::Escape;
639            }
640            DEL => {}
641            c if is_c0(c) => {
642                self.flush_raw_if_any(cb);
643                cb(VTEvent::C0(c));
644            }
645            p if is_printable(p) => {
646                self.raw_buf.push(p);
647            }
648            _ => {
649                self.raw_buf.push(b);
650            } // safe fallback
651        }
652    }
653
654    fn on_escape<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
655        use State::*;
656        match b {
657            CAN | SUB => {
658                self.st = Ground;
659            }
660            DEL => {}
661            c if is_intermediate(c) => {
662                self.ints.push(c);
663                self.st = EscInt;
664            }
665            CSI => {
666                self.clear_hdr_collectors();
667                self.st = CsiEntry;
668            }
669            DCS => {
670                self.clear_hdr_collectors();
671                self.st = DcsEntry;
672            }
673            OSC => {
674                self.clear_hdr_collectors();
675                self.osc_start(cb);
676                self.st = OscString;
677            }
678            b'X' | b'^' | b'_' => {
679                self.clear_hdr_collectors();
680                self.st = State::SosPmApcString;
681            }
682            c if is_final(c) => {
683                self.emit_esc(c, cb);
684                self.st = Ground;
685            }
686            ESC => { /* ESC ESC allowed */ }
687            _ => {
688                self.st = Ground;
689            }
690        }
691    }
692    fn on_esc_int<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
693        use State::*;
694        match b {
695            CAN | SUB => {
696                self.st = Ground;
697            }
698            DEL => {}
699            c if is_intermediate(c) => {
700                self.ints.push(c);
701            }
702            c if is_final(c) => {
703                self.emit_esc(c, cb);
704                self.st = Ground;
705            }
706            _ => {
707                self.st = Ground;
708            }
709        }
710    }
711
712    // ---- CSI
713    fn on_csi_entry<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
714        use State::*;
715        match b {
716            CAN | SUB => {
717                self.st = Ground;
718            }
719            DEL => {}
720            ESC => {
721                self.st = Escape;
722            }
723            c if is_priv(c) => {
724                self.priv_prefix = Some(c);
725                self.st = CsiParam;
726            }
727            d if is_digit(d) => {
728                self.cur_param.push(d);
729                self.st = CsiParam;
730            }
731            b';' => {
732                self.next_param();
733                self.st = CsiParam;
734            }
735            b':' => {
736                self.cur_param.push(b':');
737                self.st = CsiParam;
738            }
739            c if is_intermediate(c) => {
740                self.ints.push(c);
741                self.st = CsiInt;
742            }
743            c if is_final(c) => {
744                self.emit_csi(c, cb);
745                self.st = Ground;
746            }
747            _ => {
748                self.st = CsiIgnore;
749            }
750        }
751    }
752    fn on_csi_param<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
753        use State::*;
754        match b {
755            CAN | SUB => {
756                self.st = Ground;
757            }
758            DEL => {}
759            ESC => {
760                self.st = Escape;
761            }
762            d if is_digit(d) => {
763                self.cur_param.push(d);
764            }
765            b';' => {
766                self.next_param();
767            }
768            b':' => {
769                self.cur_param.push(b':');
770            }
771            c if is_intermediate(c) => {
772                self.ints.push(c);
773                self.st = CsiInt;
774            }
775            c if is_final(c) => {
776                self.emit_csi(c, cb);
777                self.st = Ground;
778            }
779            _ => {
780                self.st = CsiIgnore;
781            }
782        }
783    }
784    fn on_csi_int<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
785        use State::*;
786        match b {
787            CAN | SUB => {
788                self.st = Ground;
789            }
790            DEL => {}
791            ESC => {
792                self.st = Escape;
793            }
794            c if is_intermediate(c) => {
795                self.ints.push(c);
796            }
797            c if is_final(c) => {
798                self.emit_csi(c, cb);
799                self.st = Ground;
800            }
801            _ => {
802                self.st = CsiIgnore;
803            }
804        }
805    }
806    fn on_csi_ignore<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
807        use State::*;
808        match b {
809            CAN | SUB => {
810                self.st = Ground;
811            }
812            DEL => {}
813            ESC => {
814                self.st = Escape;
815            }
816            c if is_final(c) => {
817                self.st = Ground;
818            }
819            _ => {}
820        }
821    }
822
823    // ---- DCS
824    fn on_dcs_entry<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
825        use State::*;
826        match b {
827            CAN | SUB => {
828                self.st = Ground;
829            }
830            DEL => {}
831            ESC => {
832                self.st = Escape;
833            }
834            c if is_priv(c) => {
835                self.priv_prefix = Some(c);
836                self.st = DcsParam;
837            }
838            d if is_digit(d) => {
839                self.cur_param.push(d);
840                self.st = DcsParam;
841            }
842            b';' => {
843                self.next_param();
844                self.st = DcsParam;
845            }
846            b':' => {
847                self.st = DcsIgnore;
848            }
849            c if is_intermediate(c) => {
850                self.ints.push(c);
851                self.st = DcsInt;
852            }
853            c if is_final(c) => {
854                self.dcs_start(c, cb);
855                self.st = DcsPassthrough;
856            }
857            _ => {
858                self.st = DcsIgnore;
859            }
860        }
861    }
862    fn on_dcs_param<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
863        use State::*;
864        match b {
865            CAN | SUB => {
866                self.st = Ground;
867            }
868            DEL => {}
869            ESC => {
870                self.st = Escape;
871            }
872            d if is_digit(d) => {
873                self.cur_param.push(d);
874            }
875            b';' => {
876                self.next_param();
877            }
878            b':' => {
879                self.st = DcsIgnore;
880            }
881            c if is_intermediate(c) => {
882                self.ints.push(c);
883                self.st = DcsInt;
884            }
885            c if is_final(c) => {
886                self.dcs_start(c, cb);
887                self.st = DcsPassthrough;
888            }
889            _ => {
890                self.st = DcsIgnore;
891            }
892        }
893    }
894    fn on_dcs_int<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
895        use State::*;
896        match b {
897            CAN | SUB => {
898                self.st = Ground;
899            }
900            DEL => {}
901            ESC => {
902                self.st = Escape;
903            }
904            c if is_intermediate(c) => {
905                self.ints.push(c);
906            }
907            c if is_final(c) => {
908                self.dcs_start(c, cb);
909                self.st = DcsPassthrough;
910            }
911            _ => {
912                self.st = DcsIgnore;
913            }
914        }
915    }
916    fn on_dcs_ignore<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
917        use State::*;
918        match b {
919            CAN | SUB => {
920                self.st = Ground;
921            }
922            DEL => {}
923            ESC => {
924                self.st = DcsIgnoreEsc;
925            }
926            _ => {}
927        }
928    }
929    fn on_dcs_ignore_esc<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
930        use State::*;
931        match b {
932            CAN | SUB => {
933                self.st = Ground;
934            }
935            ST_FINAL => {
936                self.st = Ground;
937            }
938            DEL => {}
939            _ => {
940                self.st = DcsIgnore;
941            }
942        }
943    }
944    fn on_dcs_pass<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
945        use State::*;
946        match b {
947            CAN | SUB => {
948                cb(VTEvent::DcsCancel);
949                self.st = Ground;
950            }
951            DEL => {}
952            ESC => {
953                self.st = DcsEsc;
954            }
955            _ => {
956                self.dcs_put(b, cb);
957            }
958        }
959    }
960    fn on_dcs_esc<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
961        use State::*;
962        match b {
963            ST_FINAL => {
964                self.dcs_end(cb);
965                self.st = Ground;
966            } // ST
967            ESC => {
968                self.dcs_put(ESC, cb);
969                self.st = DcsPassthrough;
970            }
971            _ => {
972                self.dcs_put(ESC, cb);
973                self.dcs_put(b, cb);
974                self.st = DcsPassthrough;
975            }
976        }
977    }
978
979    // ---- OSC
980    fn on_osc_string<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
981        use State::*;
982        match b {
983            CAN | SUB => {
984                self.reset_collectors();
985                cb(VTEvent::OscCancel);
986                self.st = Ground;
987            }
988            DEL => {}
989            BEL => {
990                self.used_bel = true;
991                self.osc_end(cb);
992                self.st = Ground;
993            }
994            ESC => {
995                self.st = State::OscEsc;
996            }
997            p if is_printable(p) => {
998                self.osc_put(p, cb);
999            }
1000            _ => {} // ignore other C0
1001        }
1002    }
1003    fn on_osc_esc<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
1004        use State::*;
1005        match b {
1006            ST_FINAL => {
1007                self.used_bel = false;
1008                self.osc_end(cb);
1009                self.st = Ground;
1010            } // ST
1011            ESC => {
1012                self.osc_put(ESC, cb); /* remain in OscEsc */
1013            }
1014            _ => {
1015                self.osc_put(ESC, cb);
1016                self.osc_put(b, cb);
1017                self.st = OscString;
1018            }
1019        }
1020    }
1021
1022    // ---- SOS/PM/APC (ignored payload)
1023    fn on_spa_string<F: FnMut(VTEvent)>(&mut self, b: u8, _cb: &mut F) {
1024        match b {
1025            CAN | SUB => {
1026                self.reset_collectors();
1027                self.st = State::Ground;
1028            }
1029            DEL => {}
1030            ESC => {
1031                self.st = State::SpaEsc;
1032            }
1033            _ => {}
1034        }
1035    }
1036    fn on_spa_esc<F: FnMut(VTEvent)>(&mut self, b: u8, _cb: &mut F) {
1037        match b {
1038            ST_FINAL => {
1039                self.reset_collectors();
1040                self.st = State::Ground;
1041            }
1042            ESC => { /* remain */ }
1043            _ => {
1044                self.st = State::SosPmApcString;
1045            }
1046        }
1047    }
1048}
1049
1050#[cfg(test)]
1051mod tests {
1052    use pretty_assertions::assert_eq;
1053
1054    use super::*;
1055
1056    fn decode_stream(input: &[u8]) -> String {
1057        println!("Input:");
1058        let mut s = Vec::new();
1059        _ = hxdmp::hexdump(input, &mut s);
1060        println!("{}", String::from_utf8_lossy(&s));
1061        let mut parser = VTPushParser::new();
1062        let mut result = String::new();
1063        let mut callback = |vt_input: VTEvent<'_>| {
1064            result.push_str(&format!("{:?}\n", vt_input));
1065        };
1066        parser.feed_with(input, &mut callback);
1067        println!("Result:");
1068        println!("{}", result);
1069        result
1070    }
1071
1072    #[test]
1073    fn test_large_escape2() {
1074        let result = decode_stream(
1075            &hex::decode(
1076                r#"
1077        1b5b495445524d3220332e352e31346e1b5d31303b7267623a646361612f6
1078        46361622f646361611b5c1b5d31313b7267623a313538652f313933612f31
1079        6537351b5c1b5b3f36343b313b323b343b363b31373b31383b32313b32326
1080        31b5b3e36343b323530303b30631b50217c36393534373236441b5c1b503e
1081        7c695465726d3220332e352e31341b5c1b5b383b33343b31343874"#
1082                    .replace(char::is_whitespace, ""),
1083            )
1084            .unwrap(),
1085        );
1086        assert_eq!(
1087            result.trim(),
1088            r#"
1089Csi(, '', 'I')
1090Raw('TERM2 3.5.14n')
1091OscStart
1092OscData('10;rgb:dcaa/dcab/dcaa')
1093OscEnd
1094OscStart
1095OscData('11;rgb:158e/193a/1e75')
1096OscEnd
1097Csi('?', '64', '1', '2', '4', '6', '17', '18', '21', '22', '', 'c')
1098Csi('>', '64', '2500', '0', '', 'c')
1099DcsStart(, '!', |)
1100DcsData('6954726D')
1101DcsEnd
1102DcsStart('>', '', |)
1103DcsData('iTerm2 3.5.14')
1104DcsEnd
1105Csi(, '8', '34', '148', '', 't')
1106        "#
1107            .trim()
1108        );
1109    }
1110
1111    #[test]
1112    fn test_basic_escape_sequences() {
1113        // Test basic ESC sequences
1114        let result = decode_stream(b"\x1b[1;2;3d");
1115        assert_eq!(result.trim(), "Csi(, '1', '2', '3', '', 'd')");
1116
1117        // Test ESC with intermediate
1118        let result = decode_stream(b"\x1b  M");
1119        assert_eq!(result.trim(), "Esc('  ', M)");
1120
1121        // Test SS3 (ESC O)
1122        let result = decode_stream(b"\x1bOA");
1123        assert_eq!(result.trim(), "Esc('', O)\nRaw('A')");
1124    }
1125
1126    #[test]
1127    fn test_csi_sequences() {
1128        // Test CSI with private parameter
1129        let result = decode_stream(b"\x1b[?25h");
1130        assert_eq!(result.trim(), "Csi('?', '25', '', 'h')");
1131
1132        // Test CSI with multiple parameters
1133        let result = decode_stream(b"\x1b[1;2;3;4;5m");
1134        assert_eq!(result.trim(), "Csi(, '1', '2', '3', '4', '5', '', 'm')");
1135
1136        // Test CSI with colon parameter
1137        let result = decode_stream(b"\x1b[3:1;2;3;4;5m");
1138        assert_eq!(result.trim(), "Csi(, '3:1', '2', '3', '4', '5', '', 'm')");
1139
1140        // Test CSI with intermediate
1141        let result = decode_stream(b"\x1b[  M");
1142        assert_eq!(result.trim(), "Csi(, '  ', 'M')");
1143    }
1144
1145    #[test]
1146    fn test_dcs_sequences() {
1147        // Test DCS with parameters
1148        let result = decode_stream(b"\x1bP 1;2;3|test data\x1b\\");
1149        assert_eq!(
1150            result.trim(),
1151            "DcsStart(, ' ', 1)\nDcsData(';2;3|test data')\nDcsEnd"
1152        );
1153
1154        // Test DCS with private parameter
1155        let result = decode_stream(b"\x1bP>1;2;3|more data\x1b\\");
1156        assert_eq!(
1157            result.trim(),
1158            "DcsStart('>', '1', '2', '3', '', |)\nDcsData('more data')\nDcsEnd"
1159        );
1160
1161        // Test DCS with intermediate
1162        let result = decode_stream(b"\x1bP 1;2;3  |data\x1b\\");
1163        assert_eq!(
1164            result.trim(),
1165            "DcsStart(, ' ', 1)\nDcsData(';2;3  |data')\nDcsEnd"
1166        );
1167
1168        let result = decode_stream(b"\x1bP1$r\x1b\\");
1169        assert_eq!(result.trim(), "DcsStart(, '1', '$', r)\nDcsEnd");
1170    }
1171
1172    #[test]
1173    fn test_osc_sequences() {
1174        // Test OSC with BEL terminator
1175        let result = decode_stream(b"\x1b]10;rgb:fff/000/000\x07");
1176        assert_eq!(
1177            result.trim(),
1178            "OscStart\nOscData('10;rgb:fff/000/000')\nOscEnd"
1179        );
1180
1181        // Test OSC with ST terminator
1182        let result = decode_stream(b"\x1b]11;rgb:000/fff/000\x1b\\");
1183        assert_eq!(
1184            result.trim(),
1185            "OscStart\nOscData('11;rgb:000/fff/000')\nOscEnd"
1186        );
1187
1188        // Test OSC with escape in data
1189        let result = decode_stream(b"\x1b]12;test [data\x1b\\");
1190        assert_eq!(result.trim(), "OscStart\nOscData('12;test [data')\nOscEnd");
1191    }
1192
1193    #[test]
1194    fn test_cancellation_and_invalid_sequences() {
1195        // Test CAN cancellation in CSI
1196        let result = decode_stream(b"x\x1b[1;2;3\x18y");
1197        assert_eq!(result.trim(), "Raw('x')\nRaw('y')");
1198
1199        // Test SUB cancellation in CSI
1200        let result = decode_stream(b"x\x1b[1;2;3\x1ay");
1201        assert_eq!(result.trim(), "Raw('x')\nRaw('y')");
1202
1203        // Test CAN cancellation in DCS (parser completes DCS then returns to ground)
1204        let result = decode_stream(b"x\x1bP 1;2;3|data\x18y");
1205        assert_eq!(
1206            result.trim(),
1207            "Raw('x')\nDcsStart(, ' ', 1)\nDcsCancel\nRaw('y')"
1208        );
1209
1210        // Test SUB cancellation in OSC (parser emits OscStart then cancels)
1211        let result = decode_stream(b"x\x1b]10;data\x1ay");
1212        assert_eq!(result.trim(), "Raw('x')\nOscStart\nOscCancel\nRaw('y')");
1213
1214        // Test invalid CSI sequence (ignored)
1215        let result = decode_stream(b"x\x1b[1;2;3gy");
1216        assert_eq!(
1217            result.trim(),
1218            "Raw('x')\nCsi(, '1', '2', '3', '', 'g')\nRaw('y')"
1219        );
1220
1221        // Test CSI ignore state
1222        let result = decode_stream(b"x\x1b[:1;2;3gy");
1223        assert_eq!(
1224            result.trim(),
1225            "Raw('x')\nCsi(, ':1', '2', '3', '', 'g')\nRaw('y')"
1226        );
1227    }
1228
1229    #[test]
1230    fn test_escape_sequences_with_raw_text() {
1231        // Test mixed raw text and escape sequences
1232        let result = decode_stream(b"Hello\x1b[1;2;3dWorld");
1233        assert_eq!(
1234            result.trim(),
1235            "Raw('Hello')\nCsi(, '1', '2', '3', '', 'd')\nRaw('World')"
1236        );
1237
1238        // Test escape sequences with UTF-8 text
1239        let result = decode_stream(b"\x1b[1;2;3d\xe4\xb8\xad\xe6\x96\x87");
1240        assert_eq!(result.trim(), "Csi(, '1', '2', '3', '', 'd')\nRaw('中文')");
1241    }
1242
1243    #[test]
1244    fn test_c0_control_characters() {
1245        // Test various C0 control characters
1246        let result = decode_stream(b"\x0a\x0d\x09\x08\x0c\x0b");
1247        assert_eq!(
1248            result.trim(),
1249            "C0(0a)\nC0(0d)\nC0(09)\nC0(08)\nC0(0c)\nC0(0b)"
1250        );
1251
1252        // Test C0 with raw text
1253        let result = decode_stream(b"Hello\x0aWorld");
1254        assert_eq!(result.trim(), "Raw('Hello')\nC0(0a)\nRaw('World')");
1255    }
1256
1257    #[test]
1258    fn test_complex_escape_sequences() {
1259        // Test complex CSI with all features
1260        let result = decode_stream(&hex::decode("1b5b3f32353b313b323b333a343b353b363b373b383b393b31303b31313b31323b31333b31343b31353b31363b31373b31383b31393b32303b32313b32323b32333b32343b32353b32363b32373b32383b32393b33303b33313b33323b33333b33343b33353b33363b33373b33383b33393b34303b34313b34323b34333b34343b34353b34363b34373b34383b34393b35303b35313b35323b35333b35343b35353b35363b35373b35383b35393b36303b36313b36323b36333b36343b36353b36363b36373b36383b36393b37303b37313b37323b37333b37343b37353b37363b37373b37383b37393b38303b38313b38323b38333b38343b38353b38363b38373b38383b38393b39303b39313b39323b39333b39343b39353b39363b39373b39383b39393b3130306d").unwrap());
1261        assert_eq!(
1262            result.trim(),
1263            "Csi('?', '25', '1', '2', '3:4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '100', '', 'm')"
1264        );
1265    }
1266
1267    #[test]
1268    fn test_escape_sequences_with_del() {
1269        // Test DEL character handling
1270        let result = decode_stream(b"\x1b[1;2;3\x7fm");
1271        assert_eq!(result.trim(), "Csi(, '1', '2', '3', '', 'm')");
1272
1273        // Test DEL in raw text
1274        let result = decode_stream(b"Hello\x7fWorld");
1275        assert_eq!(result.trim(), "Raw('HelloWorld')");
1276    }
1277
1278    #[test]
1279    fn test_escape_sequences_with_esc_esc() {
1280        // Test ESC ESC sequence
1281        let result = decode_stream(b"\x1b\x1b[1;2;3d");
1282        assert_eq!(result.trim(), "Csi(, '1', '2', '3', '', 'd')");
1283
1284        let result = decode_stream(b"\x1bP 1;2;3|\x1b\x1bdata\x1b\\");
1285        // Note: ESC ESC in DCS is handled correctly by the parser, escaping is done by the receiver
1286        assert_eq!(
1287            result.trim().replace("\x1b", "<ESC>"),
1288            "DcsStart(, ' ', 1)\nDcsData(';2;3|<ESC>data')\nDcsEnd"
1289        );
1290    }
1291
1292    #[test]
1293    fn test_sos_pm_apc_sequences() {
1294        // Test SOS (ESC X)
1295        let result = decode_stream(b"\x1bXtest data\x1b\\");
1296        assert_eq!(result.trim(), "");
1297
1298        // Test PM (ESC ^)
1299        let result = decode_stream(b"\x1b^test data\x1b\\");
1300        assert_eq!(result.trim(), "");
1301
1302        // Test APC (ESC _)
1303        let result = decode_stream(b"\x1b_test data\x1b\\");
1304        assert_eq!(result.trim(), "");
1305    }
1306
1307    #[test]
1308    fn test_edge_cases() {
1309        // Test empty input
1310        let result = decode_stream(&[]);
1311        assert_eq!(result.trim(), "");
1312
1313        // Test single ESC
1314        let result = decode_stream(b"\x1b");
1315        assert_eq!(result.trim(), "");
1316
1317        // Test incomplete CSI
1318        let result = decode_stream(b"\x1b[");
1319        assert_eq!(result.trim(), "");
1320
1321        // Test incomplete DCS
1322        let result = decode_stream(b"\x1bP");
1323        assert_eq!(result.trim(), "");
1324
1325        // Test incomplete OSC
1326        let result = decode_stream(b"\x1b]");
1327        assert_eq!(result.trim(), "OscStart");
1328    }
1329
1330    #[test]
1331    fn test_unicode_and_special_characters() {
1332        // Test with Unicode characters
1333        let result = decode_stream(b"\x1b[1;2;3d\xe4\xb8\xad\xe6\x96\x87");
1334        assert_eq!(result.trim(), "Csi(, '1', '2', '3', '', 'd')\nRaw('中文')");
1335
1336        // Test with special ASCII characters
1337        let result = decode_stream(b"\x1b[1;2;3d~!@#$%^&*()_+|\\{}[]:;\"|<>,.?/");
1338        assert_eq!(
1339            result.trim(),
1340            "Csi(, '1', '2', '3', '', 'd')\nRaw('~!@#$%^&*()_+|\\{}[]:;\"|<>,.?/')"
1341        );
1342    }
1343
1344    #[test]
1345    fn test_streaming_behavior() {
1346        // Test streaming DCS data
1347        let mut parser = VTPushParser::new().with_stream_flush_max(4); // Small flush size
1348        let mut result = String::new();
1349        let mut callback = |vt_input: VTEvent<'_>| {
1350            result.push_str(&format!("{:?}\n", vt_input));
1351        };
1352
1353        // Feed DCS data in chunks
1354        parser.feed_with(b"\x1bP1;2;3 |", &mut callback);
1355        parser.feed_with(b"data", &mut callback);
1356        parser.feed_with(b" more", &mut callback);
1357        parser.feed_with(b"\x1b\\", &mut callback);
1358
1359        assert_eq!(
1360            result.trim(),
1361            "DcsStart(, '1', '2', '3', ' ', |)\nDcsData('data')\nDcsData(' mor')\nDcsData('e')\nDcsEnd"
1362        );
1363    }
1364
1365    #[test]
1366    fn test_finish_method() {
1367        let mut parser = VTPushParser::new();
1368        let mut result = String::new();
1369        let mut callback = |vt_input: VTEvent<'_>| {
1370            result.push_str(&format!("{:?}\n", vt_input));
1371        };
1372
1373        // Start an incomplete sequence
1374        parser.feed_with(b"\x1b[1;2;3", &mut callback);
1375
1376        // Finish should flush any pending raw data
1377        parser.finish(&mut callback);
1378
1379        assert_eq!(result.trim(), "");
1380    }
1381
1382    #[test]
1383    fn test_csi_colons() {
1384        let test_cases: Vec<(&'static [u8], &[&str], u8)> = vec![
1385            // 1. FG truecolor
1386            (b"\x1b[38:2:255:128:64m", &["38:2:255:128:64"], b'm'),
1387            // 2. BG truecolor
1388            (b"\x1b[48:2:0:0:0m", &["48:2:0:0:0"], b'm'),
1389            // 3. FG indexed
1390            (b"\x1b[38:5:208m", &["38:5:208"], b'm'),
1391            // 4. BG indexed
1392            (b"\x1b[48:5:123m", &["48:5:123"], b'm'),
1393            // 5. Bold + FG indexed + BG truecolor
1394            (
1395                b"\x1b[1;38:5:208;48:2:30:30:30m",
1396                &["1", "38:5:208", "48:2:30:30:30"],
1397                b'm',
1398            ),
1399            // 6. Reset + FG truecolor
1400            (b"\x1b[0;38:2:12:34:56m", &["0", "38:2:12:34:56"], b'm'),
1401            // 7. Underline color truecolor with empty subparam (::)
1402            (b"\x1b[58:2::186:93:0m", &["58:2::186:93:0"], b'm'),
1403            // 8. FG truecolor + BG indexed + underline color truecolor
1404            (
1405                b"\x1b[38:2:10:20:30;48:5:17;58:2::200:100:0m",
1406                &["38:2:10:20:30", "48:5:17", "58:2::200:100:0"],
1407                b'm',
1408            ),
1409            // 9. Colon params with leading zeros
1410            (b"\x1b[38:2:000:007:042m", &["38:2:000:007:042"], b'm'),
1411            // 10. Large RGB values
1412            (b"\x1b[38:2:300:300:300m", &["38:2:300:300:300"], b'm'),
1413            // 11. Trailing semicolon with colon param (empty final param)
1414            (b"\x1b[38:5:15;m", &["38:5:15", ""], b'm'),
1415            // 12. Only colon param (no numeric params)
1416            (b"\x1b[38:2:1:2:3m", &["38:2:1:2:3"], b'm'),
1417        ];
1418
1419        for (input, expected_params, expected_final) in test_cases {
1420            let mut parser = VTPushParser::new();
1421            parser.feed_with(input, |event| match event {
1422                VTEvent::Csi {
1423                    private,
1424                    params,
1425                    intermediates,
1426                    final_byte,
1427                } => {
1428                    assert_eq!(
1429                        private, None,
1430                        "Expected no private prefix for input {:?}",
1431                        input
1432                    );
1433                    assert_eq!(
1434                        intermediates.is_empty(),
1435                        true,
1436                        "Expected no intermediates for input {:?}",
1437                        input
1438                    );
1439                    assert_eq!(
1440                        final_byte, expected_final,
1441                        "Expected final byte '{}' for input {:?}",
1442                        expected_final, input
1443                    );
1444
1445                    let param_strings: Vec<String> = params
1446                        .iter()
1447                        .map(|p| String::from_utf8_lossy(p).to_string())
1448                        .collect();
1449                    assert_eq!(
1450                        param_strings, expected_params,
1451                        "Parameter mismatch for input {:?}",
1452                        input
1453                    );
1454                }
1455                _ => panic!("Expected CSI event for input {:?}, got {:?}", input, event),
1456            });
1457        }
1458    }
1459
1460    #[test]
1461    fn test_dcs_ignore_st_handling() {
1462        // Test that DCS_IGNORE state doesn't handle ST (ESC \) as per spec
1463        // This test documents the current behavior which differs from the specification
1464
1465        // Create a DCS sequence that goes into ignore state (using colon)
1466        // ESC P : 1;2;3 | data ESC \
1467        // The colon should put us in DCS_IGNORE, then ESC \ should transition to GROUND per spec
1468        let result = decode_stream(b"Hello\x1bP:1;2;3|data\x1b\\World");
1469
1470        // Current implementation: DCS_IGNORE ignores ST, so we stay in ignore state
1471        // and the sequence is never properly terminated
1472        // Expected: Should transition to GROUND after ST, but current implementation doesn't
1473        assert_eq!(result.trim(), "Raw('Hello')\nRaw('World')");
1474
1475        // Test with additional data after the ST to see if we're back in GROUND
1476        let result = decode_stream(b"\x1bP:1;2;3|data\x1b\\Hello");
1477
1478        // This should show that we're not properly back in GROUND state
1479        // because the ST wasn't handled in DCS_IGNORE
1480        assert_eq!(result.trim(), "Raw('Hello')");
1481
1482        // Compare with a valid DCS sequence that doesn't go into ignore state
1483        let result = decode_stream(b"\x1bP1;2;3|data\x1b\\Hello");
1484        assert_eq!(
1485            result.trim(),
1486            "DcsStart(, '1', '2', '3', '', |)\nDcsData('data')\nDcsEnd\nRaw('Hello')"
1487        );
1488    }
1489
1490    #[test]
1491    fn test_dcs_ignore_cancellation() {
1492        // Test that CAN/SUB properly cancel DCS_IGNORE state
1493        let result = decode_stream(b"\x1bP:1;2;3|data\x18Hello"); // CAN
1494        assert_eq!(result.trim(), "Raw('Hello')");
1495
1496        let result = decode_stream(b"\x1bP:1;2;3|data\x1aHello"); // SUB
1497        assert_eq!(result.trim(), "Raw('Hello')");
1498    }
1499
1500    #[test]
1501    fn test_dcs_payload_passthrough() {
1502        // Test cases for DCS payload passthrough behavior
1503        // Notes: body must be passed through verbatim.
1504        // - ESC '\' (ST) ends the string.
1505        // - ESC ESC stays as two bytes in the body.
1506        // - ESC X (X!='\') is data: both ESC and the following byte are payload.
1507        // - BEL (0x07) is data in DCS (not a terminator).
1508
1509        let dcs_cases: &[(&[u8], &str)] = &[
1510            // 1) Minimal: embedded CSI SGR truecolor (colon params)
1511            (b"\x1bPq\x1b[38:2:12:34:56m\x1b\\", "<ESC>[38:2:12:34:56m"),
1512            // 2) Mixed payload: CSI + literal text
1513            (b"\x1bPq\x1b[48:2:0:0:0m;xyz\x1b\\", "<ESC>[48:2:0:0:0m;xyz"),
1514            // 3) DECRQSS-style reply payload (DCS 1$r ... ST) containing colon-CSI
1515            (
1516                b"\x1bP1$r\x1b[38:2:10:20:30;58:2::200:100:0m\x1b\\",
1517                "<ESC>[38:2:10:20:30;58:2::200:100:0m",
1518            ),
1519            // 4) ESC ESC and ESC X inside body (all data)
1520            (b"\x1bPqABC\x1b\x1bDEF\x1bXG\x1b\\", "ABC<ESC>DEF<ESC>XG"),
1521            // 5) BEL in body (data, not a terminator)
1522            (b"\x1bPqDATA\x07MORE\x1b\\", "DATA<BEL>MORE"),
1523            // 6) iTerm2-style header (!|) with embedded CSI 256-color
1524            (b"\x1bP!|\x1b[38:5:208m\x1b\\", "<ESC>[38:5:208m"),
1525            // 7) Private prefix + final '|' (>|) with plain text payload
1526            (b"\x1bP>|Hello world\x1b\\", "Hello world"),
1527            // 8) Multiple embedded CSIs back-to-back
1528            (
1529                b"\x1bPq\x1b[38:2:1:2:3m\x1b[48:5:17m\x1b\\",
1530                "<ESC>[38:2:1:2:3m<ESC>[48:5:17m",
1531            ),
1532            // 9) Long colon param with leading zeros
1533            (
1534                b"\x1bPq\x1b[58:2::000:007:042m\x1b\\",
1535                "<ESC>[58:2::000:007:042m",
1536            ),
1537            // 10) Payload that includes a literal ST *text* sequence "ESC \"
1538            //     (Note: inside DCS, this would actually TERMINATE; so to keep it as data we send "ESC ESC '\\"
1539            (
1540                b"\x1bPqKEEP \x1b\x1b\\ LITERAL ST\x1b\\",
1541                "KEEP <ESC>\\ LITERAL ST",
1542            ),
1543        ];
1544
1545        for (input, expected_body) in dcs_cases {
1546            let events = collect_events(input);
1547
1548            // Find DcsData events and concatenate their payloads
1549            let mut actual_body = String::new();
1550            for event in &events {
1551                if let Some(data_part) = event
1552                    .strip_prefix("DcsData('")
1553                    .and_then(|s| s.strip_suffix("')"))
1554                {
1555                    actual_body
1556                        .push_str(&data_part.replace("\x1b", "<ESC>").replace("\x07", "<BEL>"));
1557                }
1558            }
1559
1560            assert_eq!(
1561                actual_body, *expected_body,
1562                "DCS payload mismatch for input {:?}. Full events: {:#?}",
1563                input, events
1564            );
1565
1566            // Also verify we get proper DcsStart and DcsEnd events
1567            assert!(
1568                events.iter().any(|e| e.starts_with("DcsStart")),
1569                "Missing DcsStart for input {:?}. Events: {:#?}",
1570                input,
1571                events
1572            );
1573            assert!(
1574                events.iter().any(|e| e == "DcsEnd"),
1575                "Missing DcsEnd for input {:?}. Events: {:#?}",
1576                input,
1577                events
1578            );
1579        }
1580    }
1581
1582    fn collect_events(input: &[u8]) -> Vec<String> {
1583        let mut out = Vec::new();
1584        let mut p = VTPushParser::new();
1585        p.feed_with(input, |ev| out.push(format!("{:?}", ev)));
1586        out
1587    }
1588
1589    #[test]
1590    fn dcs_header_with_colon_is_ignored_case1() {
1591        // ESC P 1:2 q ... ST   -> colon inside header params (invalid)
1592        let ev = collect_events(b"\x1bP1:2qHELLO\x1b\\");
1593        // Expect: no DcsStart; the whole thing is ignored until ST
1594        assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
1595    }
1596
1597    #[test]
1598    fn dcs_header_with_colon_is_ignored_case2() {
1599        // Colon immediately after ESC P, before any digit
1600        let ev = collect_events(b"\x1bP:1qDATA\x1b\\");
1601        assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
1602    }
1603
1604    #[test]
1605    fn dcs_header_with_colon_is_ignored_case3() {
1606        // Mixed: digits;colon;digits then intermediates/final
1607        let ev = collect_events(b"\x1bP12:34!qPAYLOAD\x1b\\");
1608        assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
1609    }
1610
1611    #[test]
1612    fn osc_aborted_by_can_mid_body() {
1613        // ESC ] 0;Title <CAN> more <BEL>
1614        let mut s = Vec::new();
1615        s.extend_from_slice(b"\x1b]0;Title");
1616        s.push(CAN);
1617        s.extend_from_slice(b"more\x07");
1618
1619        let ev = collect_debug(&s);
1620
1621        // EXPECT_SPEC_STRICT: no events at all (no Start/Data/End)
1622        // assert!(ev.is_empty(), "{ev:#?}");
1623
1624        // EXPECT_PUSH_PARSER: Start emitted, but NO Data, NO End
1625        assert!(ev.iter().any(|e| e.starts_with("OscStart")), "{ev:#?}");
1626        assert!(!ev.iter().any(|e| e.starts_with("OscData")), "{ev:#?}");
1627        assert!(!ev.iter().any(|e| e.starts_with("OscEnd")), "{ev:#?}");
1628    }
1629
1630    #[test]
1631    fn osc_aborted_by_sub_before_terminator() {
1632        let mut s = Vec::new();
1633        s.extend_from_slice(b"\x1b]52;c;YWJjZA==");
1634        s.push(SUB); // abort
1635        s.extend_from_slice(b"\x1b\\"); // would have been ST, but must be ignored after abort
1636
1637        let ev = collect_debug(&s);
1638        // SPEC-STRICT:
1639        // assert!(ev.is_empty(), "{ev:#?}");
1640        // PUSH-PARSER:
1641        assert!(ev.iter().any(|e| e.starts_with("OscStart")), "{ev:#?}");
1642        assert!(!ev.iter().any(|e| e.starts_with("OscData")), "{ev:#?}");
1643        assert!(!ev.iter().any(|e| e.starts_with("OscEnd")), "{ev:#?}");
1644    }
1645
1646    /// Collect raw VTEvent debug lines for quick assertions.
1647    fn collect_debug(input: &[u8]) -> Vec<String> {
1648        let mut out = Vec::new();
1649        let mut p = VTPushParser::new();
1650        p.feed_with(input, |ev| out.push(format!("{:?}", ev)));
1651        out
1652    }
1653
1654    const CAN: u8 = 0x18;
1655    const SUB: u8 = 0x1A;
1656
1657    #[test]
1658    fn dcs_aborted_by_can_before_body() {
1659        // ESC P q <CAN> ... ST
1660        let mut s = Vec::new();
1661        s.extend_from_slice(b"\x1bPq"); // header (valid: final 'q')
1662        s.push(CAN);
1663        s.extend_from_slice(b"IGNORED\x1b\\"); // should be raw
1664
1665        let ev = collect_debug(&s);
1666
1667        assert_eq!(ev.len(), 4, "{ev:#?}");
1668        assert_eq!(ev[0], "DcsStart(, '', q)");
1669        assert_eq!(ev[1], "DcsCancel");
1670        assert_eq!(ev[2], "Raw('IGNORED')");
1671        assert_eq!(ev[3], "Esc('', \\)");
1672    }
1673
1674    #[test]
1675    fn dcs_aborted_by_can_mid_body() {
1676        // ESC P q ABC <CAN> more ST
1677        let mut s = Vec::new();
1678        s.extend_from_slice(b"\x1bPqABC");
1679        s.push(CAN);
1680        s.extend_from_slice(b"MORE\x1b\\"); // ignored after abort
1681
1682        let ev = collect_debug(&s);
1683
1684        assert_eq!(ev.len(), 4, "{ev:#?}");
1685        assert_eq!(ev[0], "DcsStart(, '', q)");
1686        assert_eq!(ev[1], "DcsCancel");
1687        assert_eq!(ev[2], "Raw('MORE')");
1688        assert_eq!(ev[3], "Esc('', \\)");
1689    }
1690
1691    /* ========= SOS / PM / APC (ESC X, ESC ^, ESC _) ========= */
1692
1693    #[test]
1694    fn spa_aborted_by_can_is_ignored() {
1695        // ESC _ data <CAN> more ST
1696        let mut s = Vec::new();
1697        s.extend_from_slice(b"\x1b_hello");
1698        s.push(CAN);
1699        s.extend_from_slice(b"world\x1b\\");
1700
1701        let ev = collect_debug(&s);
1702        assert_eq!(ev.len(), 2, "{ev:#?}");
1703        assert_eq!(ev[0], "Raw('world')");
1704        assert_eq!(ev[1], "Esc('', \\)");
1705    }
1706
1707    #[test]
1708    fn spa_sub_aborts_too() {
1709        let mut s = Vec::new();
1710        s.extend_from_slice(b"\x1bXhello");
1711        s.push(SUB);
1712        s.extend_from_slice(b"world\x1b\\");
1713        let ev = collect_debug(&s);
1714        assert_eq!(ev.len(), 2, "{ev:#?}");
1715        assert_eq!(ev[0], "Raw('world')");
1716        assert_eq!(ev[1], "Esc('', \\)");
1717    }
1718
1719    /* ========= Sanity: CAN outside strings is a C0 EXECUTE ========= */
1720
1721    #[test]
1722    fn can_in_ground_is_c0() {
1723        let mut s = Vec::new();
1724        s.extend_from_slice(b"abc");
1725        s.push(CAN);
1726        s.extend_from_slice(b"def");
1727        let ev = collect_debug(&s);
1728        // Expect Raw("abc"), C0(0x18), Raw("def")
1729        assert_eq!(ev.len(), 3, "{ev:#?}");
1730        assert_eq!(ev[0], "Raw('abc')");
1731        assert_eq!(ev[1], "C0(18)");
1732        assert_eq!(ev[2], "Raw('def')");
1733    }
1734}