vt_push_parser/
lib.rs

1use smallvec::SmallVec;
2
3const ESC: u8 = 0x1B;
4const BEL: u8 = 0x07;
5const DEL: u8 = 0x7F;
6const CAN: u8 = 0x18;
7const SUB: u8 = 0x1A;
8const ST_FINAL: u8 = b'\\';
9
10#[derive(Default, Clone, Copy)]
11pub struct VTIntermediate {
12    data: [u8; 2],
13}
14
15impl VTIntermediate {
16    pub fn has(&self, c: u8) -> bool {
17        self.data[0] == c || self.data[1] == c
18    }
19
20    pub fn clear(&mut self) {
21        self.data[0] = 0;
22        self.data[1] = 0;
23    }
24
25    pub fn is_empty(&self) -> bool {
26        self.data[0] == 0 && self.data[1] == 0
27    }
28
29    pub fn len(&self) -> usize {
30        self.data.iter().filter(|&&c| c != 0).count()
31    }
32
33    #[must_use]
34    pub fn push(&mut self, c: u8) -> bool {
35        if c < 0x20 || c > 0x2F {
36            return false;
37        }
38
39        if self.data[0] == 0 {
40            self.data[0] = c;
41            true
42        } else if self.data[1] == 0 {
43            self.data[1] = c;
44            true
45        } else {
46            false
47        }
48    }
49}
50
51impl std::fmt::Debug for VTIntermediate {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        // Inefficient
54        write!(f, "'")?;
55        for c in self.data.iter() {
56            if *c == 0 {
57                break;
58            }
59            write!(f, "{}", *c as char)?;
60        }
61        write!(f, "'")?;
62        Ok(())
63    }
64}
65
66pub enum VTEvent<'a> {
67    // Plain printable text from GROUND (coalesced)
68    Raw(&'a [u8]),
69
70    // C0 control (EXECUTE)
71    C0(u8),
72
73    // ESC final (with intermediates)
74    Esc {
75        intermediates: VTIntermediate,
76        final_byte: u8,
77    },
78
79    // CSI short escape
80    Csi {
81        private: Option<u8>,
82        params: smallvec::SmallVec<[&'a [u8]; 4]>,
83        intermediates: VTIntermediate,
84        final_byte: u8,
85    },
86
87    // SS3 (ESC O …)
88    Ss3 {
89        intermediates: VTIntermediate,
90        final_byte: u8,
91    },
92
93    // DCS stream
94    DcsStart {
95        priv_prefix: Option<u8>,
96        params: smallvec::SmallVec<[&'a [u8]; 4]>,
97        intermediates: VTIntermediate,
98        final_byte: u8,
99    },
100    DcsData(&'a [u8]),
101    DcsEnd,
102
103    // OSC stream
104    OscStart,
105    OscData(&'a [u8]),
106    OscEnd {
107        used_bel: bool,
108    },
109}
110
111impl<'a> std::fmt::Debug for VTEvent<'a> {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        match self {
114            VTEvent::Raw(s) => {
115                write!(f, "Raw('")?;
116                for chunk in s.utf8_chunks() {
117                    write!(f, "{}", chunk.valid())?;
118                    if !chunk.invalid().is_empty() {
119                        write!(f, "<{}>", hex::encode(chunk.invalid()))?;
120                    }
121                }
122                write!(f, "')")?;
123                Ok(())
124            }
125            VTEvent::C0(b) => write!(f, "C0({:02x})", b),
126            VTEvent::Esc {
127                intermediates,
128                final_byte,
129            } => {
130                write!(f, "Esc({:?}", intermediates)?;
131                write!(f, ", {})", *final_byte as char)?;
132                Ok(())
133            }
134            VTEvent::Csi {
135                private,
136                params,
137                intermediates,
138                final_byte,
139            } => {
140                write!(f, "Csi(")?;
141                if let Some(p) = private {
142                    write!(f, "{:?}", *p as char)?;
143                }
144                for param in params {
145                    write!(f, ", '")?;
146                    for chunk in param.utf8_chunks() {
147                        write!(f, "{}", chunk.valid())?;
148                        if !chunk.invalid().is_empty() {
149                            write!(f, "<{}>", hex::encode(chunk.invalid()))?;
150                        }
151                    }
152                    write!(f, "'")?;
153                }
154                write!(f, ", {:?}", intermediates)?;
155                write!(f, ", {:?})", *final_byte as char)?;
156                Ok(())
157            }
158            VTEvent::Ss3 {
159                intermediates,
160                final_byte,
161            } => {
162                write!(f, "Ss3(")?;
163                write!(f, "{:?}", intermediates)?;
164                write!(f, ", {})", *final_byte as char)?;
165                Ok(())
166            }
167            VTEvent::DcsStart {
168                priv_prefix,
169                params,
170                intermediates,
171                final_byte,
172            } => {
173                write!(f, "DcsStart(")?;
174                if let Some(p) = priv_prefix {
175                    write!(f, "{:?}", *p as char)?;
176                }
177                for param in params {
178                    write!(f, ", {:02x?}", param)?;
179                }
180                write!(f, ", {:?}", intermediates)?;
181                write!(f, ", {})", *final_byte as char)?;
182                Ok(())
183            }
184            VTEvent::DcsData(s) => {
185                write!(f, "DcsData('")?;
186                for chunk in s.utf8_chunks() {
187                    write!(f, "{}", chunk.valid())?;
188                    if !chunk.invalid().is_empty() {
189                        write!(f, "<{}>", hex::encode(chunk.invalid()))?;
190                    }
191                }
192                write!(f, "')")?;
193                Ok(())
194            }
195            VTEvent::DcsEnd => write!(f, "DcsEnd"),
196            VTEvent::OscStart => write!(f, "OscStart"),
197            VTEvent::OscData(s) => {
198                write!(f, "OscData('")?;
199                for chunk in s.utf8_chunks() {
200                    write!(f, "{}", chunk.valid())?;
201                    if !chunk.invalid().is_empty() {
202                        write!(f, "<{}>", hex::encode(chunk.invalid()))?;
203                    }
204                }
205                write!(f, "')")?;
206                Ok(())
207            }
208            VTEvent::OscEnd { .. } => {
209                write!(f, "OscEnd")?;
210                Ok(())
211            }
212        }
213    }
214}
215
216#[inline]
217fn is_c0(b: u8) -> bool {
218    b <= 0x1F
219}
220#[inline]
221fn is_printable(b: u8) -> bool {
222    (0x20..=0x7E).contains(&b)
223}
224#[inline]
225fn is_intermediate(b: u8) -> bool {
226    (0x20..=0x2F).contains(&b)
227}
228#[inline]
229fn is_final(b: u8) -> bool {
230    (0x30..=0x7E).contains(&b)
231}
232#[inline]
233fn is_digit(b: u8) -> bool {
234    (b'0'..=b'9').contains(&b)
235}
236#[inline]
237fn is_priv(b: u8) -> bool {
238    matches!(b, b'<' | b'=' | b'>' | b'?')
239}
240
241#[derive(Debug, Copy, Clone, PartialEq, Eq)]
242enum State {
243    Ground,
244    Escape,
245    EscInt,
246    CsiEntry,
247    CsiParam,
248    CsiInt,
249    CsiIgnore,
250    DcsEntry,
251    DcsParam,
252    DcsInt,
253    DcsIgnore,
254    DcsPassthrough,
255    DcsEsc,
256    OscString,
257    OscEsc,
258    SosPmApcString,
259    SpaEsc,
260}
261
262pub struct VTPushParser {
263    st: State,
264
265    // GROUND raw coalescing
266    raw_buf: Vec<u8>,
267
268    // Header collectors for short escapes (we borrow from these in callbacks)
269    ints: VTIntermediate,
270    params: Vec<Vec<u8>>,
271    cur_param: Vec<u8>,
272    priv_prefix: Option<u8>,
273
274    // Streaming buffer (DCS/OSC bodies)
275    stream_buf: Vec<u8>,
276    used_bel: bool,
277
278    // Limits
279    max_short_hdr: usize,
280    stream_flush: usize,
281}
282
283impl VTPushParser {
284    pub fn new() -> Self {
285        Self {
286            st: State::Ground,
287            raw_buf: Vec::with_capacity(256),
288            ints: VTIntermediate::default(),
289            params: Vec::with_capacity(8),
290            cur_param: Vec::with_capacity(8),
291            priv_prefix: None,
292            stream_buf: Vec::with_capacity(8192),
293            used_bel: false,
294            max_short_hdr: 4096,
295            stream_flush: 8192,
296        }
297    }
298
299    pub fn with_limits(mut self, max_short_hdr: usize, stream_flush: usize) -> Self {
300        self.max_short_hdr = max_short_hdr;
301        self.stream_flush = stream_flush.max(1);
302        self
303    }
304
305    /* =====================
306    Callback-driven API
307    ===================== */
308
309    pub fn feed_with<F: FnMut(VTEvent)>(&mut self, input: &[u8], mut cb: F) {
310        for &b in input {
311            self.push_with(b, &mut cb);
312        }
313        // Emitting pending raw at end-of-chunk helps coalesce; you can remove if undesired.
314        self.flush_raw_if_any(&mut cb);
315    }
316
317    pub fn push_with<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
318        use State::*;
319        match self.st {
320            Ground => self.on_ground(b, cb),
321            Escape => self.on_escape(b, cb),
322            EscInt => self.on_esc_int(b, cb),
323
324            CsiEntry => self.on_csi_entry(b, cb),
325            CsiParam => self.on_csi_param(b, cb),
326            CsiInt => self.on_csi_int(b, cb),
327            CsiIgnore => self.on_csi_ignore(b, cb),
328
329            DcsEntry => self.on_dcs_entry(b, cb),
330            DcsParam => self.on_dcs_param(b, cb),
331            DcsInt => self.on_dcs_int(b, cb),
332            DcsIgnore => self.on_dcs_ignore(b, cb),
333            DcsPassthrough => self.on_dcs_pass(b, cb),
334            DcsEsc => self.on_dcs_esc(b, cb),
335
336            OscString => self.on_osc_string(b, cb),
337            OscEsc => self.on_osc_esc(b, cb),
338
339            SosPmApcString => self.on_spa_string(b, cb),
340            SpaEsc => self.on_spa_esc(b, cb),
341        }
342    }
343
344    pub fn finish<F: FnMut(VTEvent)>(&mut self, cb: &mut F) {
345        // Abort unterminated strings and flush raw.
346        self.reset_collectors();
347        self.st = State::Ground;
348        self.flush_raw_if_any(cb);
349    }
350
351    /* =====================
352    Emit helpers (borrowed)
353    ===================== */
354
355    fn flush_raw_if_any<F: FnMut(VTEvent)>(&mut self, cb: &mut F) {
356        if !self.raw_buf.is_empty() {
357            let slice = &self.raw_buf[..];
358            cb(VTEvent::Raw(slice));
359            self.raw_buf.clear(); // borrow ended with callback return
360        }
361    }
362
363    fn clear_hdr_collectors(&mut self) {
364        self.ints.clear();
365        self.params.clear();
366        self.cur_param.clear();
367        self.priv_prefix = None;
368    }
369
370    fn reset_collectors(&mut self) {
371        self.clear_hdr_collectors();
372        self.stream_buf.clear();
373        self.used_bel = false;
374    }
375
376    fn next_param(&mut self) {
377        self.params.push(std::mem::take(&mut self.cur_param));
378    }
379
380    fn finish_params_if_any(&mut self) {
381        if !self.cur_param.is_empty() || !self.params.is_empty() {
382            self.next_param();
383        }
384    }
385
386    fn emit_esc<F: FnMut(VTEvent)>(&mut self, final_byte: u8, cb: &mut F) {
387        self.flush_raw_if_any(cb);
388        cb(VTEvent::Esc {
389            intermediates: self.ints,
390            final_byte,
391        });
392        self.clear_hdr_collectors();
393    }
394
395    fn emit_csi<F: FnMut(VTEvent)>(&mut self, final_byte: u8, cb: &mut F) {
396        self.flush_raw_if_any(cb);
397        self.finish_params_if_any();
398
399        // Build borrowed views into self.params
400        let mut borrowed: SmallVec<[&[u8]; 4]> = SmallVec::new();
401        borrowed.extend(self.params.iter().map(|v| v.as_slice()));
402
403        let privp = self.priv_prefix.take();
404        cb(VTEvent::Csi {
405            private: privp,
406            params: borrowed,
407            intermediates: self.ints,
408            final_byte,
409        });
410        self.clear_hdr_collectors();
411    }
412
413    fn dcs_start<F: FnMut(VTEvent)>(&mut self, final_byte: u8, cb: &mut F) {
414        self.flush_raw_if_any(cb);
415        self.finish_params_if_any();
416
417        let mut borrowed: SmallVec<[&[u8]; 4]> = SmallVec::new();
418        borrowed.extend(self.params.iter().map(|v| v.as_slice()));
419
420        let privp = self.priv_prefix.take();
421        cb(VTEvent::DcsStart {
422            priv_prefix: privp,
423            params: borrowed,
424            intermediates: self.ints,
425            final_byte,
426        });
427        self.stream_buf.clear();
428        // keep header buffers intact until after callback; already done
429        self.clear_hdr_collectors();
430    }
431
432    fn dcs_put<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
433        self.stream_buf.push(b);
434        if self.stream_buf.len() >= self.stream_flush {
435            let slice = &self.stream_buf[..];
436            cb(VTEvent::DcsData(slice));
437            self.stream_buf.clear();
438        }
439    }
440
441    fn dcs_end<F: FnMut(VTEvent)>(&mut self, cb: &mut F) {
442        if !self.stream_buf.is_empty() {
443            let slice = &self.stream_buf[..];
444            cb(VTEvent::DcsData(slice));
445            self.stream_buf.clear();
446        }
447        cb(VTEvent::DcsEnd);
448        self.reset_collectors();
449    }
450
451    fn osc_start<F: FnMut(VTEvent)>(&mut self, cb: &mut F) {
452        self.flush_raw_if_any(cb);
453        self.used_bel = false;
454        self.stream_buf.clear();
455        cb(VTEvent::OscStart);
456    }
457
458    fn osc_put<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
459        self.stream_buf.push(b);
460        if self.stream_buf.len() >= self.stream_flush {
461            let slice = &self.stream_buf[..];
462            cb(VTEvent::OscData(slice));
463            self.stream_buf.clear();
464        }
465    }
466
467    fn osc_end<F: FnMut(VTEvent)>(&mut self, cb: &mut F) {
468        if !self.stream_buf.is_empty() {
469            let slice = &self.stream_buf[..];
470            cb(VTEvent::OscData(slice));
471            self.stream_buf.clear();
472        }
473        let used_bel = self.used_bel;
474        cb(VTEvent::OscEnd { used_bel });
475        self.reset_collectors();
476    }
477
478    /* =====================
479    State handlers
480    ===================== */
481
482    fn on_ground<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
483        match b {
484            ESC => {
485                self.clear_hdr_collectors();
486                self.flush_raw_if_any(cb);
487                self.st = State::Escape;
488            }
489            DEL => {}
490            c if is_c0(c) => {
491                self.flush_raw_if_any(cb);
492                cb(VTEvent::C0(c));
493            }
494            p if is_printable(p) => {
495                self.raw_buf.push(p);
496            }
497            _ => {
498                self.raw_buf.push(b);
499            } // safe fallback
500        }
501    }
502
503    fn on_escape<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
504        use State::*;
505        match b {
506            CAN | SUB => {
507                self.st = Ground;
508            }
509            DEL => {}
510            c if is_intermediate(c) => {
511                self.ints.push(c);
512                self.st = EscInt;
513            }
514            b'[' => {
515                self.clear_hdr_collectors();
516                self.st = CsiEntry;
517            }
518            b'P' => {
519                self.clear_hdr_collectors();
520                self.st = DcsEntry;
521            }
522            b']' => {
523                self.clear_hdr_collectors();
524                self.osc_start(cb);
525                self.st = OscString;
526            }
527            b'X' | b'^' | b'_' => {
528                self.clear_hdr_collectors();
529                self.st = State::SosPmApcString;
530            }
531            c if is_final(c) => {
532                self.emit_esc(c, cb);
533                self.st = Ground;
534            }
535            ESC => { /* ESC ESC allowed */ }
536            _ => {
537                self.st = Ground;
538            }
539        }
540    }
541    fn on_esc_int<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
542        use State::*;
543        match b {
544            CAN | SUB => {
545                self.st = Ground;
546            }
547            DEL => {}
548            c if is_intermediate(c) => {
549                self.ints.push(c);
550            }
551            c if is_final(c) => {
552                self.emit_esc(c, cb);
553                self.st = Ground;
554            }
555            _ => {
556                self.st = Ground;
557            }
558        }
559    }
560
561    // ---- CSI
562    fn on_csi_entry<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
563        use State::*;
564        match b {
565            CAN | SUB => {
566                self.st = Ground;
567            }
568            DEL => {}
569            ESC => {
570                self.st = Escape;
571            }
572            c if is_priv(c) => {
573                self.priv_prefix = Some(c);
574                self.st = CsiParam;
575            }
576            d if is_digit(d) => {
577                self.cur_param.push(d);
578                self.st = CsiParam;
579            }
580            b';' => {
581                self.next_param();
582                self.st = CsiParam;
583            }
584            b':' => {
585                self.cur_param.push(b':');
586                self.st = CsiParam;
587            }
588            c if is_intermediate(c) => {
589                self.ints.push(c);
590                self.st = CsiInt;
591            }
592            c if is_final(c) => {
593                self.emit_csi(c, cb);
594                self.st = Ground;
595            }
596            _ => {
597                self.st = CsiIgnore;
598            }
599        }
600    }
601    fn on_csi_param<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
602        use State::*;
603        match b {
604            CAN | SUB => {
605                self.st = Ground;
606            }
607            DEL => {}
608            ESC => {
609                self.st = Escape;
610            }
611            d if is_digit(d) => {
612                self.cur_param.push(d);
613            }
614            b';' => {
615                self.next_param();
616            }
617            b':' => {
618                self.cur_param.push(b':');
619            }
620            c if is_intermediate(c) => {
621                self.ints.push(c);
622                self.st = CsiInt;
623            }
624            c if is_final(c) => {
625                self.emit_csi(c, cb);
626                self.st = Ground;
627            }
628            _ => {
629                self.st = CsiIgnore;
630            }
631        }
632    }
633    fn on_csi_int<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
634        use State::*;
635        match b {
636            CAN | SUB => {
637                self.st = Ground;
638            }
639            DEL => {}
640            ESC => {
641                self.st = Escape;
642            }
643            c if is_intermediate(c) => {
644                self.ints.push(c);
645            }
646            c if is_final(c) => {
647                self.emit_csi(c, cb);
648                self.st = Ground;
649            }
650            _ => {
651                self.st = CsiIgnore;
652            }
653        }
654    }
655    fn on_csi_ignore<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
656        use State::*;
657        match b {
658            CAN | SUB => {
659                self.st = Ground;
660            }
661            DEL => {}
662            ESC => {
663                self.st = Escape;
664            }
665            c if is_final(c) => {
666                self.st = Ground;
667            }
668            _ => {}
669        }
670    }
671
672    // ---- DCS
673    fn on_dcs_entry<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
674        use State::*;
675        match b {
676            CAN | SUB => {
677                self.st = Ground;
678            }
679            DEL => {}
680            ESC => {
681                self.st = Escape;
682            }
683            c if is_priv(c) => {
684                self.priv_prefix = Some(c);
685                self.st = DcsParam;
686            }
687            d if is_digit(d) => {
688                self.cur_param.push(d);
689                self.st = DcsParam;
690            }
691            b';' => {
692                self.next_param();
693                self.st = DcsParam;
694            }
695            b':' => {
696                self.st = DcsIgnore;
697            }
698            c if is_intermediate(c) => {
699                self.ints.push(c);
700                self.st = DcsInt;
701            }
702            c if is_final(c) => {
703                self.dcs_start(c, cb);
704                self.st = DcsPassthrough;
705            }
706            _ => {
707                self.st = DcsIgnore;
708            }
709        }
710    }
711    fn on_dcs_param<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
712        use State::*;
713        match b {
714            CAN | SUB => {
715                self.st = Ground;
716            }
717            DEL => {}
718            ESC => {
719                self.st = Escape;
720            }
721            d if is_digit(d) => {
722                self.cur_param.push(d);
723            }
724            b';' => {
725                self.next_param();
726            }
727            b':' => {
728                self.st = DcsIgnore;
729            }
730            c if is_intermediate(c) => {
731                self.ints.push(c);
732                self.st = DcsInt;
733            }
734            c if is_final(c) => {
735                self.dcs_start(c, cb);
736                self.st = DcsPassthrough;
737            }
738            _ => {
739                self.st = DcsIgnore;
740            }
741        }
742    }
743    fn on_dcs_int<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
744        use State::*;
745        match b {
746            CAN | SUB => {
747                self.st = Ground;
748            }
749            DEL => {}
750            ESC => {
751                self.st = Escape;
752            }
753            c if is_intermediate(c) => {
754                self.ints.push(c);
755            }
756            c if is_final(c) => {
757                self.dcs_start(c, cb);
758                self.st = DcsPassthrough;
759            }
760            _ => {
761                self.st = DcsIgnore;
762            }
763        }
764    }
765    fn on_dcs_ignore<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
766        use State::*;
767        match b {
768            CAN | SUB => {
769                self.st = Ground;
770            }
771            DEL => {}
772            ESC => {
773                self.st = Escape;
774            }
775            // stay until ST (handled in DcsEsc path if you want); we just drop here.
776            _ => {}
777        }
778    }
779    fn on_dcs_pass<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
780        use State::*;
781        match b {
782            CAN | SUB => {
783                self.dcs_end(cb);
784                self.st = Ground;
785            }
786            DEL => {}
787            ESC => {
788                self.st = DcsEsc;
789            }
790            _ => {
791                self.dcs_put(b, cb);
792            }
793        }
794    }
795    fn on_dcs_esc<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
796        use State::*;
797        match b {
798            ST_FINAL => {
799                self.dcs_end(cb);
800                self.st = Ground;
801            } // ST
802            ESC => {
803                self.dcs_put(ESC, cb); /* remain in DcsEsc */
804            }
805            _ => {
806                self.dcs_put(ESC, cb);
807                self.dcs_put(b, cb);
808                self.st = DcsPassthrough;
809            }
810        }
811    }
812
813    // ---- OSC
814    fn on_osc_string<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
815        use State::*;
816        match b {
817            CAN | SUB => {
818                self.reset_collectors();
819                self.st = Ground;
820            }
821            DEL => {}
822            BEL => {
823                self.used_bel = true;
824                self.osc_end(cb);
825                self.st = Ground;
826            }
827            ESC => {
828                self.st = State::OscEsc;
829            }
830            p if is_printable(p) => {
831                self.osc_put(p, cb);
832            }
833            _ => {} // ignore other C0
834        }
835    }
836    fn on_osc_esc<F: FnMut(VTEvent)>(&mut self, b: u8, cb: &mut F) {
837        use State::*;
838        match b {
839            ST_FINAL => {
840                self.used_bel = false;
841                self.osc_end(cb);
842                self.st = Ground;
843            } // ST
844            ESC => {
845                self.osc_put(ESC, cb); /* remain in OscEsc */
846            }
847            _ => {
848                self.osc_put(ESC, cb);
849                self.osc_put(b, cb);
850                self.st = OscString;
851            }
852        }
853    }
854
855    // ---- SOS/PM/APC (ignored payload as per your machine)
856    fn on_spa_string<F: FnMut(VTEvent)>(&mut self, b: u8, _cb: &mut F) {
857        match b {
858            CAN | SUB => {
859                self.reset_collectors();
860                self.st = State::Ground;
861            }
862            DEL => {}
863            ESC => {
864                self.st = State::SpaEsc;
865            }
866            _ => {}
867        }
868    }
869    fn on_spa_esc<F: FnMut(VTEvent)>(&mut self, b: u8, _cb: &mut F) {
870        match b {
871            ST_FINAL => {
872                self.reset_collectors();
873                self.st = State::Ground;
874            }
875            ESC => { /* remain */ }
876            _ => {
877                self.st = State::SosPmApcString;
878            }
879        }
880    }
881}
882
883#[cfg(test)]
884mod tests {
885    use pretty_assertions::assert_eq;
886
887    use super::*;
888
889    fn decode_stream(input: &[u8]) -> String {
890        println!("Input:");
891        let mut s = Vec::new();
892        _ = hxdmp::hexdump(input, &mut s);
893        println!("{}", String::from_utf8_lossy(&s));
894        let mut parser = VTPushParser::new();
895        let mut result = String::new();
896        let mut callback = |vt_input: VTEvent<'_>| {
897            result.push_str(&format!("{:?}\n", vt_input));
898        };
899        parser.feed_with(input, &mut callback);
900        println!("Result:");
901        println!("{}", result);
902        result
903    }
904
905    #[test]
906    fn test_large_escape2() {
907        let result = decode_stream(
908            &hex::decode(
909                r#"
910        1b5b495445524d3220332e352e31346e1b5d31303b7267623a646361612f6
911        46361622f646361611b5c1b5d31313b7267623a313538652f313933612f31
912        6537351b5c1b5b3f36343b313b323b343b363b31373b31383b32313b32326
913        31b5b3e36343b323530303b30631b50217c36393534373236441b5c1b503e
914        7c695465726d3220332e352e31341b5c1b5b383b33343b31343874"#
915                    .replace(char::is_whitespace, ""),
916            )
917            .unwrap(),
918        );
919        assert_eq!(
920            result.trim(),
921            r#"
922Csi(, '', 'I')
923Raw('TERM2 3.5.14n')
924OscStart
925OscData('10;rgb:dcaa/dcab/dcaa')
926OscEnd
927OscStart
928OscData('11;rgb:158e/193a/1e75')
929OscEnd
930Csi('?', '64', '1', '2', '4', '6', '17', '18', '21', '22', '', 'c')
931Csi('>', '64', '2500', '0', '', 'c')
932DcsStart(, '!', |)
933DcsData('6954726D')
934DcsEnd
935DcsStart('>', '', |)
936DcsData('iTerm2 3.5.14')
937DcsEnd
938Csi(, '8', '34', '148', '', 't')
939        "#
940            .trim()
941        );
942    }
943}