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
20pub use event::{VTEvent, VTIntermediate};
22pub use signature::VTEscapeSignature;
23
24use crate::event::{Param, ParamBuf, Params};
25
26pub enum VTAction<'a> {
28 None,
31 Event(VTEvent<'a>),
34 Buffer(VTEmit),
37 Hold(VTEmit),
40 Cancel(VTEmit),
42}
43
44#[derive(Debug, Copy, Clone, PartialEq, Eq)]
45pub enum VTEmit {
46 Ground,
48 Dcs,
50 Osc,
52}
53
54#[inline]
55const fn is_c0(b: u8) -> bool {
56 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;
121pub const VT_PARSER_INTEREST_CSI: u8 = 1 << 0;
123pub const VT_PARSER_INTEREST_DCS: u8 = 1 << 1;
125pub const VT_PARSER_INTEREST_OSC: u8 = 1 << 2;
127pub 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 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 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 #[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 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 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 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 }
366
367 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 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 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), }
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 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 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 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 VTAction::Hold(VTEmit::Dcs)
836 }
837 _ => {
838 self.st = DcsPassthrough;
840 VTAction::Buffer(VTEmit::Dcs)
841 }
842 }
843 }
844
845 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, }
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 } ESC => VTAction::Hold(VTEmit::Osc),
875 _ => {
876 self.st = OscString;
877 VTAction::Buffer(VTEmit::Osc)
878 }
879 }
880 }
881
882 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 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 let mut result = String::new();
928 VTPushParser::decode_buffer(&[], |e| result.push_str(&format!("{:?}\n", e)));
929 assert_eq!(result.trim(), "");
930
931 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 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 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 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 let mut parser = VTPushParser::new(); let mut result = String::new();
957 let mut callback = |vt_input: VTEvent<'_>| {
958 result.push_str(&format!("{:?}\n", vt_input));
959 };
960
961 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 parser.feed_with(b"\x1b[1;2;3", &mut callback);
983
984 parser.finish(&mut callback);
986
987 assert_eq!(result.trim(), "");
988 }
989
990 #[test]
991 fn test_dcs_payload_passthrough() {
992 let dcs_cases: &[(&[u8], &str)] = &[
1000 (b"\x1bPq\x1b[38:2:12:34:56m\x1b\\", "<ESC>[38:2:12:34:56m"),
1002 (b"\x1bPq\x1b[48:2:0:0:0m;xyz\x1b\\", "<ESC>[48:2:0:0:0m;xyz"),
1004 (
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 (
1011 b"\x1bPqABC\x1b\x1bDEF\x1bXG\x1b\\",
1012 "ABC<ESC><ESC>DEF<ESC>XG",
1013 ),
1014 (b"\x1bPqDATA\x07MORE\x1b\\", "DATA<BEL>MORE"),
1016 (b"\x1bP!|\x1b[38:5:208m\x1b\\", "<ESC>[38:5:208m"),
1018 (b"\x1bP>|Hello world\x1b\\", "Hello world"),
1020 (
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 (
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 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 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 let ev = collect_events(b"\x1bP1:2qHELLO\x1b\\");
1080 assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
1082 }
1083
1084 #[test]
1085 fn dcs_header_with_colon_is_ignored_case2() {
1086 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 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 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 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); s.extend_from_slice(b"\x1b\\"); let ev = collect_debug(&s);
1125 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 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 let mut s = Vec::new();
1145 s.extend_from_slice(b"\x1bPq"); s.push(CAN);
1147 s.extend_from_slice(b"IGNORED\x1b\\"); 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 let mut s = Vec::new();
1162 s.extend_from_slice(b"\x1bPqABC");
1163 s.push(CAN);
1164 s.extend_from_slice(b"MORE\x1b\\"); 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 #[test]
1178 fn spa_aborted_by_can_is_ignored() {
1179 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 #[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 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}