vt_push_parser/
lib.rs

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