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 pub fn idle(&mut self) -> Option<VTEvent<'static>> {
336 if self.st == State::Escape {
337 self.st = State::Ground;
338 Some(VTEvent::C0(ESC))
339 } else if self.st == State::EscInt {
340 self.st = State::Ground;
341 None
342 } else {
343 None
344 }
345 }
346
347 fn push_with<'this, 'input>(&'this mut self, b: u8) -> VTAction<'this> {
348 use State::*;
349 match self.st {
350 Ground => self.on_ground(b),
351 Escape => self.on_escape(b),
352 EscInt => self.on_esc_int(b),
353
354 CsiEntry => self.on_csi_entry(b),
355 CsiParam => self.on_csi_param(b),
356 CsiInt => self.on_csi_int(b),
357 CsiIgnore => self.on_csi_ignore(b),
358
359 DcsEntry => self.on_dcs_entry(b),
360 DcsParam => self.on_dcs_param(b),
361 DcsInt => self.on_dcs_int(b),
362 DcsIgnore => self.on_dcs_ignore(b),
363 DcsIgnoreEsc => self.on_dcs_ignore_esc(b),
364 DcsPassthrough => self.on_dcs_pass(b),
365 DcsEsc => self.on_dcs_esc(b),
366
367 OscString => self.on_osc_string(b),
368 OscEsc => self.on_osc_esc(b),
369
370 SosPmApcString => self.on_spa_string(b),
371 SpaEsc => self.on_spa_esc(b),
372 }
373 }
374
375 pub fn finish<F: FnMut(VTEvent)>(&mut self, cb: &mut F) {
376 self.reset_collectors();
377 self.st = State::Ground;
378
379 }
381
382 fn clear_hdr_collectors(&mut self) {
387 self.ints.clear();
388 self.params.clear();
389 self.cur_param.clear();
390 self.priv_prefix = None;
391 }
392
393 fn reset_collectors(&mut self) {
394 self.clear_hdr_collectors();
395 }
396
397 fn next_param(&mut self) {
398 self.params.push(std::mem::take(&mut self.cur_param));
399 }
400
401 fn finish_params_if_any(&mut self) {
402 if !self.cur_param.is_empty() || !self.params.is_empty() {
403 self.next_param();
404 }
405 }
406
407 fn emit_csi(&mut self, final_byte: u8) -> VTAction {
408 self.finish_params_if_any();
409
410 let mut borrowed: SmallVec<[&[u8]; 4]> = SmallVec::new();
412 borrowed.extend(self.params.iter().map(|v| v.as_slice()));
413
414 let privp = self.priv_prefix.take();
415 VTAction::Event(VTEvent::Csi {
416 private: privp,
417 params: ParamBuf {
418 params: &self.params,
419 },
420 intermediates: self.ints,
421 final_byte,
422 })
423 }
424
425 fn dcs_start(&mut self, final_byte: u8) -> VTAction {
426 self.finish_params_if_any();
427
428 let privp = self.priv_prefix.take();
429 VTAction::Event(VTEvent::DcsStart {
430 private: privp,
431 params: ParamBuf {
432 params: &self.params,
433 },
434 intermediates: self.ints,
435 final_byte,
436 })
437 }
438
439 fn on_ground(&mut self, b: u8) -> VTAction {
444 match b {
445 ESC => {
446 self.clear_hdr_collectors();
447 self.st = State::Escape;
448 VTAction::None
449 }
450 DEL => VTAction::Event(VTEvent::C0(DEL)),
451 c if is_c0(c) => VTAction::Event(VTEvent::C0(c)),
452 p if is_printable(p) => VTAction::Buffer(VTEmit::Ground),
453 _ => VTAction::Buffer(VTEmit::Ground), }
455 }
456
457 fn on_escape(&mut self, b: u8) -> VTAction {
458 use State::*;
459 match b {
460 CAN | SUB => {
461 self.st = Ground;
462 VTAction::None
463 }
464 DEL => VTAction::None,
465 c if is_intermediate(c) => {
466 if self.ints.push(c) {
467 self.st = EscInt;
468 } else {
469 self.st = Ground;
470 }
471 VTAction::None
472 }
473 CSI => {
474 if INTEREST & VT_PARSER_INTEREST_CSI == 0 {
475 self.st = CsiIgnore;
476 } else {
477 self.st = CsiEntry;
478 }
479 VTAction::None
480 }
481 DCS => {
482 if INTEREST & VT_PARSER_INTEREST_DCS == 0 {
483 self.st = DcsIgnore;
484 } else {
485 self.st = DcsEntry;
486 }
487 VTAction::None
488 }
489 OSC => {
490 self.st = OscString;
491 VTAction::Event(VTEvent::OscStart)
492 }
493 b'X' | b'^' | b'_' => {
494 self.st = State::SosPmApcString;
495 VTAction::None
496 }
497 c if is_final(c) || is_digit(c) => {
498 self.st = Ground;
499 VTAction::Event(VTEvent::Esc {
500 intermediates: self.ints,
501 final_byte: c,
502 })
503 }
504 ESC => {
505 VTAction::Event(VTEvent::C0(ESC))
507 }
508 _ => {
509 self.st = Ground;
510 VTAction::None
511 }
512 }
513 }
514
515 fn on_esc_int(&mut self, b: u8) -> VTAction {
516 use State::*;
517 match b {
518 CAN | SUB => {
519 self.st = Ground;
520 VTAction::None
521 }
522 DEL => VTAction::None,
523 c if is_intermediate(c) => {
524 if !self.ints.push(c) {
525 self.st = Ground;
526 }
527 VTAction::None
528 }
529 c if is_final(c) || is_digit(c) => {
530 self.st = Ground;
531 VTAction::Event(VTEvent::Esc {
532 intermediates: self.ints,
533 final_byte: c,
534 })
535 }
536 _ => {
537 self.st = Ground;
538 VTAction::None
539 }
540 }
541 }
542
543 fn on_csi_entry(&mut self, b: u8) -> VTAction {
545 use State::*;
546 match b {
547 CAN | SUB => {
548 self.st = Ground;
549 VTAction::None
550 }
551 DEL => VTAction::None,
552 ESC => {
553 self.st = Escape;
554 VTAction::None
555 }
556 c if is_priv(c) => {
557 self.priv_prefix = Some(c);
558 self.st = CsiParam;
559 VTAction::None
560 }
561 d if is_digit(d) => {
562 self.cur_param.push(d);
563 self.st = CsiParam;
564 VTAction::None
565 }
566 b';' => {
567 self.next_param();
568 self.st = CsiParam;
569 VTAction::None
570 }
571 b':' => {
572 self.cur_param.push(b':');
573 self.st = CsiParam;
574 VTAction::None
575 }
576 c if is_intermediate(c) => {
577 if self.ints.push(c) {
578 self.st = CsiInt;
579 } else {
580 self.st = Ground;
581 }
582 VTAction::None
583 }
584 c if is_final(c) => {
585 self.st = Ground;
586 self.emit_csi(c)
587 }
588 _ => {
589 self.st = CsiIgnore;
590 VTAction::None
591 }
592 }
593 }
594
595 fn on_csi_param(&mut self, b: u8) -> VTAction {
596 use State::*;
597 match b {
598 CAN | SUB => {
599 self.st = Ground;
600 VTAction::None
601 }
602 DEL => VTAction::None,
603 ESC => {
604 self.st = Escape;
605 VTAction::None
606 }
607 d if is_digit(d) => {
608 self.cur_param.push(d);
609 VTAction::None
610 }
611 b';' => {
612 self.next_param();
613 VTAction::None
614 }
615 b':' => {
616 self.cur_param.push(b':');
617 VTAction::None
618 }
619 c if is_intermediate(c) => {
620 if self.ints.push(c) {
621 self.st = CsiInt;
622 } else {
623 self.st = Ground;
624 }
625 VTAction::None
626 }
627 c if is_final(c) => {
628 self.st = Ground;
629 self.emit_csi(c)
630 }
631 _ => {
632 self.st = CsiIgnore;
633 VTAction::None
634 }
635 }
636 }
637
638 fn on_csi_int(&mut self, b: u8) -> VTAction {
639 use State::*;
640 match b {
641 CAN | SUB => {
642 self.st = Ground;
643 VTAction::None
644 }
645 DEL => VTAction::None,
646 ESC => {
647 self.st = Escape;
648 VTAction::None
649 }
650 c if is_intermediate(c) => {
651 if self.ints.push(c) {
652 self.st = CsiInt;
653 } else {
654 self.st = Ground;
655 }
656 VTAction::None
657 }
658 c if is_final(c) => {
659 self.st = Ground;
660 self.emit_csi(c)
661 }
662 _ => {
663 self.st = CsiIgnore;
664 VTAction::None
665 }
666 }
667 }
668
669 fn on_csi_ignore(&mut self, b: u8) -> VTAction {
670 use State::*;
671 match b {
672 CAN | SUB => {
673 self.st = Ground;
674 VTAction::None
675 }
676 DEL => VTAction::None,
677 ESC => {
678 self.st = Escape;
679 VTAction::None
680 }
681 c if is_final(c) => {
682 self.st = Ground;
683 VTAction::None
684 }
685 _ => VTAction::None,
686 }
687 }
688
689 fn on_dcs_entry(&mut self, b: u8) -> VTAction {
691 use State::*;
692 match b {
693 CAN | SUB => {
694 self.st = Ground;
695 VTAction::None
696 }
697 DEL => VTAction::None,
698 ESC => {
699 self.st = Escape;
700 VTAction::None
701 }
702 c if is_priv(c) => {
703 self.priv_prefix = Some(c);
704 self.st = DcsParam;
705 VTAction::None
706 }
707 d if is_digit(d) => {
708 self.cur_param.push(d);
709 self.st = DcsParam;
710 VTAction::None
711 }
712 b';' => {
713 self.next_param();
714 self.st = DcsParam;
715 VTAction::None
716 }
717 b':' => {
718 self.st = DcsIgnore;
719 VTAction::None
720 }
721 c if is_intermediate(c) => {
722 if self.ints.push(c) {
723 self.st = DcsInt;
724 } else {
725 self.st = Ground;
726 }
727 VTAction::None
728 }
729 c if is_final(c) => {
730 self.st = DcsPassthrough;
731 self.dcs_start(c)
732 }
733 _ => {
734 self.st = DcsIgnore;
735 VTAction::None
736 }
737 }
738 }
739
740 fn on_dcs_param(&mut self, b: u8) -> VTAction {
741 use State::*;
742 match b {
743 CAN | SUB => {
744 self.st = Ground;
745 VTAction::None
746 }
747 DEL => VTAction::None,
748 ESC => {
749 self.st = Escape;
750 VTAction::None
751 }
752 d if is_digit(d) => {
753 self.cur_param.push(d);
754 VTAction::None
755 }
756 b';' => {
757 self.next_param();
758 VTAction::None
759 }
760 b':' => {
761 self.st = DcsIgnore;
762 VTAction::None
763 }
764 c if is_intermediate(c) => {
765 if self.ints.push(c) {
766 self.st = DcsInt;
767 } else {
768 self.st = Ground;
769 }
770 self.st = DcsInt;
771 VTAction::None
772 }
773 c if is_final(c) => {
774 self.st = DcsPassthrough;
775 self.dcs_start(c)
776 }
777 _ => {
778 self.st = DcsIgnore;
779 VTAction::None
780 }
781 }
782 }
783
784 fn on_dcs_int(&mut self, b: u8) -> VTAction {
785 use State::*;
786 match b {
787 CAN | SUB => {
788 self.st = Ground;
789 VTAction::None
790 }
791 DEL => VTAction::None,
792 ESC => {
793 self.st = Escape;
794 VTAction::None
795 }
796 c if is_intermediate(c) => {
797 if self.ints.push(c) {
798 self.st = DcsInt;
799 } else {
800 self.st = Ground;
801 }
802 VTAction::None
803 }
804 c if is_final(c) || is_digit(c) || c == b':' || c == b';' => {
805 self.st = DcsPassthrough;
806 self.dcs_start(c)
807 }
808 _ => {
809 self.st = DcsIgnore;
810 VTAction::None
811 }
812 }
813 }
814
815 fn on_dcs_ignore(&mut self, b: u8) -> VTAction {
816 use State::*;
817 match b {
818 CAN | SUB => {
819 self.st = Ground;
820 VTAction::None
821 }
822 DEL => VTAction::None,
823 ESC => {
824 self.st = DcsIgnoreEsc;
825 VTAction::None
826 }
827 _ => VTAction::None,
828 }
829 }
830
831 fn on_dcs_ignore_esc(&mut self, b: u8) -> VTAction {
832 use State::*;
833 match b {
834 CAN | SUB => {
835 self.st = Ground;
836 VTAction::None
837 }
838 ST_FINAL => {
839 self.st = Ground;
840 VTAction::None
841 }
842 DEL => VTAction::None,
843 ESC => VTAction::None,
844 _ => {
845 self.st = DcsIgnore;
846 VTAction::None
847 }
848 }
849 }
850
851 fn on_dcs_pass(&mut self, b: u8) -> VTAction {
852 use State::*;
853 match b {
854 CAN | SUB => {
855 self.st = Ground;
856 VTAction::Cancel(VTEmit::Dcs)
857 }
858 DEL => VTAction::None,
859 ESC => {
860 self.st = DcsEsc;
861 VTAction::Hold(VTEmit::Dcs)
862 }
863 _ => VTAction::Buffer(VTEmit::Dcs),
864 }
865 }
866
867 fn on_dcs_esc(&mut self, b: u8) -> VTAction {
868 use State::*;
869 match b {
870 ST_FINAL => {
871 self.st = Ground;
872 VTAction::Event(VTEvent::DcsEnd)
873 }
874 ESC => {
875 VTAction::Hold(VTEmit::Dcs)
877 }
878 _ => {
879 self.st = DcsPassthrough;
881 VTAction::Buffer(VTEmit::Dcs)
882 }
883 }
884 }
885
886 fn on_osc_string(&mut self, b: u8) -> VTAction {
888 use State::*;
889 match b {
890 CAN | SUB => {
891 self.st = Ground;
892 VTAction::Cancel(VTEmit::Osc)
893 }
894 DEL => VTAction::None,
895 BEL => {
896 self.st = Ground;
897 VTAction::Event(VTEvent::OscEnd { used_bel: true })
898 }
899 ESC => {
900 self.st = OscEsc;
901 VTAction::Hold(VTEmit::Osc)
902 }
903 p if is_printable(p) => VTAction::Buffer(VTEmit::Osc),
904 _ => VTAction::None, }
906 }
907
908 fn on_osc_esc(&mut self, b: u8) -> VTAction {
909 use State::*;
910 match b {
911 ST_FINAL => {
912 self.st = Ground;
913 VTAction::Event(VTEvent::OscEnd { used_bel: false })
914 } ESC => VTAction::Hold(VTEmit::Osc),
916 _ => {
917 self.st = OscString;
918 VTAction::Buffer(VTEmit::Osc)
919 }
920 }
921 }
922
923 fn on_spa_string(&mut self, b: u8) -> VTAction {
925 use State::*;
926 match b {
927 CAN | SUB => {
928 self.st = Ground;
929 VTAction::None
930 }
931 DEL => VTAction::None,
932 ESC => {
933 self.st = SpaEsc;
934 VTAction::None
935 }
936 _ => VTAction::None,
937 }
938 }
939
940 fn on_spa_esc(&mut self, b: u8) -> VTAction {
941 use State::*;
942 match b {
943 ST_FINAL => {
944 self.st = Ground;
945 VTAction::None
946 }
947 ESC => {
948 VTAction::None
950 }
951 _ => {
952 self.st = State::SosPmApcString;
953 VTAction::None
954 }
955 }
956 }
957}
958
959#[cfg(test)]
960mod tests {
961 use pretty_assertions::assert_eq;
962
963 use super::*;
964
965 #[test]
966 fn test_edge_cases() {
967 let mut result = String::new();
969 VTPushParser::decode_buffer(&[], |e| result.push_str(&format!("{:?}\n", e)));
970 assert_eq!(result.trim(), "");
971
972 let mut result = String::new();
974 VTPushParser::decode_buffer(b"\x1b", |e| result.push_str(&format!("{:?}\n", e)));
975 assert_eq!(result.trim(), "");
976
977 let mut result = String::new();
979 VTPushParser::decode_buffer(b"\x1b[", |e| result.push_str(&format!("{:?}\n", e)));
980 assert_eq!(result.trim(), "");
981
982 let mut result = String::new();
984 VTPushParser::decode_buffer(b"\x1bP", |e| result.push_str(&format!("{:?}\n", e)));
985 assert_eq!(result.trim(), "");
986
987 let mut result = String::new();
989 VTPushParser::decode_buffer(b"\x1b]", |e| result.push_str(&format!("{:?}\n", e)));
990 assert_eq!(result.trim(), "OscStart");
991 }
992
993 #[test]
994 fn test_streaming_behavior() {
995 let mut parser = VTPushParser::new(); let mut result = String::new();
998 let mut callback = |vt_input: VTEvent<'_>| {
999 result.push_str(&format!("{:?}\n", vt_input));
1000 };
1001
1002 parser.feed_with(b"\x1bP1;2;3 |", &mut callback);
1004 parser.feed_with(b"data", &mut callback);
1005 parser.feed_with(b" more", &mut callback);
1006 parser.feed_with(b"\x1b\\", &mut callback);
1007
1008 assert_eq!(
1009 result.trim(),
1010 "DcsStart(, '1', '2', '3', ' ', |)\nDcsData('data')\nDcsData(' more')\nDcsEnd"
1011 );
1012 }
1013
1014 #[test]
1015 fn test_finish_method() {
1016 let mut parser = VTPushParser::new();
1017 let mut result = String::new();
1018 let mut callback = |vt_input: VTEvent<'_>| {
1019 result.push_str(&format!("{:?}\n", vt_input));
1020 };
1021
1022 parser.feed_with(b"\x1b[1;2;3", &mut callback);
1024
1025 parser.finish(&mut callback);
1027
1028 assert_eq!(result.trim(), "");
1029 }
1030
1031 #[test]
1032 fn test_dcs_payload_passthrough() {
1033 let dcs_cases: &[(&[u8], &str)] = &[
1041 (b"\x1bPq\x1b[38:2:12:34:56m\x1b\\", "<ESC>[38:2:12:34:56m"),
1043 (b"\x1bPq\x1b[48:2:0:0:0m;xyz\x1b\\", "<ESC>[48:2:0:0:0m;xyz"),
1045 (
1047 b"\x1bP1$r\x1b[38:2:10:20:30;58:2::200:100:0m\x1b\\",
1048 "<ESC>[38:2:10:20:30;58:2::200:100:0m",
1049 ),
1050 (
1052 b"\x1bPqABC\x1b\x1bDEF\x1bXG\x1b\\",
1053 "ABC<ESC><ESC>DEF<ESC>XG",
1054 ),
1055 (b"\x1bPqDATA\x07MORE\x1b\\", "DATA<BEL>MORE"),
1057 (b"\x1bP!|\x1b[38:5:208m\x1b\\", "<ESC>[38:5:208m"),
1059 (b"\x1bP>|Hello world\x1b\\", "Hello world"),
1061 (
1063 b"\x1bPq\x1b[38:2:1:2:3m\x1b[48:5:17m\x1b\\",
1064 "<ESC>[38:2:1:2:3m<ESC>[48:5:17m",
1065 ),
1066 (
1068 b"\x1bPq\x1b[58:2::000:007:042m\x1b\\",
1069 "<ESC>[58:2::000:007:042m",
1070 ),
1071 ];
1072
1073 for (input, expected_body) in dcs_cases {
1074 let events = collect_events(input);
1075
1076 let mut actual_body = String::new();
1078 for event in &events {
1079 if let Some(data_part) = event
1080 .strip_prefix("DcsData('")
1081 .and_then(|s| s.strip_suffix("')"))
1082 {
1083 actual_body
1084 .push_str(&data_part.replace("\x1b", "<ESC>").replace("\x07", "<BEL>"));
1085 }
1086 }
1087
1088 assert_eq!(
1089 actual_body, *expected_body,
1090 "DCS payload mismatch for input {:?}. Full events: {:#?}",
1091 input, events
1092 );
1093
1094 assert!(
1096 events.iter().any(|e| e.starts_with("DcsStart")),
1097 "Missing DcsStart for input {:?}. Events: {:#?}",
1098 input,
1099 events
1100 );
1101 assert!(
1102 events.iter().any(|e| e == "DcsEnd"),
1103 "Missing DcsEnd for input {:?}. Events: {:#?}",
1104 input,
1105 events
1106 );
1107 }
1108 }
1109
1110 fn collect_events(input: &[u8]) -> Vec<String> {
1111 let mut out = Vec::new();
1112 let mut p = VTPushParser::new();
1113 p.feed_with(input, &mut |ev| out.push(format!("{:?}", ev)));
1114 out
1115 }
1116
1117 #[test]
1118 fn dcs_header_with_colon_is_ignored_case1() {
1119 let ev = collect_events(b"\x1bP1:2qHELLO\x1b\\");
1121 assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
1123 }
1124
1125 #[test]
1126 fn dcs_header_with_colon_is_ignored_case2() {
1127 let ev = collect_events(b"\x1bP:1qDATA\x1b\\");
1129 assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
1130 }
1131
1132 #[test]
1133 fn dcs_header_with_colon_is_ignored_case3() {
1134 let ev = collect_events(b"\x1bP12:34!qPAYLOAD\x1b\\");
1136 assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
1137 }
1138
1139 #[test]
1140 fn osc_aborted_by_can_mid_body() {
1141 let mut s = Vec::new();
1143 s.extend_from_slice(b"\x1b]0;Title");
1144 s.push(CAN);
1145 s.extend_from_slice(b"more\x07");
1146
1147 let ev = collect_debug(&s);
1148
1149 assert!(ev.iter().any(|e| e.starts_with("OscStart")), "{ev:#?}");
1154 assert!(!ev.iter().any(|e| e.starts_with("OscData")), "{ev:#?}");
1155 assert!(!ev.iter().any(|e| e.starts_with("OscEnd")), "{ev:#?}");
1156 }
1157
1158 #[test]
1159 fn osc_aborted_by_sub_before_terminator() {
1160 let mut s = Vec::new();
1161 s.extend_from_slice(b"\x1b]52;c;YWJjZA==");
1162 s.push(SUB); s.extend_from_slice(b"\x1b\\"); let ev = collect_debug(&s);
1166 assert!(ev.iter().any(|e| e.starts_with("OscStart")), "{ev:#?}");
1170 assert!(!ev.iter().any(|e| e.starts_with("OscData")), "{ev:#?}");
1171 assert!(!ev.iter().any(|e| e.starts_with("OscEnd")), "{ev:#?}");
1172 }
1173
1174 fn collect_debug(input: &[u8]) -> Vec<String> {
1176 let mut out = Vec::new();
1177 let mut p = VTPushParser::new();
1178 p.feed_with(input, &mut |ev| out.push(format!("{:?}", ev)));
1179 out
1180 }
1181
1182 #[test]
1183 fn dcs_aborted_by_can_before_body() {
1184 let mut s = Vec::new();
1186 s.extend_from_slice(b"\x1bPq"); s.push(CAN);
1188 s.extend_from_slice(b"IGNORED\x1b\\"); let ev = collect_debug(&s);
1191
1192 assert_eq!(ev.len(), 4, "{ev:#?}");
1193 assert_eq!(ev[0], "DcsStart(, '', q)");
1194 assert_eq!(ev[1], "DcsCancel");
1195 assert_eq!(ev[2], "Raw('IGNORED')");
1196 assert_eq!(ev[3], "Esc('', \\)");
1197 }
1198
1199 #[test]
1200 fn dcs_aborted_by_can_mid_body() {
1201 let mut s = Vec::new();
1203 s.extend_from_slice(b"\x1bPqABC");
1204 s.push(CAN);
1205 s.extend_from_slice(b"MORE\x1b\\"); let ev = collect_debug(&s);
1208
1209 assert_eq!(ev.len(), 4, "{ev:#?}");
1210 assert_eq!(ev[0], "DcsStart(, '', q)");
1211 assert_eq!(ev[1], "DcsCancel");
1212 assert_eq!(ev[2], "Raw('MORE')");
1213 assert_eq!(ev[3], "Esc('', \\)");
1214 }
1215
1216 #[test]
1219 fn spa_aborted_by_can_is_ignored() {
1220 let mut s = Vec::new();
1222 s.extend_from_slice(b"\x1b_hello");
1223 s.push(CAN);
1224 s.extend_from_slice(b"world\x1b\\");
1225
1226 let ev = collect_debug(&s);
1227 assert_eq!(ev.len(), 2, "{ev:#?}");
1228 assert_eq!(ev[0], "Raw('world')");
1229 assert_eq!(ev[1], "Esc('', \\)");
1230 }
1231
1232 #[test]
1233 fn spa_sub_aborts_too() {
1234 let mut s = Vec::new();
1235 s.extend_from_slice(b"\x1bXhello");
1236 s.push(SUB);
1237 s.extend_from_slice(b"world\x1b\\");
1238 let ev = collect_debug(&s);
1239 assert_eq!(ev.len(), 2, "{ev:#?}");
1240 assert_eq!(ev[0], "Raw('world')");
1241 assert_eq!(ev[1], "Esc('', \\)");
1242 }
1243
1244 #[test]
1247 fn can_in_ground_is_c0() {
1248 let mut s = Vec::new();
1249 s.extend_from_slice(b"abc");
1250 s.push(CAN);
1251 s.extend_from_slice(b"def");
1252 let ev = collect_debug(&s);
1253 assert_eq!(ev.len(), 3, "{ev:#?}");
1255 assert_eq!(ev[0], "Raw('abc')");
1256 assert_eq!(ev[1], "C0(18)");
1257 assert_eq!(ev[2], "Raw('def')");
1258 }
1259}