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 SS3: u8 = b'O';
18const DCS: u8 = b'P';
19const ST_FINAL: u8 = b'\\';
20
21pub use event::{VTEvent, VTIntermediate};
23pub use signature::VTEscapeSignature;
24
25use crate::event::ParamBuf;
26
27pub enum VTAction<'a> {
29 None,
32 Event(VTEvent<'a>),
35 Buffer(VTEmit),
38 Hold(VTEmit),
41 Cancel(VTEmit),
43}
44
45#[derive(Debug, Copy, Clone, PartialEq, Eq)]
46pub enum VTEmit {
47 Ground,
49 Dcs,
51 Osc,
53}
54
55#[inline]
56fn is_c0(b: u8) -> bool {
57 b <= 0x1F
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]
68fn is_final(b: u8) -> bool {
69 (0x30..=0x7E).contains(&b)
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
80#[derive(Debug, Copy, Clone, PartialEq, Eq)]
81enum State {
82 Ground,
83 Escape,
84 EscInt,
85 CsiEntry,
86 CsiParam,
87 CsiInt,
88 CsiIgnore,
89 DcsEntry,
90 DcsParam,
91 DcsInt,
92 DcsIgnore,
93 DcsIgnoreEsc,
94 DcsPassthrough,
95 DcsEsc,
96 OscString,
97 OscEsc,
98 SosPmApcString,
99 SpaEsc,
100}
101
102pub struct VTPushParser {
103 st: State,
104
105 ints: VTIntermediate,
107 params: Vec<Vec<u8>>,
108 cur_param: Vec<u8>,
109 priv_prefix: Option<u8>,
110 held_byte: Option<u8>,
111 held_emit: Option<VTEmit>,
112
113 used_bel: bool,
115}
116
117impl VTPushParser {
118 pub fn new() -> Self {
119 Self {
120 st: State::Ground,
121 ints: VTIntermediate::default(),
122 params: Vec::with_capacity(8),
123 cur_param: Vec::with_capacity(8),
124 priv_prefix: None,
125 held_byte: None,
126 held_emit: None,
127 used_bel: false,
128 }
129 }
130
131 pub fn decode_buffer<'a>(input: &'a [u8], mut cb: impl for<'b> FnMut(VTEvent<'b>)) {
133 let mut parser = Self::new();
134 parser.feed_with(input, &mut cb);
135 parser.finish(&mut cb);
136 }
137
138 pub fn feed_with<'this: 'input, 'input, F: for<'any> FnMut(VTEvent<'any>)>(
150 &'this mut self,
151 input: &'input [u8],
152 cb: &mut F,
153 ) {
154 if input.is_empty() {
155 return;
156 }
157
158 struct State {
159 buffer_idx: usize,
160 current_emit: Option<VTEmit>,
161 hold: bool,
162 }
163
164 let mut state = State {
165 buffer_idx: 0,
166 current_emit: self.held_emit.take(),
167 hold: self.held_byte.is_some(),
168 };
169
170 macro_rules! emit {
171 ($state:ident, $i:expr, $cb:expr) => {
172 let hold = std::mem::take(&mut $state.hold);
173 if let Some(emit) = $state.current_emit.take() {
174 let mut i = $i;
175 if hold {
176 i = i - 1;
177 }
178
179 let range = $state.buffer_idx..i;
180 if range.len() > 0 {
181 match emit {
182 VTEmit::Ground => $cb(VTEvent::Raw(&input[range])),
183 VTEmit::Dcs => $cb(VTEvent::DcsData(&input[range])),
184 VTEmit::Osc => $cb(VTEvent::OscData(&input[range])),
185 }
186 }
187 }
188 };
189 }
190
191 let mut held_byte = self.held_byte.take();
192
193 for (i, &b) in input.iter().enumerate() {
194 let action = self.push_with(b);
195
196 match action {
197 VTAction::None => {
198 emit!(state, i, cb);
199 }
200 VTAction::Event(e) => {
201 emit!(state, i, cb);
202 cb(e);
203 }
204 VTAction::Buffer(emit) | VTAction::Hold(emit) => {
205 if i == 0 {
206 if let Some(h) = held_byte.take() {
207 match emit {
208 VTEmit::Ground => cb(VTEvent::Raw(&[h])),
209 VTEmit::Dcs => cb(VTEvent::DcsData(&[h])),
210 VTEmit::Osc => cb(VTEvent::OscData(&[h])),
211 }
212 }
213 }
214
215 debug_assert!(state.current_emit.is_none() || state.current_emit == Some(emit));
216
217 state.hold = matches!(action, VTAction::Hold(_));
218 if state.current_emit.is_none() {
219 state.buffer_idx = i;
220 state.current_emit = Some(emit);
221 }
222 }
223 VTAction::Cancel(emit) => {
224 state.current_emit = None;
225 state.hold = false;
226 match emit {
227 VTEmit::Ground => unreachable!(),
228 VTEmit::Dcs => cb(VTEvent::DcsCancel),
229 VTEmit::Osc => cb(VTEvent::OscCancel),
230 }
231 }
232 };
233 }
234
235 let hold = state.hold;
237 emit!(state, input.len(), cb);
238
239 if hold {
240 self.held_byte = Some(input[input.len() - 1]);
241 }
242 }
243
244 fn push_with<'this, 'input>(&'this mut self, b: u8) -> VTAction<'this> {
245 use State::*;
246 match self.st {
247 Ground => self.on_ground(b),
248 Escape => self.on_escape(b),
249 EscInt => self.on_esc_int(b),
250
251 CsiEntry => self.on_csi_entry(b),
252 CsiParam => self.on_csi_param(b),
253 CsiInt => self.on_csi_int(b),
254 CsiIgnore => self.on_csi_ignore(b),
255
256 DcsEntry => self.on_dcs_entry(b),
257 DcsParam => self.on_dcs_param(b),
258 DcsInt => self.on_dcs_int(b),
259 DcsIgnore => self.on_dcs_ignore(b),
260 DcsIgnoreEsc => self.on_dcs_ignore_esc(b),
261 DcsPassthrough => self.on_dcs_pass(b),
262 DcsEsc => self.on_dcs_esc(b),
263
264 OscString => self.on_osc_string(b),
265 OscEsc => self.on_osc_esc(b),
266
267 SosPmApcString => self.on_spa_string(b),
268 SpaEsc => self.on_spa_esc(b),
269 }
270 }
271
272 pub fn finish<F: FnMut(VTEvent)>(&mut self, cb: &mut F) {
273 self.reset_collectors();
274 self.st = State::Ground;
275
276 }
278
279 fn clear_hdr_collectors(&mut self) {
284 self.ints.clear();
285 self.params.clear();
286 self.cur_param.clear();
287 self.priv_prefix = None;
288 }
289
290 fn reset_collectors(&mut self) {
291 self.clear_hdr_collectors();
292 self.used_bel = false;
293 }
294
295 fn next_param(&mut self) {
296 self.params.push(std::mem::take(&mut self.cur_param));
297 }
298
299 fn finish_params_if_any(&mut self) {
300 if !self.cur_param.is_empty() || !self.params.is_empty() {
301 self.next_param();
302 }
303 }
304
305 fn emit_csi(&mut self, final_byte: u8) -> VTAction {
306 self.finish_params_if_any();
307
308 let mut borrowed: SmallVec<[&[u8]; 4]> = SmallVec::new();
310 borrowed.extend(self.params.iter().map(|v| v.as_slice()));
311
312 let privp = self.priv_prefix.take();
313 VTAction::Event(VTEvent::Csi {
314 private: privp,
315 params: ParamBuf {
316 params: &self.params,
317 },
318 intermediates: self.ints,
319 final_byte,
320 })
321 }
322
323 fn dcs_start(&mut self, final_byte: u8) -> VTAction {
324 self.finish_params_if_any();
325
326 let privp = self.priv_prefix.take();
327 VTAction::Event(VTEvent::DcsStart {
328 priv_prefix: privp,
329 params: ParamBuf {
330 params: &self.params,
331 },
332 intermediates: self.ints,
333 final_byte,
334 })
335 }
336
337 fn on_ground(&mut self, b: u8) -> VTAction {
342 match b {
343 ESC => {
344 self.clear_hdr_collectors();
345 self.st = State::Escape;
346 VTAction::None
347 }
348 DEL => VTAction::Event(VTEvent::C0(DEL)),
349 c if is_c0(c) => VTAction::Event(VTEvent::C0(c)),
350 p if is_printable(p) => VTAction::Buffer(VTEmit::Ground),
351 _ => VTAction::Buffer(VTEmit::Ground), }
353 }
354
355 fn on_escape(&mut self, b: u8) -> VTAction {
356 use State::*;
357 match b {
358 CAN | SUB => {
359 self.st = Ground;
360 VTAction::None
361 }
362 DEL => VTAction::None,
363 c if is_intermediate(c) => {
364 self.ints.push(c);
365 self.st = EscInt;
366 VTAction::None
367 }
368 CSI => {
369 self.st = CsiEntry;
370 VTAction::None
371 }
372 DCS => {
373 self.st = DcsEntry;
374 VTAction::None
375 }
376 OSC => {
377 self.used_bel = false;
378 self.st = OscString;
379 VTAction::Event(VTEvent::OscStart)
380 }
381 b'X' | b'^' | b'_' => {
382 self.st = State::SosPmApcString;
383 VTAction::None
384 }
385 c if is_final(c) => {
386 self.st = Ground;
387 VTAction::Event(VTEvent::Esc {
388 intermediates: self.ints,
389 final_byte: c,
390 })
391 }
392 ESC => {
393 VTAction::None
395 }
396 _ => {
397 self.st = Ground;
398 VTAction::None
399 }
400 }
401 }
402
403 fn on_esc_int(&mut self, b: u8) -> VTAction {
404 use State::*;
405 match b {
406 CAN | SUB => {
407 self.st = Ground;
408 VTAction::None
409 }
410 DEL => VTAction::None,
411 c if is_intermediate(c) => {
412 self.ints.push(c);
413 VTAction::None
414 }
415 c if is_final(c) => {
416 self.st = Ground;
417 VTAction::Event(VTEvent::Esc {
418 intermediates: self.ints,
419 final_byte: c,
420 })
421 }
422 _ => {
423 self.st = Ground;
424 VTAction::None
425 }
426 }
427 }
428
429 fn on_csi_entry(&mut self, b: u8) -> VTAction {
431 use State::*;
432 match b {
433 CAN | SUB => {
434 self.st = Ground;
435 VTAction::None
436 }
437 DEL => VTAction::None,
438 ESC => {
439 self.st = Escape;
440 VTAction::None
441 }
442 c if is_priv(c) => {
443 self.priv_prefix = Some(c);
444 self.st = CsiParam;
445 VTAction::None
446 }
447 d if is_digit(d) => {
448 self.cur_param.push(d);
449 self.st = CsiParam;
450 VTAction::None
451 }
452 b';' => {
453 self.next_param();
454 self.st = CsiParam;
455 VTAction::None
456 }
457 b':' => {
458 self.cur_param.push(b':');
459 self.st = CsiParam;
460 VTAction::None
461 }
462 c if is_intermediate(c) => {
463 self.ints.push(c);
464 self.st = CsiInt;
465 VTAction::None
466 }
467 c if is_final(c) => {
468 self.st = Ground;
469 self.emit_csi(c)
470 }
471 _ => {
472 self.st = CsiIgnore;
473 VTAction::None
474 }
475 }
476 }
477
478 fn on_csi_param(&mut self, b: u8) -> VTAction {
479 use State::*;
480 match b {
481 CAN | SUB => {
482 self.st = Ground;
483 VTAction::None
484 }
485 DEL => VTAction::None,
486 ESC => {
487 self.st = Escape;
488 VTAction::None
489 }
490 d if is_digit(d) => {
491 self.cur_param.push(d);
492 VTAction::None
493 }
494 b';' => {
495 self.next_param();
496 VTAction::None
497 }
498 b':' => {
499 self.cur_param.push(b':');
500 VTAction::None
501 }
502 c if is_intermediate(c) => {
503 self.ints.push(c);
504 self.st = CsiInt;
505 VTAction::None
506 }
507 c if is_final(c) => {
508 self.st = Ground;
509 self.emit_csi(c)
510 }
511 _ => {
512 self.st = CsiIgnore;
513 VTAction::None
514 }
515 }
516 }
517
518 fn on_csi_int(&mut self, b: u8) -> VTAction {
519 use State::*;
520 match b {
521 CAN | SUB => {
522 self.st = Ground;
523 VTAction::None
524 }
525 DEL => VTAction::None,
526 ESC => {
527 self.st = Escape;
528 VTAction::None
529 }
530 c if is_intermediate(c) => {
531 self.ints.push(c);
532 VTAction::None
533 }
534 c if is_final(c) => {
535 self.st = Ground;
536 self.emit_csi(c)
537 }
538 _ => {
539 self.st = CsiIgnore;
540 VTAction::None
541 }
542 }
543 }
544
545 fn on_csi_ignore(&mut self, b: u8) -> VTAction {
546 use State::*;
547 match b {
548 CAN | SUB => {
549 self.st = Ground;
550 VTAction::None
551 }
552 DEL => VTAction::None,
553 ESC => {
554 self.st = Escape;
555 VTAction::None
556 }
557 c if is_final(c) => {
558 self.st = Ground;
559 VTAction::None
560 }
561 _ => VTAction::None,
562 }
563 }
564
565 fn on_dcs_entry(&mut self, b: u8) -> VTAction {
567 use State::*;
568 match b {
569 CAN | SUB => {
570 self.st = Ground;
571 VTAction::None
572 }
573 DEL => VTAction::None,
574 ESC => {
575 self.st = Escape;
576 VTAction::None
577 }
578 c if is_priv(c) => {
579 self.priv_prefix = Some(c);
580 self.st = DcsParam;
581 VTAction::None
582 }
583 d if is_digit(d) => {
584 self.cur_param.push(d);
585 self.st = DcsParam;
586 VTAction::None
587 }
588 b';' => {
589 self.next_param();
590 self.st = DcsParam;
591 VTAction::None
592 }
593 b':' => {
594 self.st = DcsIgnore;
595 VTAction::None
596 }
597 c if is_intermediate(c) => {
598 self.ints.push(c);
599 self.st = DcsInt;
600 VTAction::None
601 }
602 c if is_final(c) => {
603 self.st = DcsPassthrough;
604 self.dcs_start(c)
605 }
606 _ => {
607 self.st = DcsIgnore;
608 VTAction::None
609 }
610 }
611 }
612
613 fn on_dcs_param(&mut self, b: u8) -> VTAction {
614 use State::*;
615 match b {
616 CAN | SUB => {
617 self.st = Ground;
618 VTAction::None
619 }
620 DEL => VTAction::None,
621 ESC => {
622 self.st = Escape;
623 VTAction::None
624 }
625 d if is_digit(d) => {
626 self.cur_param.push(d);
627 VTAction::None
628 }
629 b';' => {
630 self.next_param();
631 VTAction::None
632 }
633 b':' => {
634 self.st = DcsIgnore;
635 VTAction::None
636 }
637 c if is_intermediate(c) => {
638 self.ints.push(c);
639 self.st = DcsInt;
640 VTAction::None
641 }
642 c if is_final(c) => {
643 self.st = DcsPassthrough;
644 self.dcs_start(c)
645 }
646 _ => {
647 self.st = DcsIgnore;
648 VTAction::None
649 }
650 }
651 }
652
653 fn on_dcs_int(&mut self, b: u8) -> VTAction {
654 use State::*;
655 match b {
656 CAN | SUB => {
657 self.st = Ground;
658 VTAction::None
659 }
660 DEL => VTAction::None,
661 ESC => {
662 self.st = Escape;
663 VTAction::None
664 }
665 c if is_intermediate(c) => {
666 self.ints.push(c);
667 VTAction::None
668 }
669 c if is_final(c) => {
670 self.st = DcsPassthrough;
671 self.dcs_start(c)
672 }
673 _ => {
674 self.st = DcsIgnore;
675 VTAction::None
676 }
677 }
678 }
679
680 fn on_dcs_ignore(&mut self, b: u8) -> VTAction {
681 use State::*;
682 match b {
683 CAN | SUB => {
684 self.st = Ground;
685 VTAction::None
686 }
687 DEL => VTAction::None,
688 ESC => {
689 self.st = DcsIgnoreEsc;
690 VTAction::None
691 }
692 _ => VTAction::None,
693 }
694 }
695
696 fn on_dcs_ignore_esc(&mut self, b: u8) -> VTAction {
697 use State::*;
698 match b {
699 CAN | SUB => {
700 self.st = Ground;
701 VTAction::None
702 }
703 ST_FINAL => {
704 self.st = Ground;
705 VTAction::None
706 }
707 DEL => VTAction::None,
708 _ => {
709 self.st = DcsIgnore;
710 VTAction::None
711 }
712 }
713 }
714
715 fn on_dcs_pass(&mut self, b: u8) -> VTAction {
716 use State::*;
717 match b {
718 CAN | SUB => {
719 self.st = Ground;
720 VTAction::Cancel(VTEmit::Dcs)
721 }
722 DEL => VTAction::None,
723 ESC => {
724 self.st = DcsEsc;
725 VTAction::Hold(VTEmit::Dcs)
726 }
727 _ => VTAction::Buffer(VTEmit::Dcs),
728 }
729 }
730
731 fn on_dcs_esc(&mut self, b: u8) -> VTAction {
732 use State::*;
733 match b {
734 ST_FINAL => {
735 self.st = Ground;
736 VTAction::Event(VTEvent::DcsEnd)
737 }
738 ESC => {
739 VTAction::Hold(VTEmit::Dcs)
741 }
742 _ => {
743 self.st = DcsPassthrough;
745 VTAction::Buffer(VTEmit::Dcs)
746 }
747 }
748 }
749
750 fn on_osc_string(&mut self, b: u8) -> VTAction {
752 use State::*;
753 match b {
754 CAN | SUB => {
755 self.st = Ground;
756 VTAction::Cancel(VTEmit::Osc)
757 }
758 DEL => VTAction::None,
759 BEL => {
760 self.used_bel = true;
761 self.st = Ground;
762 VTAction::Event(VTEvent::OscEnd {
763 used_bel: self.used_bel,
764 })
765 }
766 ESC => {
767 self.st = OscEsc;
768 VTAction::Hold(VTEmit::Osc)
769 }
770 p if is_printable(p) => VTAction::Buffer(VTEmit::Osc),
771 _ => VTAction::None, }
773 }
774
775 fn on_osc_esc(&mut self, b: u8) -> VTAction {
776 use State::*;
777 match b {
778 ST_FINAL => {
779 self.used_bel = false;
780 self.st = Ground;
781 VTAction::Event(VTEvent::OscEnd {
782 used_bel: self.used_bel,
783 })
784 } ESC => VTAction::Hold(VTEmit::Osc),
786 _ => {
787 self.st = OscString;
788 VTAction::Buffer(VTEmit::Osc)
789 }
790 }
791 }
792
793 fn on_spa_string(&mut self, b: u8) -> VTAction {
795 use State::*;
796 match b {
797 CAN | SUB => {
798 self.st = Ground;
799 VTAction::None
800 }
801 DEL => VTAction::None,
802 ESC => {
803 self.st = SpaEsc;
804 VTAction::None
805 }
806 _ => VTAction::None,
807 }
808 }
809
810 fn on_spa_esc(&mut self, b: u8) -> VTAction {
811 use State::*;
812 match b {
813 ST_FINAL => {
814 self.st = Ground;
815 VTAction::None
816 }
817 ESC => {
818 VTAction::None
820 }
821 _ => {
822 self.st = State::SosPmApcString;
823 VTAction::None
824 }
825 }
826 }
827}
828
829#[cfg(test)]
830mod tests {
831 use pretty_assertions::assert_eq;
832
833 use super::*;
834
835 #[test]
836 fn test_edge_cases() {
837 let mut result = String::new();
839 VTPushParser::decode_buffer(&[], |e| result.push_str(&format!("{:?}\n", e)));
840 assert_eq!(result.trim(), "");
841
842 let mut result = String::new();
844 VTPushParser::decode_buffer(b"\x1b", |e| result.push_str(&format!("{:?}\n", e)));
845 assert_eq!(result.trim(), "");
846
847 let mut result = String::new();
849 VTPushParser::decode_buffer(b"\x1b[", |e| result.push_str(&format!("{:?}\n", e)));
850 assert_eq!(result.trim(), "");
851
852 let mut result = String::new();
854 VTPushParser::decode_buffer(b"\x1bP", |e| result.push_str(&format!("{:?}\n", e)));
855 assert_eq!(result.trim(), "");
856
857 let mut result = String::new();
859 VTPushParser::decode_buffer(b"\x1b]", |e| result.push_str(&format!("{:?}\n", e)));
860 assert_eq!(result.trim(), "OscStart");
861 }
862
863 #[test]
864 fn test_streaming_behavior() {
865 let mut parser = VTPushParser::new(); let mut result = String::new();
868 let mut callback = |vt_input: VTEvent<'_>| {
869 result.push_str(&format!("{:?}\n", vt_input));
870 };
871
872 parser.feed_with(b"\x1bP1;2;3 |", &mut callback);
874 parser.feed_with(b"data", &mut callback);
875 parser.feed_with(b" more", &mut callback);
876 parser.feed_with(b"\x1b\\", &mut callback);
877
878 assert_eq!(
879 result.trim(),
880 "DcsStart(, '1', '2', '3', ' ', |)\nDcsData('data')\nDcsData(' more')\nDcsEnd"
881 );
882 }
883
884 #[test]
885 fn test_finish_method() {
886 let mut parser = VTPushParser::new();
887 let mut result = String::new();
888 let mut callback = |vt_input: VTEvent<'_>| {
889 result.push_str(&format!("{:?}\n", vt_input));
890 };
891
892 parser.feed_with(b"\x1b[1;2;3", &mut callback);
894
895 parser.finish(&mut callback);
897
898 assert_eq!(result.trim(), "");
899 }
900
901 #[test]
902 fn test_dcs_payload_passthrough() {
903 let dcs_cases: &[(&[u8], &str)] = &[
911 (b"\x1bPq\x1b[38:2:12:34:56m\x1b\\", "<ESC>[38:2:12:34:56m"),
913 (b"\x1bPq\x1b[48:2:0:0:0m;xyz\x1b\\", "<ESC>[48:2:0:0:0m;xyz"),
915 (
917 b"\x1bP1$r\x1b[38:2:10:20:30;58:2::200:100:0m\x1b\\",
918 "<ESC>[38:2:10:20:30;58:2::200:100:0m",
919 ),
920 (
922 b"\x1bPqABC\x1b\x1bDEF\x1bXG\x1b\\",
923 "ABC<ESC><ESC>DEF<ESC>XG",
924 ),
925 (b"\x1bPqDATA\x07MORE\x1b\\", "DATA<BEL>MORE"),
927 (b"\x1bP!|\x1b[38:5:208m\x1b\\", "<ESC>[38:5:208m"),
929 (b"\x1bP>|Hello world\x1b\\", "Hello world"),
931 (
933 b"\x1bPq\x1b[38:2:1:2:3m\x1b[48:5:17m\x1b\\",
934 "<ESC>[38:2:1:2:3m<ESC>[48:5:17m",
935 ),
936 (
938 b"\x1bPq\x1b[58:2::000:007:042m\x1b\\",
939 "<ESC>[58:2::000:007:042m",
940 ),
941 ];
942
943 for (input, expected_body) in dcs_cases {
944 let events = collect_events(input);
945
946 let mut actual_body = String::new();
948 for event in &events {
949 if let Some(data_part) = event
950 .strip_prefix("DcsData('")
951 .and_then(|s| s.strip_suffix("')"))
952 {
953 actual_body
954 .push_str(&data_part.replace("\x1b", "<ESC>").replace("\x07", "<BEL>"));
955 }
956 }
957
958 assert_eq!(
959 actual_body, *expected_body,
960 "DCS payload mismatch for input {:?}. Full events: {:#?}",
961 input, events
962 );
963
964 assert!(
966 events.iter().any(|e| e.starts_with("DcsStart")),
967 "Missing DcsStart for input {:?}. Events: {:#?}",
968 input,
969 events
970 );
971 assert!(
972 events.iter().any(|e| e == "DcsEnd"),
973 "Missing DcsEnd for input {:?}. Events: {:#?}",
974 input,
975 events
976 );
977 }
978 }
979
980 fn collect_events(input: &[u8]) -> Vec<String> {
981 let mut out = Vec::new();
982 let mut p = VTPushParser::new();
983 p.feed_with(input, &mut |ev| out.push(format!("{:?}", ev)));
984 out
985 }
986
987 #[test]
988 fn dcs_header_with_colon_is_ignored_case1() {
989 let ev = collect_events(b"\x1bP1:2qHELLO\x1b\\");
991 assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
993 }
994
995 #[test]
996 fn dcs_header_with_colon_is_ignored_case2() {
997 let ev = collect_events(b"\x1bP:1qDATA\x1b\\");
999 assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
1000 }
1001
1002 #[test]
1003 fn dcs_header_with_colon_is_ignored_case3() {
1004 let ev = collect_events(b"\x1bP12:34!qPAYLOAD\x1b\\");
1006 assert!(ev.iter().all(|e| !e.starts_with("DcsStart")), "{ev:#?}");
1007 }
1008
1009 #[test]
1010 fn osc_aborted_by_can_mid_body() {
1011 let mut s = Vec::new();
1013 s.extend_from_slice(b"\x1b]0;Title");
1014 s.push(CAN);
1015 s.extend_from_slice(b"more\x07");
1016
1017 let ev = collect_debug(&s);
1018
1019 assert!(ev.iter().any(|e| e.starts_with("OscStart")), "{ev:#?}");
1024 assert!(!ev.iter().any(|e| e.starts_with("OscData")), "{ev:#?}");
1025 assert!(!ev.iter().any(|e| e.starts_with("OscEnd")), "{ev:#?}");
1026 }
1027
1028 #[test]
1029 fn osc_aborted_by_sub_before_terminator() {
1030 let mut s = Vec::new();
1031 s.extend_from_slice(b"\x1b]52;c;YWJjZA==");
1032 s.push(SUB); s.extend_from_slice(b"\x1b\\"); let ev = collect_debug(&s);
1036 assert!(ev.iter().any(|e| e.starts_with("OscStart")), "{ev:#?}");
1040 assert!(!ev.iter().any(|e| e.starts_with("OscData")), "{ev:#?}");
1041 assert!(!ev.iter().any(|e| e.starts_with("OscEnd")), "{ev:#?}");
1042 }
1043
1044 fn collect_debug(input: &[u8]) -> Vec<String> {
1046 let mut out = Vec::new();
1047 let mut p = VTPushParser::new();
1048 p.feed_with(input, &mut |ev| out.push(format!("{:?}", ev)));
1049 out
1050 }
1051
1052 #[test]
1053 fn dcs_aborted_by_can_before_body() {
1054 let mut s = Vec::new();
1056 s.extend_from_slice(b"\x1bPq"); s.push(CAN);
1058 s.extend_from_slice(b"IGNORED\x1b\\"); let ev = collect_debug(&s);
1061
1062 assert_eq!(ev.len(), 4, "{ev:#?}");
1063 assert_eq!(ev[0], "DcsStart(, '', q)");
1064 assert_eq!(ev[1], "DcsCancel");
1065 assert_eq!(ev[2], "Raw('IGNORED')");
1066 assert_eq!(ev[3], "Esc('', \\)");
1067 }
1068
1069 #[test]
1070 fn dcs_aborted_by_can_mid_body() {
1071 let mut s = Vec::new();
1073 s.extend_from_slice(b"\x1bPqABC");
1074 s.push(CAN);
1075 s.extend_from_slice(b"MORE\x1b\\"); let ev = collect_debug(&s);
1078
1079 assert_eq!(ev.len(), 4, "{ev:#?}");
1080 assert_eq!(ev[0], "DcsStart(, '', q)");
1081 assert_eq!(ev[1], "DcsCancel");
1082 assert_eq!(ev[2], "Raw('MORE')");
1083 assert_eq!(ev[3], "Esc('', \\)");
1084 }
1085
1086 #[test]
1089 fn spa_aborted_by_can_is_ignored() {
1090 let mut s = Vec::new();
1092 s.extend_from_slice(b"\x1b_hello");
1093 s.push(CAN);
1094 s.extend_from_slice(b"world\x1b\\");
1095
1096 let ev = collect_debug(&s);
1097 assert_eq!(ev.len(), 2, "{ev:#?}");
1098 assert_eq!(ev[0], "Raw('world')");
1099 assert_eq!(ev[1], "Esc('', \\)");
1100 }
1101
1102 #[test]
1103 fn spa_sub_aborts_too() {
1104 let mut s = Vec::new();
1105 s.extend_from_slice(b"\x1bXhello");
1106 s.push(SUB);
1107 s.extend_from_slice(b"world\x1b\\");
1108 let ev = collect_debug(&s);
1109 assert_eq!(ev.len(), 2, "{ev:#?}");
1110 assert_eq!(ev[0], "Raw('world')");
1111 assert_eq!(ev[1], "Esc('', \\)");
1112 }
1113
1114 #[test]
1117 fn can_in_ground_is_c0() {
1118 let mut s = Vec::new();
1119 s.extend_from_slice(b"abc");
1120 s.push(CAN);
1121 s.extend_from_slice(b"def");
1122 let ev = collect_debug(&s);
1123 assert_eq!(ev.len(), 3, "{ev:#?}");
1125 assert_eq!(ev[0], "Raw('abc')");
1126 assert_eq!(ev[1], "C0(18)");
1127 assert_eq!(ev[2], "Raw('def')");
1128 }
1129}