1#![forbid(unsafe_code)]
2
3use crate::event::{
27 ClipboardEvent, ClipboardSource, Event, KeyCode, KeyEvent, KeyEventKind, Modifiers,
28 MouseButton, MouseEvent, MouseEventKind, PasteEvent,
29};
30
31#[cfg(feature = "tracing")]
33use crate::logging::{debug, debug_span, trace};
34#[cfg(not(feature = "tracing"))]
35use crate::{debug, debug_span, trace};
36
37const MAX_CSI_LEN: usize = 256;
39
40const MAX_OSC_LEN: usize = 102_400;
42
43const MAX_PASTE_LEN: usize = 1024 * 1024; const MAX_EVENT_RESERVE_HINT: usize = 8 * 1024 + 1;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
53enum ParserState {
54 #[default]
56 Ground,
57 Escape,
59 Csi,
61 CsiParam,
63 CsiIgnore,
65 Ss3,
67 Osc,
69 OscContent,
71 OscEscape,
73 OscIgnore,
75 DcsIgnore,
81 DcsEscape,
84 Utf8 {
86 collected: u8,
88 expected: u8,
90 },
91 MouseX10 { collected: u8, buffer: [u8; 3] },
93}
94
95#[derive(Debug)]
105pub struct InputParser {
106 state: ParserState,
108 buffer: Vec<u8>,
110 paste_buffer: Vec<u8>,
112 utf8_buffer: [u8; 4],
114 in_paste: bool,
116 pending_event: Option<Event>,
118 expect_x10_mouse: bool,
127 allow_legacy_mouse: bool,
134}
135
136impl Default for InputParser {
137 fn default() -> Self {
138 Self::new()
139 }
140}
141
142impl InputParser {
143 #[inline]
144 fn event_reserve_hint(input_len: usize) -> usize {
145 input_len.saturating_add(1).min(MAX_EVENT_RESERVE_HINT)
146 }
147
148 #[must_use]
150 pub fn new() -> Self {
151 Self {
152 state: ParserState::Ground,
153 buffer: Vec::with_capacity(64),
154 paste_buffer: Vec::new(),
155 utf8_buffer: [0; 4],
156 in_paste: false,
157 pending_event: None,
158 expect_x10_mouse: false,
159 allow_legacy_mouse: false,
160 }
161 }
162
163 pub fn set_expect_x10_mouse(&mut self, enabled: bool) {
168 self.expect_x10_mouse = enabled;
169 }
170
171 pub fn set_allow_legacy_mouse(&mut self, enabled: bool) {
178 self.allow_legacy_mouse = enabled;
179 }
180
181 #[must_use]
184 pub const fn has_pending_timeout_state(&self) -> bool {
185 matches!(self.state, ParserState::Escape | ParserState::Utf8 { .. })
186 }
187
188 pub fn timeout(&mut self) -> Option<Event> {
193 match self.state {
194 ParserState::Escape => {
195 self.state = ParserState::Ground;
196 Some(Event::Key(KeyEvent::new(KeyCode::Escape)))
197 }
198 ParserState::Utf8 { .. } => {
199 self.state = ParserState::Ground;
201 self.utf8_buffer = [0; 4];
202 Some(Event::Key(KeyEvent::new(KeyCode::Char(
203 std::char::REPLACEMENT_CHARACTER,
204 ))))
205 }
206 _ => None,
207 }
208 }
209
210 pub fn parse(&mut self, input: &[u8]) -> Vec<Event> {
212 let mut events = Vec::with_capacity(Self::event_reserve_hint(input.len()));
213 self.parse_with(input, |event| events.push(event));
214 events
215 }
216
217 pub fn parse_with<F>(&mut self, input: &[u8], mut emit: F)
219 where
220 F: FnMut(Event),
221 {
222 let span = debug_span!("event.normalize", raw_byte_count = input.len());
223 let _guard = span.enter();
224 trace!("raw input bytes: {} bytes", input.len());
225
226 for &byte in input {
227 if let Some(event) = self.process_byte(byte) {
228 debug!(event_type = event.event_type_label(), "normalized event");
229 emit(event);
230 }
231 if let Some(pending) = self.pending_event.take() {
232 debug!(event_type = pending.event_type_label(), "normalized event");
233 emit(pending);
234 }
235 }
236 }
237
238 pub fn parse_into(&mut self, input: &[u8], events: &mut Vec<Event>) {
243 let needed = Self::event_reserve_hint(input.len());
244 let available = events.capacity().saturating_sub(events.len());
245 if available < needed {
246 events.reserve(needed - available);
247 }
248 self.parse_with(input, |event| events.push(event));
249 }
250
251 fn process_byte(&mut self, byte: u8) -> Option<Event> {
253 if self.in_paste {
255 return self.process_paste_byte(byte);
256 }
257
258 match self.state {
259 ParserState::Ground => self.process_ground(byte),
260 ParserState::Escape => self.process_escape(byte),
261 ParserState::Csi => self.process_csi(byte),
262 ParserState::CsiParam => self.process_csi_param(byte),
263 ParserState::CsiIgnore => self.process_csi_ignore(byte),
264 ParserState::Ss3 => self.process_ss3(byte),
265 ParserState::Osc => self.process_osc(byte),
266 ParserState::OscContent => self.process_osc_content(byte),
267 ParserState::OscEscape => self.process_osc_escape(byte),
268 ParserState::OscIgnore => self.process_osc_ignore(byte),
269 ParserState::DcsIgnore => self.process_dcs_ignore(byte),
270 ParserState::DcsEscape => self.process_dcs_escape(byte),
271 ParserState::Utf8 {
272 collected,
273 expected,
274 } => self.process_utf8(byte, collected, expected),
275 ParserState::MouseX10 { .. } => self.process_mouse_x10(byte),
276 }
277 }
278
279 fn process_ground(&mut self, byte: u8) -> Option<Event> {
281 match byte {
282 0x1B => {
284 self.state = ParserState::Escape;
285 None
286 }
287 0x9B => {
289 self.state = ParserState::Csi;
290 self.buffer.clear();
291 None
292 }
293 0x8F => {
295 self.state = ParserState::Ss3;
296 None
297 }
298 0x9D => {
300 self.state = ParserState::Osc;
301 self.buffer.clear();
302 None
303 }
304 0x00 => Some(Event::Key(KeyEvent::new(KeyCode::Null))),
306 0x08 => Some(Event::Key(KeyEvent::new(KeyCode::Backspace))),
308 0x09 => Some(Event::Key(KeyEvent::new(KeyCode::Tab))),
310 0x0D => Some(Event::Key(KeyEvent::new(KeyCode::Enter))),
312 0x01..=0x07 | 0x0A..=0x0C | 0x0E..=0x1A => {
314 let c = (byte + b'a' - 1) as char;
315 Some(Event::Key(
316 KeyEvent::new(KeyCode::Char(c)).with_modifiers(Modifiers::CTRL),
317 ))
318 }
319 0x1C => Some(Event::Key(
321 KeyEvent::new(KeyCode::Char('\\')).with_modifiers(Modifiers::CTRL),
322 )),
323 0x1D => Some(Event::Key(
324 KeyEvent::new(KeyCode::Char(']')).with_modifiers(Modifiers::CTRL),
325 )),
326 0x1E => Some(Event::Key(
327 KeyEvent::new(KeyCode::Char('^')).with_modifiers(Modifiers::CTRL),
328 )),
329 0x1F => Some(Event::Key(
330 KeyEvent::new(KeyCode::Char('_')).with_modifiers(Modifiers::CTRL),
331 )),
332 0x7F => Some(Event::Key(KeyEvent::new(KeyCode::Backspace))),
334 0x20..=0x7E => Some(Event::Key(KeyEvent::new(KeyCode::Char(byte as char)))),
336 0xC2..=0xDF => {
338 self.utf8_buffer[0] = byte;
339 self.state = ParserState::Utf8 {
340 collected: 1,
341 expected: 2,
342 };
343 None
344 }
345 0xE0..=0xEF => {
346 self.utf8_buffer[0] = byte;
347 self.state = ParserState::Utf8 {
348 collected: 1,
349 expected: 3,
350 };
351 None
352 }
353 0xF0..=0xF4 => {
354 self.utf8_buffer[0] = byte;
355 self.state = ParserState::Utf8 {
356 collected: 1,
357 expected: 4,
358 };
359 None
360 }
361 0xC0..=0xC1 | 0xF5..=0xFF => Some(Event::Key(KeyEvent::new(KeyCode::Char(
363 std::char::REPLACEMENT_CHARACTER,
364 )))),
365 _ => None,
367 }
368 }
369
370 fn process_escape(&mut self, byte: u8) -> Option<Event> {
372 match byte {
373 b'[' => {
375 self.state = ParserState::Csi;
376 self.buffer.clear();
377 None
378 }
379 b'O' => {
381 self.state = ParserState::Ss3;
382 None
383 }
384 b']' => {
386 self.state = ParserState::Osc;
387 self.buffer.clear();
388 None
389 }
390 b'P' => {
408 self.state = ParserState::DcsIgnore;
409 self.buffer.clear();
410 None
411 }
412 0x1B => {
415 self.state = ParserState::Ground;
416 Some(Event::Key(
417 KeyEvent::new(KeyCode::Escape).with_modifiers(Modifiers::ALT),
418 ))
419 }
420 0x00..=0x1F => {
422 self.state = ParserState::Ground;
423 if let Some(mut event) = self.process_ground(byte) {
426 if let Event::Key(ref mut key) = event {
427 key.modifiers |= Modifiers::ALT;
428 }
429 Some(event)
430 } else {
431 None
432 }
433 }
434 0x20..=0x7E => {
436 self.state = ParserState::Ground;
437 Some(Event::Key(
438 KeyEvent::new(KeyCode::Char(byte as char)).with_modifiers(Modifiers::ALT),
439 ))
440 }
441 0x7F => {
443 self.state = ParserState::Ground;
444 Some(Event::Key(
445 KeyEvent::new(KeyCode::Backspace).with_modifiers(Modifiers::ALT),
446 ))
447 }
448 _ => {
450 self.state = ParserState::Ground;
451 None
452 }
453 }
454 }
455
456 fn process_csi(&mut self, byte: u8) -> Option<Event> {
458 if byte == 0x1B {
460 self.state = ParserState::Escape;
461 self.buffer.clear();
462 return None;
463 }
464
465 self.buffer.push(byte);
466
467 match byte {
468 0x20..=0x3F => {
470 self.state = ParserState::CsiParam;
471 None
472 }
473 0x40..=0x7E => {
475 if self.expect_x10_mouse && byte == b'M' && self.buffer.len() == 1 {
479 self.state = ParserState::MouseX10 {
480 collected: 0,
481 buffer: [0; 3],
482 };
483 self.buffer.clear();
484 return None;
485 }
486
487 self.state = ParserState::Ground;
488 self.parse_csi_sequence()
489 }
490 _ => {
492 self.state = ParserState::Ground;
493 self.buffer.clear();
494 None
495 }
496 }
497 }
498
499 fn process_csi_param(&mut self, byte: u8) -> Option<Event> {
501 if byte == 0x1B {
503 self.state = ParserState::Escape;
504 self.buffer.clear();
505 return None;
506 }
507
508 if self.buffer.len() >= MAX_CSI_LEN {
510 self.state = ParserState::CsiIgnore;
511 self.buffer.clear();
512 return None;
513 }
514
515 self.buffer.push(byte);
516
517 match byte {
518 0x20..=0x3F => None,
520 0x40..=0x7E => {
522 self.state = ParserState::Ground;
523 self.parse_csi_sequence()
524 }
525 _ => {
527 self.state = ParserState::Ground;
528 self.buffer.clear();
529 self.process_ground(byte)
530 }
531 }
532 }
533
534 fn process_csi_ignore(&mut self, byte: u8) -> Option<Event> {
536 if byte == 0x1B {
538 self.state = ParserState::Escape;
539 return None;
540 }
541
542 if (0x40..=0x7E).contains(&byte) {
544 self.state = ParserState::Ground;
545 None
546 } else if (0x20..=0x3F).contains(&byte) {
547 None
549 } else {
550 self.state = ParserState::Ground;
552 self.process_ground(byte)
553 }
554 }
555
556 fn parse_csi_sequence(&mut self) -> Option<Event> {
558 let seq = std::mem::take(&mut self.buffer);
559 if seq.is_empty() {
560 return None;
561 }
562
563 let final_byte = *seq.last()?;
564 let params = &seq[..seq.len() - 1];
565
566 match (params, final_byte) {
568 ([], b'I') => return Some(Event::Focus(true)),
570 ([], b'O') => return Some(Event::Focus(false)),
571
572 (b"200", b'~') => {
574 self.in_paste = true;
575 self.paste_buffer.clear();
576 self.buffer.clear(); return None;
578 }
579 (b"201", b'~') => {
580 self.in_paste = false;
581 let content = String::from_utf8_lossy(&self.paste_buffer).into_owned();
582 self.paste_buffer.clear();
583 return Some(Event::Paste(PasteEvent::bracketed(content)));
584 }
585
586 _ if params.starts_with(b"<") && (final_byte == b'M' || final_byte == b'm') => {
588 return self.parse_sgr_mouse(params, final_byte);
589 }
590 _ if (self.allow_legacy_mouse || self.expect_x10_mouse) && final_byte == b'M' => {
597 if let Some(event) = self.parse_legacy_mouse(params) {
598 return Some(event);
599 }
600 }
601
602 _ => {}
603 }
604
605 match final_byte {
607 b'A' => Some(Event::Key(self.key_with_modifiers(KeyCode::Up, params))),
608 b'B' => Some(Event::Key(self.key_with_modifiers(KeyCode::Down, params))),
609 b'C' => Some(Event::Key(self.key_with_modifiers(KeyCode::Right, params))),
610 b'D' => Some(Event::Key(self.key_with_modifiers(KeyCode::Left, params))),
611 b'H' => Some(Event::Key(self.key_with_modifiers(KeyCode::Home, params))),
612 b'F' => Some(Event::Key(self.key_with_modifiers(KeyCode::End, params))),
613 b'P' => Some(Event::Key(self.key_with_modifiers(KeyCode::F(1), params))),
614 b'Q' => Some(Event::Key(self.key_with_modifiers(KeyCode::F(2), params))),
615 b'R' => Some(Event::Key(self.key_with_modifiers(KeyCode::F(3), params))),
616 b'S' => Some(Event::Key(self.key_with_modifiers(KeyCode::F(4), params))),
617 b'Z' => Some(Event::Key(
618 self.key_with_modifiers(KeyCode::BackTab, params),
619 )),
620 b'~' => self.parse_csi_tilde(params),
621 b'u' => self.parse_kitty_keyboard(params),
622 _ => None,
623 }
624 }
625
626 fn parse_csi_tilde(&self, params: &[u8]) -> Option<Event> {
628 let num = self.parse_first_param(params)?;
629 let mods = self.parse_modifier_param(params);
630
631 let code = match num {
632 1 => KeyCode::Home,
633 2 => KeyCode::Insert,
634 3 => KeyCode::Delete,
635 4 => KeyCode::End,
636 5 => KeyCode::PageUp,
637 6 => KeyCode::PageDown,
638 15 => KeyCode::F(5),
639 17 => KeyCode::F(6),
640 18 => KeyCode::F(7),
641 19 => KeyCode::F(8),
642 20 => KeyCode::F(9),
643 21 => KeyCode::F(10),
644 23 => KeyCode::F(11),
645 24 => KeyCode::F(12),
646 _ => return None,
647 };
648
649 Some(Event::Key(KeyEvent::new(code).with_modifiers(mods)))
650 }
651
652 fn parse_first_param(&self, params: &[u8]) -> Option<u32> {
654 let s = std::str::from_utf8(params).ok()?;
655 let first = s.split(';').next()?;
656 first.parse().ok()
657 }
658
659 fn parse_modifier_param(&self, params: &[u8]) -> Modifiers {
661 let s = match std::str::from_utf8(params) {
662 Ok(s) => s,
663 Err(_) => return Modifiers::NONE,
664 };
665
666 let modifier_value: u32 = s
667 .split(';')
668 .nth(1)
669 .and_then(|s| s.parse().ok())
670 .unwrap_or(1);
671
672 Self::modifiers_from_xterm(modifier_value)
673 }
674
675 fn parse_kitty_keyboard(&self, params: &[u8]) -> Option<Event> {
679 let s = std::str::from_utf8(params).ok()?;
680 if s.is_empty() {
681 return None;
682 }
683
684 let mut parts = s.split(';');
685 let key_part = parts.next().unwrap_or("");
686 let key_code_str = key_part.split(':').next().unwrap_or("");
687 let key_code: u32 = key_code_str.parse().ok()?;
688
689 let mod_part = parts.next().unwrap_or("");
690 let (modifiers, kind) = Self::kitty_modifiers_and_kind(mod_part);
691
692 let code = Self::kitty_keycode_to_keycode(key_code)?;
693 Some(Event::Key(
694 KeyEvent::new(code)
695 .with_modifiers(modifiers)
696 .with_kind(kind),
697 ))
698 }
699
700 fn kitty_modifiers_and_kind(mod_part: &str) -> (Modifiers, KeyEventKind) {
701 if mod_part.is_empty() {
702 return (Modifiers::NONE, KeyEventKind::Press);
703 }
704
705 let mut parts = mod_part.split(':');
706 let mod_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
707 let kind_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
708
709 let modifiers = Self::modifiers_from_xterm(mod_value);
710 let kind = match kind_value {
711 2 => KeyEventKind::Repeat,
712 3 => KeyEventKind::Release,
713 _ => KeyEventKind::Press,
714 };
715
716 (modifiers, kind)
717 }
718
719 fn kitty_keycode_to_keycode(key_code: u32) -> Option<KeyCode> {
720 match key_code {
721 9 => Some(KeyCode::Tab),
723 13 => Some(KeyCode::Enter),
724 27 => Some(KeyCode::Escape),
725 8 | 127 => Some(KeyCode::Backspace),
726 57_344 => Some(KeyCode::Escape),
728 57_345 => Some(KeyCode::Enter),
729 57_346 => Some(KeyCode::Tab),
730 57_347 => Some(KeyCode::Backspace),
731 57_348 => Some(KeyCode::Insert),
732 57_349 => Some(KeyCode::Delete),
733 57_350 => Some(KeyCode::Left),
734 57_351 => Some(KeyCode::Right),
735 57_352 => Some(KeyCode::Up),
736 57_353 => Some(KeyCode::Down),
737 57_354 => Some(KeyCode::PageUp),
738 57_355 => Some(KeyCode::PageDown),
739 57_356 => Some(KeyCode::Home),
740 57_357 => Some(KeyCode::End),
741 57_364..=57_387 => {
743 let f_num = key_code - 57_364 + 1;
746 debug_assert!(f_num <= 24, "F-key number {f_num} exceeds F24");
747 Some(KeyCode::F(f_num as u8))
748 }
749 57_358..=57_363 | 57_388..=63_743 => None,
751 _ => char::from_u32(key_code).map(KeyCode::Char),
753 }
754 }
755
756 fn modifiers_from_xterm(value: u32) -> Modifiers {
757 let bits = value.saturating_sub(1);
760 let mut mods = Modifiers::NONE;
761 if bits & 1 != 0 {
762 mods |= Modifiers::SHIFT;
763 }
764 if bits & 2 != 0 {
765 mods |= Modifiers::ALT;
766 }
767 if bits & 4 != 0 {
768 mods |= Modifiers::CTRL;
769 }
770 if bits & 8 != 0 {
771 mods |= Modifiers::SUPER;
772 }
773 mods
774 }
775
776 fn key_with_modifiers(&self, code: KeyCode, params: &[u8]) -> KeyEvent {
778 KeyEvent::new(code).with_modifiers(self.parse_modifier_param(params))
779 }
780
781 fn parse_sgr_mouse(&self, params: &[u8], final_byte: u8) -> Option<Event> {
783 let params = ¶ms[1..];
786 let s = std::str::from_utf8(params).ok()?;
787 let mut parts = s.split(';');
788
789 let button_code_u32 = Self::parse_u32_prefix(parts.next()?)?;
792 let button_code = button_code_u32.min(u16::MAX as u32) as u16;
793 let x_raw = Self::parse_i32_prefix(parts.next()?)?;
794 let y_raw = Self::parse_i32_prefix(parts.next()?)?;
795
796 let (button, mods) = self.decode_mouse_button(button_code);
798
799 let kind = if final_byte == b'M' {
800 if button_code & 64 != 0 {
801 match button_code & 3 {
804 0 => MouseEventKind::ScrollUp,
805 1 => MouseEventKind::ScrollDown,
806 2 => MouseEventKind::ScrollLeft,
807 _ => MouseEventKind::ScrollRight,
808 }
809 } else if button_code & 32 != 0 {
810 if button_code & 3 == 3 {
813 MouseEventKind::Moved
814 } else {
815 MouseEventKind::Drag(button)
816 }
817 } else if (button_code & 3) == 3 {
818 MouseEventKind::Up(MouseButton::Left)
821 } else {
822 MouseEventKind::Down(button)
823 }
824 } else {
825 MouseEventKind::Up(button)
826 };
827
828 Some(Event::Mouse(MouseEvent {
829 kind,
830 x: Self::normalize_sgr_coord(x_raw),
831 y: Self::normalize_sgr_coord(y_raw),
832 modifiers: mods,
833 }))
834 }
835
836 #[inline]
837 fn parse_u32_prefix(token: &str) -> Option<u32> {
838 let bytes = token.as_bytes();
839 let digits = bytes.iter().take_while(|b| b.is_ascii_digit()).count();
840 if digits == 0 {
841 return None;
842 }
843 token[..digits].parse().ok()
844 }
845
846 #[inline]
847 fn parse_i32_prefix(token: &str) -> Option<i32> {
848 let bytes = token.as_bytes();
849 if bytes.is_empty() {
850 return None;
851 }
852 let start = if bytes[0] == b'-' || bytes[0] == b'+' {
853 1
854 } else {
855 0
856 };
857 let digits = bytes[start..]
858 .iter()
859 .take_while(|b| b.is_ascii_digit())
860 .count();
861 if digits == 0 {
862 return None;
863 }
864 token[..start + digits].parse().ok()
865 }
866
867 #[inline]
868 fn normalize_sgr_coord(raw: i32) -> u16 {
869 if raw <= 1 {
870 return 0;
871 }
872 let zero_indexed = raw - 1;
873 zero_indexed.min(i32::from(u16::MAX)) as u16
874 }
875
876 fn parse_legacy_mouse(&self, params: &[u8]) -> Option<Event> {
881 if params.is_empty() || params.starts_with(b"<") {
882 return None;
883 }
884
885 let s = std::str::from_utf8(params).ok()?;
886 let mut parts = s.split(';');
887 let button_code: u16 = parts.next()?.parse().ok()?;
888 let x: u16 = parts.next()?.parse().ok()?;
889 let y: u16 = parts.next()?.parse().ok()?;
890 if parts.next().is_some() {
892 return None;
893 }
894
895 let (button, mods) = self.decode_mouse_button(button_code);
896 let kind = if button_code & 64 != 0 {
897 match button_code & 3 {
899 0 => MouseEventKind::ScrollUp,
900 1 => MouseEventKind::ScrollDown,
901 2 => MouseEventKind::ScrollLeft,
902 _ => MouseEventKind::ScrollRight,
903 }
904 } else if button_code & 32 != 0 {
905 if button_code & 3 == 3 {
907 MouseEventKind::Moved
908 } else {
909 MouseEventKind::Drag(button)
910 }
911 } else if (button_code & 3) == 3 {
912 MouseEventKind::Up(MouseButton::Left)
914 } else {
915 MouseEventKind::Down(button)
916 };
917
918 Some(Event::Mouse(MouseEvent {
919 kind,
920 x: x.saturating_sub(1),
921 y: y.saturating_sub(1),
922 modifiers: mods,
923 }))
924 }
925
926 fn decode_mouse_button(&self, code: u16) -> (MouseButton, Modifiers) {
928 let button = match code & 0b11 {
929 0 => MouseButton::Left,
930 1 => MouseButton::Middle,
931 2 => MouseButton::Right,
932 _ => MouseButton::Left,
933 };
934
935 let mut mods = Modifiers::NONE;
936 if code & 4 != 0 {
937 mods |= Modifiers::SHIFT;
938 }
939 if code & 8 != 0 {
940 mods |= Modifiers::ALT;
941 }
942 if code & 16 != 0 {
943 mods |= Modifiers::CTRL;
944 }
945
946 (button, mods)
947 }
948
949 fn process_ss3(&mut self, byte: u8) -> Option<Event> {
951 if byte == 0x1B {
953 self.state = ParserState::Escape;
954 return None;
955 }
956
957 self.state = ParserState::Ground;
958
959 let code = match byte {
960 b'P' => KeyCode::F(1),
961 b'Q' => KeyCode::F(2),
962 b'R' => KeyCode::F(3),
963 b'S' => KeyCode::F(4),
964 b'A' => KeyCode::Up,
965 b'B' => KeyCode::Down,
966 b'C' => KeyCode::Right,
967 b'D' => KeyCode::Left,
968 b'H' => KeyCode::Home,
969 b'F' => KeyCode::End,
970 _ => return None,
971 };
972
973 Some(Event::Key(KeyEvent::new(code)))
974 }
975
976 fn process_osc(&mut self, byte: u8) -> Option<Event> {
978 if byte == 0x1B {
980 self.state = ParserState::OscEscape;
981 return None;
982 }
983
984 self.buffer.push(byte);
985
986 match byte {
987 0x07 => {
989 self.state = ParserState::Ground;
990 self.parse_osc_sequence()
991 }
992 _ => {
994 self.state = ParserState::OscContent;
995 None
996 }
997 }
998 }
999
1000 fn process_osc_content(&mut self, byte: u8) -> Option<Event> {
1002 if byte == 0x1B {
1004 self.state = ParserState::OscEscape;
1005 return None;
1006 }
1007
1008 if byte < 0x20 && byte != 0x07 {
1010 self.state = ParserState::Ground;
1011 self.buffer.clear();
1012 return self.process_ground(byte);
1013 }
1014
1015 if self.buffer.len() >= MAX_OSC_LEN {
1017 self.state = ParserState::OscIgnore;
1018 self.buffer.clear();
1019 return None;
1020 }
1021
1022 match byte {
1023 0x07 => {
1025 self.state = ParserState::Ground;
1026 self.parse_osc_sequence()
1027 }
1028 _ => {
1030 self.buffer.push(byte);
1031 None
1032 }
1033 }
1034 }
1035
1036 fn process_osc_escape(&mut self, byte: u8) -> Option<Event> {
1038 if byte == b'\\' {
1039 self.state = ParserState::Ground;
1041 self.parse_osc_sequence()
1042 } else if byte == 0x1B {
1043 self.state = ParserState::Escape;
1045 self.buffer.clear();
1046 None
1047 } else {
1048 self.buffer.clear();
1054 self.state = ParserState::Escape;
1055 self.process_escape(byte)
1056 }
1057 }
1058
1059 fn process_osc_ignore(&mut self, byte: u8) -> Option<Event> {
1061 match byte {
1062 0x07 => {
1064 self.state = ParserState::Ground;
1065 None
1066 }
1067 0x1B => {
1069 self.state = ParserState::OscEscape;
1070 None
1071 }
1072 _ if byte < 0x20 => {
1074 self.state = ParserState::Ground;
1075 self.process_ground(byte)
1076 }
1077 _ => None,
1079 }
1080 }
1081
1082 fn process_dcs_ignore(&mut self, byte: u8) -> Option<Event> {
1089 match byte {
1090 0x07 => {
1092 self.state = ParserState::Ground;
1093 None
1094 }
1095 0x1B => {
1097 self.state = ParserState::DcsEscape;
1098 None
1099 }
1100 _ if byte < 0x20 => {
1103 self.state = ParserState::Ground;
1104 self.process_ground(byte)
1105 }
1106 _ => None,
1108 }
1109 }
1110
1111 fn process_dcs_escape(&mut self, byte: u8) -> Option<Event> {
1113 if byte == b'\\' {
1114 self.state = ParserState::Ground;
1116 None
1117 } else if byte == 0x1B {
1118 self.state = ParserState::Escape;
1120 None
1121 } else {
1122 self.state = ParserState::Escape;
1125 self.process_escape(byte)
1126 }
1127 }
1128
1129 fn parse_osc_sequence(&mut self) -> Option<Event> {
1131 let seq = std::mem::take(&mut self.buffer);
1132
1133 if seq.starts_with(b"52;") {
1135 return self.parse_osc52_clipboard(&seq);
1136 }
1137
1138 None
1140 }
1141
1142 fn parse_osc52_clipboard(&self, seq: &[u8]) -> Option<Event> {
1144 let content = &seq[3..]; if content.is_empty() {
1147 return None;
1148 }
1149
1150 let source = ClipboardSource::Osc52;
1153
1154 let base64_start = content.iter().position(|&b| b == b';').map(|i| i + 1)?;
1156 let base64_data = &content[base64_start..];
1157
1158 let decoded = self.decode_base64(base64_data)?;
1160
1161 Some(Event::Clipboard(ClipboardEvent::new(
1162 String::from_utf8_lossy(&decoded).into_owned(),
1163 source,
1164 )))
1165 }
1166
1167 fn decode_base64(&self, input: &[u8]) -> Option<Vec<u8>> {
1169 const DECODE_TABLE: [i8; 256] = {
1170 let mut table = [-1i8; 256];
1171 let mut i = 0u8;
1172 while i < 26 {
1173 table[(b'A' + i) as usize] = i as i8;
1174 table[(b'a' + i) as usize] = (i + 26) as i8;
1175 i += 1;
1176 }
1177 let mut i = 0u8;
1178 while i < 10 {
1179 table[(b'0' + i) as usize] = (i + 52) as i8;
1180 i += 1;
1181 }
1182 table[b'+' as usize] = 62;
1183 table[b'/' as usize] = 63;
1184 table
1185 };
1186
1187 let mut output = Vec::with_capacity(input.len() * 3 / 4);
1188 let mut buffer = 0u32;
1189 let mut bits = 0u8;
1190
1191 for &byte in input {
1192 if byte == b'=' {
1193 break;
1194 }
1195 let value = DECODE_TABLE[byte as usize];
1196 if value < 0 {
1197 continue; }
1199 buffer = (buffer << 6) | (value as u32);
1200 bits += 6;
1201 if bits >= 8 {
1202 bits -= 8;
1203 output.push((buffer >> bits) as u8);
1204 buffer &= (1 << bits) - 1;
1205 }
1206 }
1207
1208 Some(output)
1209 }
1210
1211 fn process_utf8(&mut self, byte: u8, collected: u8, expected: u8) -> Option<Event> {
1213 if (byte & 0xC0) != 0x80 {
1215 self.state = ParserState::Ground;
1218
1219 self.pending_event = self.process_ground(byte);
1221
1222 return Some(Event::Key(KeyEvent::new(KeyCode::Char(
1223 std::char::REPLACEMENT_CHARACTER,
1224 ))));
1225 }
1226
1227 self.utf8_buffer[collected as usize] = byte;
1228 let new_collected = collected + 1;
1229
1230 if new_collected == expected {
1231 self.state = ParserState::Ground;
1233 match std::str::from_utf8(&self.utf8_buffer[..expected as usize]) {
1234 Ok(s) => {
1235 let c = s.chars().next()?;
1236 Some(Event::Key(KeyEvent::new(KeyCode::Char(c))))
1237 }
1238 Err(_) => Some(Event::Key(KeyEvent::new(KeyCode::Char(
1239 std::char::REPLACEMENT_CHARACTER,
1240 )))),
1241 }
1242 } else {
1243 self.state = ParserState::Utf8 {
1245 collected: new_collected,
1246 expected,
1247 };
1248 None
1249 }
1250 }
1251
1252 fn process_mouse_x10(&mut self, byte: u8) -> Option<Event> {
1254 if let ParserState::MouseX10 {
1255 ref mut collected,
1256 ref mut buffer,
1257 } = self.state
1258 {
1259 buffer[*collected as usize] = byte;
1260 *collected += 1;
1261
1262 if *collected == 3 {
1263 let buf = *buffer;
1265 self.state = ParserState::Ground;
1266
1267 if buf[0] < 32 || buf[1] < 33 || buf[2] < 33 {
1271 return None;
1272 }
1273 let cb = buf[0].saturating_sub(32) as u16;
1274 let cx = buf[1].saturating_sub(33) as u16; let cy = buf[2].saturating_sub(33) as u16;
1276
1277 let (button, mods) = self.decode_mouse_button(cb);
1278
1279 let kind = if (cb & 3) == 3 {
1285 MouseEventKind::Up(MouseButton::Left)
1287 } else if cb & 64 != 0 {
1288 match cb & 3 {
1290 0 => MouseEventKind::ScrollUp,
1291 1 => MouseEventKind::ScrollDown,
1292 _ => MouseEventKind::ScrollUp,
1294 }
1295 } else {
1296 MouseEventKind::Down(button)
1298 };
1299
1300 return Some(Event::Mouse(MouseEvent {
1301 kind,
1302 x: cx,
1303 y: cy,
1304 modifiers: mods,
1305 }));
1306 }
1307 }
1308 None
1309 }
1310
1311 fn process_paste_byte(&mut self, byte: u8) -> Option<Event> {
1313 const END_SEQ: &[u8] = b"\x1b[201~";
1314
1315 if self.paste_buffer.len() < MAX_PASTE_LEN {
1321 self.paste_buffer.push(byte);
1322
1323 if self.paste_buffer.ends_with(END_SEQ) {
1325 self.in_paste = false;
1326 let content_len = self.paste_buffer.len() - END_SEQ.len();
1328 let content =
1329 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
1330 self.paste_buffer.clear();
1331 return Some(Event::Paste(PasteEvent::bracketed(content)));
1332 }
1333 } else {
1334 self.buffer.push(byte);
1339 if self.buffer.len() > END_SEQ.len() {
1340 self.buffer.remove(0);
1341 }
1342
1343 let mut last_bytes = [0u8; 6];
1350 let tail_len = self.buffer.len();
1351 let paste_len = self.paste_buffer.len();
1352
1353 if tail_len + paste_len >= 6 {
1355 for i in 0..tail_len {
1357 last_bytes[6 - tail_len + i] = self.buffer[i];
1358 }
1359 let remaining = 6 - tail_len;
1361 if remaining > 0 {
1362 let start = paste_len - remaining;
1363 last_bytes[..remaining]
1364 .copy_from_slice(&self.paste_buffer[start..(remaining + start)]);
1365 }
1366
1367 if last_bytes == END_SEQ {
1368 self.in_paste = false;
1369
1370 let content_len = paste_len - remaining;
1375 let content =
1376 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
1377
1378 self.paste_buffer.clear();
1379 self.buffer.clear();
1380
1381 return Some(Event::Paste(PasteEvent::bracketed(content)));
1382 }
1383 }
1384 }
1385
1386 None
1387 }
1388}
1389
1390#[cfg(test)]
1391mod tests {
1392 use super::*;
1393
1394 #[test]
1395 fn csi_ignore_handles_final_bytes() {
1396 let mut parser = InputParser::new();
1397
1398 let mut seq = vec![0x1B, b'['];
1401 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100)); seq.push(b'@'); let events = parser.parse(&seq);
1405 assert_eq!(events.len(), 0);
1406
1407 let events = parser.parse(b"a");
1410 assert_eq!(events.len(), 1, "Subsequent char 'a' was swallowed");
1411 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1412 }
1413
1414 #[test]
1415 fn ascii_characters_parsed() {
1416 let mut parser = InputParser::new();
1417
1418 let events = parser.parse(b"abc");
1419 assert_eq!(events.len(), 3);
1420 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1421 assert!(matches!(events[1], Event::Key(k) if k.code == KeyCode::Char('b')));
1422 assert!(matches!(events[2], Event::Key(k) if k.code == KeyCode::Char('c')));
1423 }
1424
1425 #[test]
1426 fn control_characters() {
1427 let mut parser = InputParser::new();
1428
1429 let events = parser.parse(&[0x01]);
1431 assert_eq!(events.len(), 1);
1432 assert!(matches!(
1433 events[0],
1434 Event::Key(k) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::CTRL)
1435 ));
1436
1437 let events = parser.parse(&[0x7F]);
1439 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace));
1440 }
1441
1442 #[test]
1443 fn arrow_keys() {
1444 let mut parser = InputParser::new();
1445
1446 assert!(matches!(
1447 parser.parse(b"\x1b[A").first(),
1448 Some(Event::Key(k)) if k.code == KeyCode::Up
1449 ));
1450 assert!(matches!(
1451 parser.parse(b"\x1b[B").first(),
1452 Some(Event::Key(k)) if k.code == KeyCode::Down
1453 ));
1454 assert!(matches!(
1455 parser.parse(b"\x1b[C").first(),
1456 Some(Event::Key(k)) if k.code == KeyCode::Right
1457 ));
1458 assert!(matches!(
1459 parser.parse(b"\x1b[D").first(),
1460 Some(Event::Key(k)) if k.code == KeyCode::Left
1461 ));
1462 }
1463
1464 #[test]
1465 fn c1_csi_arrow_keys() {
1466 let mut parser = InputParser::new();
1467
1468 assert!(matches!(
1469 parser.parse(&[0x9B, b'A']).first(),
1470 Some(Event::Key(k)) if k.code == KeyCode::Up
1471 ));
1472 assert!(matches!(
1473 parser.parse(&[0x9B, b'B']).first(),
1474 Some(Event::Key(k)) if k.code == KeyCode::Down
1475 ));
1476 }
1477
1478 #[test]
1479 fn c1_csi_mouse_sgr_protocol() {
1480 let mut parser = InputParser::new();
1481
1482 let events = parser.parse(&[0x9B, b'<', b'0', b';', b'1', b'0', b';', b'2', b'0', b'M']);
1483 assert!(matches!(
1484 events.first(),
1485 Some(Event::Mouse(m)) if m.x == 9 && m.y == 19
1486 ));
1487 }
1488
1489 #[test]
1490 fn function_keys_ss3() {
1491 let mut parser = InputParser::new();
1492
1493 assert!(matches!(
1494 parser.parse(b"\x1bOP").first(),
1495 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1496 ));
1497 assert!(matches!(
1498 parser.parse(b"\x1bOQ").first(),
1499 Some(Event::Key(k)) if k.code == KeyCode::F(2)
1500 ));
1501 assert!(matches!(
1502 parser.parse(b"\x1bOR").first(),
1503 Some(Event::Key(k)) if k.code == KeyCode::F(3)
1504 ));
1505 assert!(matches!(
1506 parser.parse(b"\x1bOS").first(),
1507 Some(Event::Key(k)) if k.code == KeyCode::F(4)
1508 ));
1509 }
1510
1511 #[test]
1512 fn function_keys_csi() {
1513 let mut parser = InputParser::new();
1514
1515 assert!(matches!(
1516 parser.parse(b"\x1b[15~").first(),
1517 Some(Event::Key(k)) if k.code == KeyCode::F(5)
1518 ));
1519 assert!(matches!(
1520 parser.parse(b"\x1b[17~").first(),
1521 Some(Event::Key(k)) if k.code == KeyCode::F(6)
1522 ));
1523 }
1524
1525 #[test]
1526 fn modifiers_in_csi() {
1527 let mut parser = InputParser::new();
1528
1529 let events = parser.parse(b"\x1b[1;2A");
1531 assert!(matches!(
1532 events.first(),
1533 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::SHIFT)
1534 ));
1535
1536 let events = parser.parse(b"\x1b[1;5A");
1538 assert!(matches!(
1539 events.first(),
1540 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::CTRL)
1541 ));
1542 }
1543
1544 #[test]
1545 fn modifiers_in_csi_alt_ctrl() {
1546 let mut parser = InputParser::new();
1547
1548 let events = parser.parse(b"\x1b[1;7A");
1550 assert!(matches!(
1551 events.first(),
1552 Some(Event::Key(k))
1553 if k.code == KeyCode::Up
1554 && k.modifiers.contains(Modifiers::ALT)
1555 && k.modifiers.contains(Modifiers::CTRL)
1556 ));
1557 }
1558
1559 #[test]
1560 fn kitty_keyboard_basic_char() {
1561 let mut parser = InputParser::new();
1562
1563 let events = parser.parse(b"\x1b[97u");
1564 assert!(matches!(
1565 events.first(),
1566 Some(Event::Key(k))
1567 if k.code == KeyCode::Char('a')
1568 && k.modifiers == Modifiers::NONE
1569 && k.kind == KeyEventKind::Press
1570 ));
1571 }
1572
1573 #[test]
1574 fn kitty_keyboard_with_modifiers_and_kind() {
1575 let mut parser = InputParser::new();
1576
1577 let events = parser.parse(b"\x1b[97;5:2u");
1579 assert!(matches!(
1580 events.first(),
1581 Some(Event::Key(k))
1582 if k.code == KeyCode::Char('a')
1583 && k.modifiers.contains(Modifiers::CTRL)
1584 && k.kind == KeyEventKind::Repeat
1585 ));
1586 }
1587
1588 #[test]
1589 fn kitty_keyboard_function_key() {
1590 let mut parser = InputParser::new();
1591
1592 let events = parser.parse(b"\x1b[57364;1u");
1593 assert!(matches!(
1594 events.first(),
1595 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1596 ));
1597 }
1598
1599 #[test]
1600 fn alt_key_escapes() {
1601 let mut parser = InputParser::new();
1602
1603 let events = parser.parse(b"\x1ba");
1604 assert!(matches!(
1605 events.first(),
1606 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::ALT)
1607 ));
1608 }
1609
1610 #[test]
1611 fn alt_backspace() {
1612 let mut parser = InputParser::new();
1613
1614 let events = parser.parse(b"\x1b\x7f");
1615 assert!(matches!(
1616 events.first(),
1617 Some(Event::Key(k))
1618 if k.code == KeyCode::Backspace && k.modifiers.contains(Modifiers::ALT)
1619 ));
1620 }
1621
1622 #[test]
1623 fn escape_escape_resets_state() {
1624 let mut parser = InputParser::new();
1625
1626 let events = parser.parse(b"\x1b\x1b");
1627 assert!(matches!(
1628 events.first(),
1629 Some(Event::Key(k)) if k.code == KeyCode::Escape && k.modifiers.contains(Modifiers::ALT)
1630 ));
1631
1632 let events = parser.parse(b"a");
1633 assert!(matches!(
1634 events.first(),
1635 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers == Modifiers::NONE
1636 ));
1637 }
1638
1639 #[test]
1640 fn focus_events() {
1641 let mut parser = InputParser::new();
1642
1643 assert!(matches!(
1644 parser.parse(b"\x1b[I").first(),
1645 Some(Event::Focus(true))
1646 ));
1647 assert!(matches!(
1648 parser.parse(b"\x1b[O").first(),
1649 Some(Event::Focus(false))
1650 ));
1651 }
1652
1653 #[test]
1654 fn bracketed_paste() {
1655 let mut parser = InputParser::new();
1656
1657 let events = parser.parse(b"\x1b[200~hello world\x1b[201~");
1659 assert_eq!(events.len(), 1);
1660 assert!(matches!(
1661 &events[0],
1662 Event::Paste(p) if p.text == "hello world"
1663 ));
1664 }
1665
1666 #[test]
1667 fn mouse_sgr_protocol() {
1668 let mut parser = InputParser::new();
1669
1670 let events = parser.parse(b"\x1b[<0;10;20M");
1672 assert!(matches!(
1673 events.first(),
1674 Some(Event::Mouse(m)) if m.x == 9 && m.y == 19 ));
1676 }
1677
1678 #[test]
1679 fn mouse_sgr_protocol_with_subparams() {
1680 let mut parser = InputParser::new();
1681
1682 let events = parser.parse(b"\x1b[<0:0;10:0;20:0M");
1684 assert!(matches!(
1685 events.first(),
1686 Some(Event::Mouse(m))
1687 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1688 && m.x == 9
1689 && m.y == 19
1690 ));
1691 }
1692
1693 #[test]
1694 fn mouse_sgr_protocol_large_coords_clamped() {
1695 let mut parser = InputParser::new();
1696
1697 let events = parser.parse(b"\x1b[<0;70000;80000M");
1699 assert!(matches!(
1700 events.first(),
1701 Some(Event::Mouse(m))
1702 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1703 && m.x == u16::MAX
1704 && m.y == u16::MAX
1705 ));
1706 }
1707
1708 #[test]
1709 fn mouse_sgr_protocol_negative_coords_clamped() {
1710 let mut parser = InputParser::new();
1711
1712 let events = parser.parse(b"\x1b[<0;-12;-3M");
1715 assert!(matches!(
1716 events.first(),
1717 Some(Event::Mouse(m))
1718 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1719 && m.x == 0
1720 && m.y == 0
1721 ));
1722 }
1723
1724 #[test]
1725 fn mouse_sgr_modifiers() {
1726 let mut parser = InputParser::new();
1727
1728 let events = parser.parse(b"\x1b[<28;3;4M");
1730 assert!(matches!(
1731 events.first(),
1732 Some(Event::Mouse(m))
1733 if m.modifiers.contains(Modifiers::SHIFT)
1734 && m.modifiers.contains(Modifiers::ALT)
1735 && m.modifiers.contains(Modifiers::CTRL)
1736 ));
1737 }
1738
1739 #[test]
1740 fn mouse_sgr_scroll_up() {
1741 let mut parser = InputParser::new();
1742
1743 let events = parser.parse(b"\x1b[<64;5;5M");
1745 assert!(matches!(
1746 events.first(),
1747 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollUp)
1748 ));
1749 }
1750
1751 #[test]
1752 fn mouse_sgr_scroll_down() {
1753 let mut parser = InputParser::new();
1754
1755 let events = parser.parse(b"\x1b[<65;5;5M");
1757 assert!(matches!(
1758 events.first(),
1759 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollDown)
1760 ));
1761 }
1762
1763 #[test]
1764 fn mouse_sgr_scroll_left() {
1765 let mut parser = InputParser::new();
1766
1767 let events = parser.parse(b"\x1b[<66;5;5M");
1769 assert!(matches!(
1770 events.first(),
1771 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollLeft)
1772 ));
1773 }
1774
1775 #[test]
1776 fn mouse_sgr_scroll_right() {
1777 let mut parser = InputParser::new();
1778
1779 let events = parser.parse(b"\x1b[<67;5;5M");
1781 assert!(matches!(
1782 events.first(),
1783 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollRight)
1784 ));
1785 }
1786
1787 #[test]
1788 fn mouse_sgr_drag_left() {
1789 let mut parser = InputParser::new();
1790
1791 let events = parser.parse(b"\x1b[<32;10;20M");
1793 assert!(matches!(
1794 events.first(),
1795 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Drag(MouseButton::Left))
1796 ));
1797 }
1798
1799 #[test]
1800 fn utf8_characters() {
1801 let mut parser = InputParser::new();
1802
1803 let events = parser.parse(&[0xC3, 0xA9]);
1805 assert!(matches!(
1806 events.first(),
1807 Some(Event::Key(k)) if k.code == KeyCode::Char('é')
1808 ));
1809 }
1810
1811 #[test]
1812 fn invalid_utf8_emits_replacement_then_reprocesses_byte() {
1813 let mut parser = InputParser::new();
1814
1815 let events = parser.parse(&[0xE2, 0x28]);
1817 assert_eq!(events.len(), 2);
1818 assert!(matches!(
1819 events[0],
1820 Event::Key(k) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)
1821 ));
1822 assert!(matches!(
1823 events[1],
1824 Event::Key(k) if k.code == KeyCode::Char('(')
1825 ));
1826 }
1827
1828 #[test]
1829 fn dos_protection_csi() {
1830 let mut parser = InputParser::new();
1831
1832 let mut seq = vec![0x1B, b'['];
1834 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
1835 seq.push(b'A');
1836
1837 let events = parser.parse(&seq);
1840 assert_eq!(
1841 events.len(),
1842 0,
1843 "Oversized CSI sequence should produce no events"
1844 );
1845
1846 let events = parser.parse(b"\x1b[A");
1849 assert!(matches!(
1850 events.first(),
1851 Some(Event::Key(k)) if k.code == KeyCode::Up
1852 ));
1853 }
1854
1855 #[test]
1856 fn incomplete_csi_sequence_emits_no_event() {
1857 let mut parser = InputParser::new();
1858 let events = parser.parse(b"\x1b[");
1859 assert!(events.is_empty());
1860 }
1861
1862 #[test]
1863 fn dos_protection_paste() {
1864 let mut parser = InputParser::new();
1865
1866 parser.parse(b"\x1b[200~");
1868
1869 let content = vec![b'x'; MAX_PASTE_LEN - 100]; parser.parse(&content);
1872
1873 let events = parser.parse(b"\x1b[201~");
1875
1876 assert!(matches!(
1878 events.first(),
1879 Some(Event::Paste(p)) if p.text.len() <= MAX_PASTE_LEN
1880 ));
1881 }
1882
1883 #[test]
1884 fn dos_protection_paste_overflow_terminator() {
1885 let mut parser = InputParser::new();
1886
1887 parser.parse(b"\x1b[200~");
1889
1890 let overflow = 100;
1894 let content = vec![b'a'; MAX_PASTE_LEN + overflow];
1895 parser.parse(&content);
1896
1897 let events = parser.parse(b"\x1b[201~");
1900
1901 assert_eq!(events.len(), 1, "Should emit paste event");
1902 match &events[0] {
1903 Event::Paste(p) => {
1904 assert_eq!(
1907 p.text.len(),
1908 MAX_PASTE_LEN,
1909 "Paste should be capped at MAX_PASTE_LEN bytes"
1910 );
1911 assert!(p.text.chars().all(|c| c == 'a'));
1913 }
1914 _ => unreachable!("Expected Paste event"),
1915 }
1916
1917 let events = parser.parse(b"b");
1919 assert_eq!(events.len(), 1);
1920 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('b')));
1921 }
1922
1923 #[test]
1924 fn no_panic_on_invalid_input() {
1925 let mut parser = InputParser::new();
1926
1927 let garbage = [0xFF, 0xFE, 0x00, 0x1B, 0x1B, 0x1B, b'[', 0xFF, b']', 0x00];
1929
1930 let _ = parser.parse(&garbage);
1932 }
1933
1934 #[test]
1935 fn dos_protection_paste_boundary() {
1936 let mut parser = InputParser::new();
1937 parser.parse(b"\x1b[200~");
1939
1940 let content = vec![b'x'; MAX_PASTE_LEN];
1942 parser.parse(&content);
1943
1944 let events = parser.parse(b"\x1b[201~");
1947
1948 assert!(
1949 !events.is_empty(),
1950 "Parser trapped in paste mode after hitting limit"
1951 );
1952 assert!(matches!(events[0], Event::Paste(_)));
1953 }
1954
1955 #[test]
1958 fn csi_tilde_home() {
1959 let mut parser = InputParser::new();
1960 let events = parser.parse(b"\x1b[1~");
1961 assert!(matches!(
1962 events.first(),
1963 Some(Event::Key(k)) if k.code == KeyCode::Home
1964 ));
1965 }
1966
1967 #[test]
1968 fn csi_tilde_insert() {
1969 let mut parser = InputParser::new();
1970 let events = parser.parse(b"\x1b[2~");
1971 assert!(matches!(
1972 events.first(),
1973 Some(Event::Key(k)) if k.code == KeyCode::Insert
1974 ));
1975 }
1976
1977 #[test]
1978 fn csi_tilde_delete() {
1979 let mut parser = InputParser::new();
1980 let events = parser.parse(b"\x1b[3~");
1981 assert!(matches!(
1982 events.first(),
1983 Some(Event::Key(k)) if k.code == KeyCode::Delete
1984 ));
1985 }
1986
1987 #[test]
1988 fn csi_tilde_end() {
1989 let mut parser = InputParser::new();
1990 let events = parser.parse(b"\x1b[4~");
1991 assert!(matches!(
1992 events.first(),
1993 Some(Event::Key(k)) if k.code == KeyCode::End
1994 ));
1995 }
1996
1997 #[test]
1998 fn csi_tilde_page_up() {
1999 let mut parser = InputParser::new();
2000 let events = parser.parse(b"\x1b[5~");
2001 assert!(matches!(
2002 events.first(),
2003 Some(Event::Key(k)) if k.code == KeyCode::PageUp
2004 ));
2005 }
2006
2007 #[test]
2008 fn csi_tilde_page_down() {
2009 let mut parser = InputParser::new();
2010 let events = parser.parse(b"\x1b[6~");
2011 assert!(matches!(
2012 events.first(),
2013 Some(Event::Key(k)) if k.code == KeyCode::PageDown
2014 ));
2015 }
2016
2017 #[test]
2020 fn csi_home_and_end() {
2021 let mut parser = InputParser::new();
2022 assert!(matches!(
2023 parser.parse(b"\x1b[H").first(),
2024 Some(Event::Key(k)) if k.code == KeyCode::Home
2025 ));
2026 assert!(matches!(
2027 parser.parse(b"\x1b[F").first(),
2028 Some(Event::Key(k)) if k.code == KeyCode::End
2029 ));
2030 }
2031
2032 #[test]
2035 fn ss3_home_and_end() {
2036 let mut parser = InputParser::new();
2037 assert!(matches!(
2038 parser.parse(b"\x1bOH").first(),
2039 Some(Event::Key(k)) if k.code == KeyCode::Home
2040 ));
2041 assert!(matches!(
2042 parser.parse(b"\x1bOF").first(),
2043 Some(Event::Key(k)) if k.code == KeyCode::End
2044 ));
2045 }
2046
2047 #[test]
2050 fn backtab_csi_z() {
2051 let mut parser = InputParser::new();
2052 let events = parser.parse(b"\x1b[Z");
2053 assert!(matches!(
2054 events.first(),
2055 Some(Event::Key(k)) if k.code == KeyCode::BackTab
2056 ));
2057 }
2058
2059 #[test]
2062 fn function_keys_f7_to_f12() {
2063 let mut parser = InputParser::new();
2064 assert!(matches!(
2065 parser.parse(b"\x1b[18~").first(),
2066 Some(Event::Key(k)) if k.code == KeyCode::F(7)
2067 ));
2068 assert!(matches!(
2069 parser.parse(b"\x1b[19~").first(),
2070 Some(Event::Key(k)) if k.code == KeyCode::F(8)
2071 ));
2072 assert!(matches!(
2073 parser.parse(b"\x1b[20~").first(),
2074 Some(Event::Key(k)) if k.code == KeyCode::F(9)
2075 ));
2076 assert!(matches!(
2077 parser.parse(b"\x1b[21~").first(),
2078 Some(Event::Key(k)) if k.code == KeyCode::F(10)
2079 ));
2080 assert!(matches!(
2081 parser.parse(b"\x1b[23~").first(),
2082 Some(Event::Key(k)) if k.code == KeyCode::F(11)
2083 ));
2084 assert!(matches!(
2085 parser.parse(b"\x1b[24~").first(),
2086 Some(Event::Key(k)) if k.code == KeyCode::F(12)
2087 ));
2088 }
2089
2090 #[test]
2093 fn ctrl_home_and_alt_end() {
2094 let mut parser = InputParser::new();
2095
2096 let events = parser.parse(b"\x1b[1;5H");
2098 assert!(matches!(
2099 events.first(),
2100 Some(Event::Key(k)) if k.code == KeyCode::Home && k.modifiers.contains(Modifiers::CTRL)
2101 ));
2102
2103 let events = parser.parse(b"\x1b[1;3F");
2105 assert!(matches!(
2106 events.first(),
2107 Some(Event::Key(k)) if k.code == KeyCode::End && k.modifiers.contains(Modifiers::ALT)
2108 ));
2109 }
2110
2111 #[test]
2112 fn shift_ctrl_arrow() {
2113 let mut parser = InputParser::new();
2114
2115 let events = parser.parse(b"\x1b[1;6C");
2117 assert!(matches!(
2118 events.first(),
2119 Some(Event::Key(k)) if k.code == KeyCode::Right
2120 && k.modifiers.contains(Modifiers::SHIFT)
2121 && k.modifiers.contains(Modifiers::CTRL)
2122 ));
2123 }
2124
2125 #[test]
2126 fn modifiers_on_tilde_keys() {
2127 let mut parser = InputParser::new();
2128
2129 let events = parser.parse(b"\x1b[3;5~");
2131 assert!(matches!(
2132 events.first(),
2133 Some(Event::Key(k)) if k.code == KeyCode::Delete && k.modifiers.contains(Modifiers::CTRL)
2134 ));
2135
2136 let events = parser.parse(b"\x1b[5;2~");
2138 assert!(matches!(
2139 events.first(),
2140 Some(Event::Key(k)) if k.code == KeyCode::PageUp && k.modifiers.contains(Modifiers::SHIFT)
2141 ));
2142 }
2143
2144 #[test]
2147 fn mouse_sgr_right_click() {
2148 let mut parser = InputParser::new();
2149 let events = parser.parse(b"\x1b[<2;15;10M");
2151 assert!(matches!(
2152 events.first(),
2153 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Right))
2154 && m.x == 14 && m.y == 9
2155 ));
2156 }
2157
2158 #[test]
2159 fn mouse_sgr_middle_click() {
2160 let mut parser = InputParser::new();
2161 let events = parser.parse(b"\x1b[<1;5;5M");
2163 assert!(matches!(
2164 events.first(),
2165 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Middle))
2166 ));
2167 }
2168
2169 #[test]
2170 fn mouse_sgr_button_release() {
2171 let mut parser = InputParser::new();
2172 let events = parser.parse(b"\x1b[<0;10;20m");
2174 assert!(matches!(
2175 events.first(),
2176 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Up(MouseButton::Left))
2177 ));
2178 }
2179
2180 #[test]
2181 fn mouse_sgr_button_release_uppercase_m_compat() {
2182 let mut parser = InputParser::new();
2183 let events = parser.parse(b"\x1b[<3;10;20M");
2186 assert!(matches!(
2187 events.first(),
2188 Some(Event::Mouse(m))
2189 if matches!(m.kind, MouseEventKind::Up(MouseButton::Left))
2190 && m.x == 9
2191 && m.y == 19
2192 ));
2193 }
2194
2195 #[test]
2196 fn mouse_sgr_moved() {
2197 let mut parser = InputParser::new();
2198 let events = parser.parse(b"\x1b[<35;10;20M");
2200 assert!(matches!(
2201 events.first(),
2202 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Moved)
2203 ));
2204 }
2205
2206 #[test]
2207 fn mouse_sgr_with_modifiers() {
2208 let mut parser = InputParser::new();
2209 let events = parser.parse(b"\x1b[<4;5;5M");
2211 assert!(matches!(
2212 events.first(),
2213 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2214 && m.modifiers.contains(Modifiers::SHIFT)
2215 ));
2216
2217 let events = parser.parse(b"\x1b[<16;5;5M");
2219 assert!(matches!(
2220 events.first(),
2221 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2222 && m.modifiers.contains(Modifiers::CTRL)
2223 ));
2224
2225 let events = parser.parse(b"\x1b[<8;5;5M");
2227 assert!(matches!(
2228 events.first(),
2229 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2230 && m.modifiers.contains(Modifiers::ALT)
2231 ));
2232 }
2233
2234 #[test]
2235 fn mouse_legacy_1015_when_enabled() {
2236 let mut parser = InputParser::new();
2237 parser.set_expect_x10_mouse(true);
2238
2239 let events = parser.parse(b"\x1b[0;10;20M");
2240 assert!(matches!(
2241 events.first(),
2242 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2243 && m.x == 9 && m.y == 19
2244 ));
2245 }
2246
2247 #[test]
2248 fn mouse_legacy_1015_with_fallback_enabled() {
2249 let mut parser = InputParser::new();
2250 parser.set_allow_legacy_mouse(true);
2251
2252 let events = parser.parse(b"\x1b[0;10;20M");
2253 assert!(matches!(
2254 events.first(),
2255 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2256 && m.x == 9 && m.y == 19
2257 ));
2258 }
2259
2260 #[test]
2261 fn mouse_legacy_1015_ignored_when_disabled() {
2262 let mut parser = InputParser::new();
2263 let events = parser.parse(b"\x1b[0;10;20M");
2264 assert!(
2265 events.is_empty(),
2266 "legacy mouse should require explicit opt-in"
2267 );
2268 }
2269
2270 #[test]
2271 fn mouse_x10_when_enabled() {
2272 let mut parser = InputParser::new();
2273 parser.set_expect_x10_mouse(true);
2274
2275 let events = parser.parse(&[0x1B, b'[', b'M', 32, 42, 52]);
2276 assert!(matches!(
2277 events.first(),
2278 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2279 && m.x == 9 && m.y == 19
2280 ));
2281 }
2282
2283 #[test]
2284 fn mouse_x10_malformed_packet_ignored() {
2285 let mut parser = InputParser::new();
2286 parser.set_expect_x10_mouse(true);
2287
2288 let events = parser.parse(&[0x1B, b'[', b'M', 31, 0, 10]);
2290 assert!(
2291 events.iter().all(|event| !matches!(event, Event::Mouse(_))),
2292 "malformed X10 payload must not emit mouse events"
2293 );
2294 }
2295
2296 #[test]
2299 fn kitty_keyboard_release_event() {
2300 let mut parser = InputParser::new();
2301 let events = parser.parse(b"\x1b[97;1:3u");
2303 assert!(matches!(
2304 events.first(),
2305 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.kind == KeyEventKind::Release
2306 ));
2307
2308 let events = parser.parse(b"\x1b[65;5:3u");
2310 assert!(matches!(
2311 events.first(),
2312 Some(Event::Key(k))
2313 if k.code == KeyCode::Char('A')
2314 && k.modifiers.contains(Modifiers::CTRL)
2315 && k.kind == KeyEventKind::Release
2316 ));
2317 }
2318
2319 #[test]
2320 fn kitty_keyboard_special_keys() {
2321 let mut parser = InputParser::new();
2322
2323 assert!(matches!(
2325 parser.parse(b"\x1b[57344u").first(),
2326 Some(Event::Key(k)) if k.code == KeyCode::Escape
2327 ));
2328
2329 assert!(matches!(
2331 parser.parse(b"\x1b[57345u").first(),
2332 Some(Event::Key(k)) if k.code == KeyCode::Enter
2333 ));
2334
2335 assert!(matches!(
2337 parser.parse(b"\x1b[57346u").first(),
2338 Some(Event::Key(k)) if k.code == KeyCode::Tab
2339 ));
2340
2341 assert!(matches!(
2343 parser.parse(b"\x1b[57347u").first(),
2344 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2345 ));
2346
2347 assert!(matches!(
2349 parser.parse(b"\x1b[57348u").first(),
2350 Some(Event::Key(k)) if k.code == KeyCode::Insert
2351 ));
2352
2353 assert!(matches!(
2355 parser.parse(b"\x1b[57349u").first(),
2356 Some(Event::Key(k)) if k.code == KeyCode::Delete
2357 ));
2358 }
2359
2360 #[test]
2361 fn kitty_keyboard_navigation_keys() {
2362 let mut parser = InputParser::new();
2363
2364 assert!(matches!(
2366 parser.parse(b"\x1b[57350u").first(),
2367 Some(Event::Key(k)) if k.code == KeyCode::Left
2368 ));
2369 assert!(matches!(
2371 parser.parse(b"\x1b[57351u").first(),
2372 Some(Event::Key(k)) if k.code == KeyCode::Right
2373 ));
2374 assert!(matches!(
2376 parser.parse(b"\x1b[57352u").first(),
2377 Some(Event::Key(k)) if k.code == KeyCode::Up
2378 ));
2379 assert!(matches!(
2381 parser.parse(b"\x1b[57353u").first(),
2382 Some(Event::Key(k)) if k.code == KeyCode::Down
2383 ));
2384 assert!(matches!(
2386 parser.parse(b"\x1b[57354u").first(),
2387 Some(Event::Key(k)) if k.code == KeyCode::PageUp
2388 ));
2389 assert!(matches!(
2391 parser.parse(b"\x1b[57355u").first(),
2392 Some(Event::Key(k)) if k.code == KeyCode::PageDown
2393 ));
2394 assert!(matches!(
2396 parser.parse(b"\x1b[57356u").first(),
2397 Some(Event::Key(k)) if k.code == KeyCode::Home
2398 ));
2399 assert!(matches!(
2401 parser.parse(b"\x1b[57357u").first(),
2402 Some(Event::Key(k)) if k.code == KeyCode::End
2403 ));
2404 }
2405
2406 #[test]
2407 fn kitty_keyboard_f_keys() {
2408 let mut parser = InputParser::new();
2409 assert!(matches!(
2411 parser.parse(b"\x1b[57364u").first(),
2412 Some(Event::Key(k)) if k.code == KeyCode::F(1)
2413 ));
2414 assert!(matches!(
2416 parser.parse(b"\x1b[57375u").first(),
2417 Some(Event::Key(k)) if k.code == KeyCode::F(12)
2418 ));
2419 assert!(matches!(
2421 parser.parse(b"\x1b[57387u").first(),
2422 Some(Event::Key(k)) if k.code == KeyCode::F(24)
2423 ));
2424 }
2425
2426 #[test]
2427 fn kitty_keyboard_ascii_as_standard() {
2428 let mut parser = InputParser::new();
2429 assert!(matches!(
2431 parser.parse(b"\x1b[9u").first(),
2432 Some(Event::Key(k)) if k.code == KeyCode::Tab
2433 ));
2434 assert!(matches!(
2435 parser.parse(b"\x1b[13u").first(),
2436 Some(Event::Key(k)) if k.code == KeyCode::Enter
2437 ));
2438 assert!(matches!(
2439 parser.parse(b"\x1b[27u").first(),
2440 Some(Event::Key(k)) if k.code == KeyCode::Escape
2441 ));
2442 assert!(matches!(
2443 parser.parse(b"\x1b[127u").first(),
2444 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2445 ));
2446 assert!(matches!(
2448 parser.parse(b"\x1b[8u").first(),
2449 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2450 ));
2451 }
2452
2453 #[test]
2456 fn osc52_clipboard_bel_terminated() {
2457 let mut parser = InputParser::new();
2458 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x07");
2461 assert!(matches!(
2462 events.first(),
2463 Some(Event::Clipboard(c)) if c.content == "hello" && c.source == ClipboardSource::Osc52
2464 ));
2465 }
2466
2467 #[test]
2468 fn osc52_clipboard_st_terminated() {
2469 let mut parser = InputParser::new();
2470 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x1b\\");
2472 assert!(matches!(
2473 events.first(),
2474 Some(Event::Clipboard(c)) if c.content == "hello"
2475 ));
2476 }
2477
2478 #[test]
2481 fn dcs_xtgettcap_reply_produces_no_events() {
2482 let mut parser = InputParser::new();
2483 let events = parser.parse(b"\x1bP1+r524742=8/8/8\x1b\\");
2487 assert!(
2488 events.is_empty(),
2489 "a DCS reply must produce no events, got: {events:?}"
2490 );
2491 }
2492
2493 #[test]
2494 fn dcs_then_real_key_recovers_to_ground() {
2495 let mut parser = InputParser::new();
2496 let events = parser.parse(b"\x1bP1+r524742=8/8/8\x1b\\a");
2499 assert!(
2500 matches!(events.as_slice(), [Event::Key(k)] if k.code == KeyCode::Char('a')),
2501 "parser must recover to Ground after a DCS, got: {events:?}"
2502 );
2503 }
2504
2505 #[test]
2506 fn dcs_bel_terminated_is_ignored() {
2507 let mut parser = InputParser::new();
2508 let events = parser.parse(b"\x1bPsome-payload\x07b");
2509 assert!(
2510 matches!(events.as_slice(), [Event::Key(k)] if k.code == KeyCode::Char('b')),
2511 "BEL-terminated DCS ignored, then key parses, got: {events:?}"
2512 );
2513 }
2514
2515 #[test]
2516 fn sos_pm_apc_introducers_stay_alt_keys() {
2517 for &introducer in b"X^_" {
2521 let mut parser = InputParser::new();
2522 let events = parser.parse(&[0x1b, introducer]);
2523 assert!(
2524 matches!(
2525 events.as_slice(),
2526 [Event::Key(k)]
2527 if k.code == KeyCode::Char(introducer as char)
2528 && k.modifiers.contains(Modifiers::ALT)
2529 ),
2530 "ESC {} must stay an Alt key, got: {events:?}",
2531 introducer as char
2532 );
2533 }
2534 }
2535
2536 #[test]
2537 fn dcs_esc_then_csi_recovers_to_arrow_key() {
2538 let mut parser = InputParser::new();
2539 let events = parser.parse(b"\x1bP payload \x1b[A");
2542 assert!(
2543 matches!(events.as_slice(), [Event::Key(k)] if k.code == KeyCode::Up),
2544 "ESC-mid-DCS then a CSI must recover and parse the arrow, got: {events:?}"
2545 );
2546 }
2547
2548 #[test]
2549 fn dcs_aborts_on_control_char_so_input_is_not_swallowed() {
2550 let mut parser = InputParser::new();
2551 let events = parser.parse(b"\x1bPunterminated\r");
2555 assert!(
2556 !events.is_empty(),
2557 "a control char must abort a stuck DCS and emit the key, got: {events:?}"
2558 );
2559 }
2560
2561 #[test]
2562 fn osc52_clipboard_primary_selection() {
2563 let mut parser = InputParser::new();
2564 let events = parser.parse(b"\x1b]52;p;YWJj\x07");
2567 assert!(matches!(
2568 events.first(),
2569 Some(Event::Clipboard(c)) if c.content == "abc"
2570 ));
2571 }
2572
2573 #[test]
2576 fn ctrl_space_is_null() {
2577 let mut parser = InputParser::new();
2578 let events = parser.parse(&[0x00]);
2579 assert!(matches!(
2580 events.first(),
2581 Some(Event::Key(k)) if k.code == KeyCode::Null
2582 ));
2583 }
2584
2585 #[test]
2586 fn all_ctrl_letter_keys() {
2587 let mut parser = InputParser::new();
2588 for byte in 0x01..=0x1Au8 {
2590 let events = parser.parse(&[byte]);
2591 assert_eq!(
2592 events.len(),
2593 1,
2594 "Ctrl+{} should produce one event",
2595 (byte + b'a' - 1) as char
2596 );
2597 match byte {
2598 0x08 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace)),
2599 0x09 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Tab)),
2600 0x0D => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Enter)),
2601 _ => {
2602 let expected_char = (byte + b'a' - 1) as char;
2603 match &events[0] {
2604 Event::Key(k) => {
2605 assert_eq!(
2606 k.code,
2607 KeyCode::Char(expected_char),
2608 "Byte 0x{byte:02X} should produce Ctrl+{expected_char}"
2609 );
2610 assert!(
2611 k.modifiers.contains(Modifiers::CTRL),
2612 "Byte 0x{byte:02X} should have Ctrl modifier"
2613 );
2614 }
2615 other => {
2616 panic!("Byte 0x{byte:02X}: expected Key event, got {other:?}");
2617 }
2618 }
2619 }
2620 }
2621 }
2622 }
2623
2624 #[test]
2627 fn utf8_3byte_cjk() {
2628 let mut parser = InputParser::new();
2629 let events = parser.parse(&[0xE4, 0xB8, 0xAD]);
2631 assert!(matches!(
2632 events.first(),
2633 Some(Event::Key(k)) if k.code == KeyCode::Char('中')
2634 ));
2635 }
2636
2637 #[test]
2638 fn utf8_4byte_emoji() {
2639 let mut parser = InputParser::new();
2640 let events = parser.parse(&[0xF0, 0x9F, 0xA6, 0x80]);
2642 assert!(matches!(
2643 events.first(),
2644 Some(Event::Key(k)) if k.code == KeyCode::Char('🦀')
2645 ));
2646 }
2647
2648 #[test]
2651 fn empty_input_returns_no_events() {
2652 let mut parser = InputParser::new();
2653 let events = parser.parse(b"");
2654 assert!(events.is_empty());
2655 }
2656
2657 #[test]
2660 fn unknown_csi_tilde_ignored() {
2661 let mut parser = InputParser::new();
2662 let events = parser.parse(b"\x1b[99~");
2664 assert!(events.is_empty());
2665
2666 let events = parser.parse(b"a");
2668 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('a')));
2669 }
2670
2671 #[test]
2674 fn alt_special_chars() {
2675 let mut parser = InputParser::new();
2676
2677 let events = parser.parse(b"\x1b ");
2679 assert!(matches!(
2680 events.first(),
2681 Some(Event::Key(k)) if k.code == KeyCode::Char(' ') && k.modifiers.contains(Modifiers::ALT)
2682 ));
2683
2684 let events = parser.parse(b"\x1b5");
2686 assert!(matches!(
2687 events.first(),
2688 Some(Event::Key(k)) if k.code == KeyCode::Char('5') && k.modifiers.contains(Modifiers::ALT)
2689 ));
2690
2691 let events = parser.parse(b"\x1b}");
2693 assert!(matches!(
2694 events.first(),
2695 Some(Event::Key(k)) if k.code == KeyCode::Char('}') && k.modifiers.contains(Modifiers::ALT)
2696 ));
2697 }
2698
2699 #[test]
2700 fn alt_ctrl_key_combinations() {
2701 let mut parser = InputParser::new();
2702
2703 let events = parser.parse(&[0x1B, 0x01]);
2705 assert_eq!(events.len(), 1);
2706 match &events[0] {
2707 Event::Key(k) => {
2708 assert_eq!(k.code, KeyCode::Char('a'));
2709 assert!(k.modifiers.contains(Modifiers::ALT));
2710 assert!(k.modifiers.contains(Modifiers::CTRL));
2711 }
2712 _ => panic!("Expected Key event"),
2713 }
2714
2715 let events = parser.parse(&[0x1B, 0x08]);
2719 assert_eq!(events.len(), 1);
2720 match &events[0] {
2721 Event::Key(k) => {
2722 assert_eq!(k.code, KeyCode::Backspace);
2723 assert!(k.modifiers.contains(Modifiers::ALT));
2724 }
2725 _ => panic!("Expected Key event"),
2726 }
2727 }
2728
2729 #[test]
2732 fn ss3_arrow_keys() {
2733 let mut parser = InputParser::new();
2734 assert!(matches!(
2735 parser.parse(b"\x1bOA").first(),
2736 Some(Event::Key(k)) if k.code == KeyCode::Up
2737 ));
2738 assert!(matches!(
2739 parser.parse(b"\x1bOB").first(),
2740 Some(Event::Key(k)) if k.code == KeyCode::Down
2741 ));
2742 assert!(matches!(
2743 parser.parse(b"\x1bOC").first(),
2744 Some(Event::Key(k)) if k.code == KeyCode::Right
2745 ));
2746 assert!(matches!(
2747 parser.parse(b"\x1bOD").first(),
2748 Some(Event::Key(k)) if k.code == KeyCode::Left
2749 ));
2750 }
2751
2752 #[test]
2755 fn xterm_modifier_encoding() {
2756 assert_eq!(InputParser::modifiers_from_xterm(1), Modifiers::NONE);
2758 assert_eq!(InputParser::modifiers_from_xterm(2), Modifiers::SHIFT);
2759 assert_eq!(InputParser::modifiers_from_xterm(3), Modifiers::ALT);
2760 assert_eq!(
2761 InputParser::modifiers_from_xterm(4),
2762 Modifiers::SHIFT | Modifiers::ALT
2763 );
2764 assert_eq!(InputParser::modifiers_from_xterm(5), Modifiers::CTRL);
2765 assert_eq!(
2766 InputParser::modifiers_from_xterm(6),
2767 Modifiers::SHIFT | Modifiers::CTRL
2768 );
2769 assert_eq!(InputParser::modifiers_from_xterm(9), Modifiers::SUPER);
2770 }
2771
2772 #[test]
2775 fn ss3_interrupted_by_esc() {
2776 let mut parser = InputParser::new();
2777 let events = parser.parse(b"\x1bO\x1b[A");
2779 assert!(matches!(
2781 events.first(),
2782 Some(Event::Key(k)) if k.code == KeyCode::Up
2783 ));
2784 }
2785
2786 #[test]
2789 fn kitty_keyboard_reserved_keycode_ignored() {
2790 let mut parser = InputParser::new();
2791 let events = parser.parse(b"\x1b[57360u");
2793 assert!(events.is_empty());
2794
2795 let events = parser.parse(b"x");
2797 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('x')));
2798 }
2799 #[test]
2800 fn utf8_invalid_sequence_emits_replacement() {
2801 let mut parser = InputParser::new();
2802
2803 let events = parser.parse(&[0xE0, 0x41]);
2807 assert_eq!(events.len(), 2);
2808
2809 match &events[0] {
2810 Event::Key(k) => assert_eq!(k.code, KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2811 _ => panic!("Expected replacement character"),
2812 }
2813
2814 match &events[1] {
2815 Event::Key(k) => assert_eq!(k.code, KeyCode::Char('A')),
2816 _ => panic!("Expected character 'A'"),
2817 }
2818 }
2819
2820 #[test]
2821 fn utf8_invalid_lead_emits_replacement() {
2822 let mut parser = InputParser::new();
2823
2824 let events = parser.parse(&[0xC0, b'a']);
2826 assert!(
2827 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2828 "Expected replacement for invalid lead"
2829 );
2830 assert!(
2831 events
2832 .iter()
2833 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('a'))),
2834 "Expected subsequent ASCII to be preserved"
2835 );
2836
2837 let events = parser.parse(&[0xF5, b'b']);
2839 assert!(
2840 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2841 "Expected replacement for out-of-range lead"
2842 );
2843 assert!(
2844 events
2845 .iter()
2846 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('b'))),
2847 "Expected subsequent ASCII to be preserved"
2848 );
2849 }
2850}
2851
2852#[cfg(test)]
2853mod proptest_fuzz {
2854 use super::*;
2855 use proptest::prelude::*;
2856
2857 fn arb_byte() -> impl Strategy<Value = u8> {
2861 any::<u8>()
2862 }
2863
2864 fn arb_byte_vec(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
2865 prop::collection::vec(arb_byte(), 0..=max_len)
2866 }
2867
2868 fn csi_sequence() -> impl Strategy<Value = Vec<u8>> {
2870 let params = prop::collection::vec(0x30u8..=0x3F, 0..=20);
2871 let final_byte = 0x40u8..=0x7E;
2872 (params, final_byte).prop_map(|(p, f)| {
2873 let mut buf = vec![0x1B, b'['];
2874 buf.extend_from_slice(&p);
2875 buf.push(f);
2876 buf
2877 })
2878 }
2879
2880 fn osc_sequence() -> impl Strategy<Value = Vec<u8>> {
2882 let content = prop::collection::vec(0x20u8..=0x7E, 0..=64);
2883 let terminator = prop_oneof![
2884 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
2887 (content, terminator).prop_map(|(c, t)| {
2888 let mut buf = vec![0x1B, b']'];
2889 buf.extend_from_slice(&c);
2890 buf.extend_from_slice(&t);
2891 buf
2892 })
2893 }
2894
2895 fn ss3_sequence() -> impl Strategy<Value = Vec<u8>> {
2897 (0x40u8..=0x7E).prop_map(|f| vec![0x1B, b'O', f])
2898 }
2899
2900 fn paste_sequence() -> impl Strategy<Value = Vec<u8>> {
2902 prop::collection::vec(0x20u8..=0x7E, 0..=128).prop_map(|content| {
2903 let mut buf = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
2904 buf.extend_from_slice(&content);
2905 buf.extend_from_slice(b"\x1b[201~");
2906 buf
2907 })
2908 }
2909
2910 fn mixed_adversarial() -> impl Strategy<Value = Vec<u8>> {
2912 let fragment = prop_oneof![
2913 csi_sequence(),
2914 osc_sequence(),
2915 ss3_sequence(),
2916 paste_sequence(),
2917 arb_byte_vec(16), Just(vec![0x1B]), Just(vec![0x1B, b'[']), Just(vec![0x1B, b']']), prop::collection::vec(0x80u8..=0xFF, 1..=4), ];
2923 prop::collection::vec(fragment, 1..=8)
2924 .prop_map(|frags| frags.into_iter().flatten().collect())
2925 }
2926
2927 proptest! {
2930 #[test]
2932 fn random_bytes_never_panic(input in arb_byte_vec(512)) {
2933 let mut parser = InputParser::new();
2934 let _ = parser.parse(&input);
2935 }
2936
2937 #[test]
2939 fn parser_recovers_after_garbage(input in arb_byte_vec(256)) {
2940 let mut parser = InputParser::new();
2941 let _ = parser.parse(&input);
2942
2943 let events = parser.parse(b"z");
2945 let _ = events;
2948 }
2949
2950 #[test]
2952 fn mixed_sequences_never_panic(input in mixed_adversarial()) {
2953 let mut parser = InputParser::new();
2954 let _ = parser.parse(&input);
2955 }
2956
2957 #[test]
2959 fn events_are_well_formed(input in arb_byte_vec(256)) {
2960 let mut parser = InputParser::new();
2961 let events = parser.parse(&input);
2962 for event in &events {
2963 let _ = format!("{event:?}");
2965 }
2966 }
2967
2968 #[test]
2970 fn csi_event_count_bounded(seq in csi_sequence()) {
2971 let mut parser = InputParser::new();
2972 let events = parser.parse(&seq);
2973 prop_assert!(events.len() <= seq.len(),
2974 "Got {} events from {} bytes", events.len(), seq.len());
2975 }
2976
2977 #[test]
2979 fn osc_event_count_bounded(seq in osc_sequence()) {
2980 let mut parser = InputParser::new();
2981 let events = parser.parse(&seq);
2982 prop_assert!(events.len() <= seq.len(),
2983 "Got {} events from {} bytes", events.len(), seq.len());
2984 }
2985
2986 #[test]
2988 fn paste_content_bounded(content in prop::collection::vec(arb_byte(), 0..=2048)) {
2989 let mut parser = InputParser::new();
2990 let mut input = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
2991 input.extend_from_slice(&content);
2992 input.extend_from_slice(b"\x1b[201~");
2993
2994 let events = parser.parse(&input);
2995 for event in &events {
2996 if let Event::Paste(p) = event {
2997 prop_assert!(p.text.len() <= MAX_PASTE_LEN,
2998 "Paste text {} exceeds limit {}", p.text.len(), MAX_PASTE_LEN);
2999 }
3000 }
3001 }
3002
3003 #[test]
3005 fn incremental_matches_bulk(input in arb_byte_vec(128)) {
3006 let mut bulk_parser = InputParser::new();
3007 let bulk_events = bulk_parser.parse(&input);
3008
3009 let mut incr_parser = InputParser::new();
3010 let mut incr_events = Vec::new();
3011 for byte in &input {
3012 incr_events.extend(incr_parser.parse(std::slice::from_ref(byte)));
3013 }
3014
3015 let bulk_dbg: Vec<String> = bulk_events.iter().map(|e| format!("{e:?}")).collect();
3016 let incr_dbg: Vec<String> = incr_events.iter().map(|e| format!("{e:?}")).collect();
3017 prop_assert_eq!(bulk_dbg, incr_dbg,
3018 "Bulk vs incremental mismatch for input {:?}", input);
3019 }
3020
3021 #[test]
3024 fn deterministic_output(input in arb_byte_vec(128)) {
3025 let mut parser1 = InputParser::new();
3026 let events1 = parser1.parse(&input);
3027
3028 let mut parser2 = InputParser::new();
3029 let events2 = parser2.parse(&input);
3030
3031 let dbg1: Vec<String> = events1.iter().map(|e| format!("{e:?}")).collect();
3032 let dbg2: Vec<String> = events2.iter().map(|e| format!("{e:?}")).collect();
3033 prop_assert_eq!(dbg1, dbg2);
3034 }
3035 }
3036
3037 #[test]
3041 fn no_quadratic_blowup() {
3042 let mut parser = InputParser::new();
3043
3044 let garbage: Vec<u8> = (0..65536).map(|i| (i % 256) as u8).collect();
3046 let _ = parser.parse(&garbage);
3047
3048 let events = parser.parse(b"a");
3050 let _ = events; }
3052
3053 #[test]
3055 fn oversized_csi_transitions_to_ignore() {
3056 let mut parser = InputParser::new();
3057
3058 let mut input = vec![0x1B, b'['];
3060 input.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
3061 input.push(b'm');
3062
3063 let _ = parser.parse(&input);
3064
3065 let events = parser.parse(b"x");
3067 assert_eq!(events.len(), 1);
3068 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('x')));
3069 }
3070
3071 #[test]
3073 fn oversized_osc_transitions_to_ignore() {
3074 let mut parser = InputParser::new();
3075
3076 let mut input = vec![0x1B, b']'];
3078 input.extend(std::iter::repeat_n(b'a', MAX_OSC_LEN + 100));
3079 input.push(0x07); let _ = parser.parse(&input);
3082
3083 let events = parser.parse(b"y");
3085 assert_eq!(events.len(), 1);
3086 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('y')));
3087 }
3088
3089 #[test]
3091 fn rapid_esc_toggle() {
3092 let mut parser = InputParser::new();
3093
3094 let input: Vec<u8> = vec![0x1B; 1000];
3096 let _ = parser.parse(&input);
3097
3098 let events = parser.parse(b"k");
3100 assert!(!events.is_empty());
3101 }
3102
3103 #[test]
3105 fn unterminated_paste_recovery() {
3106 let mut parser = InputParser::new();
3107
3108 let mut input = b"\x1b[200~".to_vec();
3110 input.extend(std::iter::repeat_n(b'x', 2048));
3111
3112 let _ = parser.parse(&input);
3113
3114 let events = parser.parse(b"\x1b[201~");
3116 assert!(
3117 !events.is_empty(),
3118 "Parser should emit paste event on terminator"
3119 );
3120 }
3121
3122 #[test]
3124 fn truncated_utf8_lead_bytes() {
3125 let mut parser = InputParser::new();
3126
3127 for lead in [0xC2, 0xE0, 0xF0] {
3129 let _ = parser.parse(&[lead]);
3130 let events = parser.parse(b"a");
3132 let _ = events;
3134 }
3135 }
3136
3137 #[test]
3139 fn null_bytes_interleaved() {
3140 let mut parser = InputParser::new();
3141
3142 let input = b"\x00A\x00\x1b[A\x00B\x00";
3143 let events = parser.parse(input);
3144 assert!(
3146 events.len() >= 2,
3147 "Expected at least 2 events, got {}",
3148 events.len()
3149 );
3150 }
3151
3152 fn osc52_sequence() -> impl Strategy<Value = Vec<u8>> {
3156 let selector = prop_oneof![Just(b'c'), Just(b'p'), Just(b's')];
3157 let payload = prop::collection::vec(
3159 prop_oneof![
3160 0x41u8..=0x5A, 0x61u8..=0x7A, 0x30u8..=0x39, Just(b'+'),
3164 Just(b'/'),
3165 Just(b'='),
3166 ],
3167 0..=128,
3168 );
3169 let terminator = prop_oneof![
3170 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
3173 (selector, payload, terminator).prop_map(|(sel, pay, term)| {
3174 let mut buf = vec![0x1B, b']', b'5', b'2', b';', sel, b';'];
3175 buf.extend_from_slice(&pay);
3176 buf.extend_from_slice(&term);
3177 buf
3178 })
3179 }
3180
3181 fn sgr_mouse_sequence() -> impl Strategy<Value = Vec<u8>> {
3183 let button_code = 0u16..128;
3184 let x = 1u16..300;
3185 let y = 1u16..100;
3186 let final_byte = prop_oneof![Just(b'M'), Just(b'm')];
3187 (button_code, x, y, final_byte)
3188 .prop_map(|(btn, x, y, fb)| format!("\x1b[<{btn};{x};{y}{}", fb as char).into_bytes())
3189 }
3190
3191 fn kitty_keyboard_sequence() -> impl Strategy<Value = Vec<u8>> {
3193 let keycode = prop_oneof![
3194 0x20u32..0x7F, 0x57344u32..0x57400, 0x100u32..0x200, ];
3198 let modifier = 1u32..16;
3199 let kind = prop_oneof![Just(1u32), Just(2u32), Just(3u32)]; (keycode, prop::option::of(modifier), prop::option::of(kind)).prop_map(
3201 |(kc, mods, kind)| match (mods, kind) {
3202 (Some(m), Some(k)) => format!("\x1b[{kc};{m}:{k}u").into_bytes(),
3203 (Some(m), None) => format!("\x1b[{kc};{m}u").into_bytes(),
3204 _ => format!("\x1b[{kc}u").into_bytes(),
3205 },
3206 )
3207 }
3208
3209 proptest! {
3210 #[test]
3214 fn osc52_never_panics(seq in osc52_sequence()) {
3215 let mut parser = InputParser::new();
3216 let events = parser.parse(&seq);
3217 for event in &events {
3219 if let Event::Clipboard(c) = event {
3220 prop_assert!(!c.content.is_empty() || c.content.is_empty(),
3221 "Clipboard event must have a content field");
3222 }
3223 }
3224 }
3225
3226 #[test]
3228 fn osc52_corrupt_base64_safe(payload in arb_byte_vec(128)) {
3229 let mut parser = InputParser::new();
3230 let mut input = b"\x1b]52;c;".to_vec();
3231 input.extend_from_slice(&payload);
3232 input.push(0x07); let _ = parser.parse(&input);
3234 }
3235
3236 #[test]
3240 fn sgr_mouse_never_panics(seq in sgr_mouse_sequence()) {
3241 let mut parser = InputParser::new();
3242 let events = parser.parse(&seq);
3243 for event in &events {
3244 let _ = format!("{event:?}");
3246 }
3247 }
3248
3249 #[test]
3251 fn sgr_mouse_extreme_coords(
3252 btn in 0u16..128,
3253 x in 0u16..=65535,
3254 y in 0u16..=65535,
3255 ) {
3256 let mut parser = InputParser::new();
3257 let input = format!("\x1b[<{btn};{x};{y}M").into_bytes();
3258 let events = parser.parse(&input);
3259 for event in &events {
3260 if let Event::Mouse(m) = event {
3261 prop_assert!(m.x <= x, "Mouse x {} > input x {}", m.x, x);
3262 prop_assert!(m.y <= y, "Mouse y {} > input y {}", m.y, y);
3263 }
3264 }
3265 }
3266
3267 #[test]
3271 fn kitty_keyboard_never_panics(seq in kitty_keyboard_sequence()) {
3272 let mut parser = InputParser::new();
3273 let _ = parser.parse(&seq);
3274 }
3275
3276 #[test]
3280 fn truncated_csi_then_valid(
3281 params in prop::collection::vec(0x30u8..=0x3F, 1..=10),
3282 valid_char in 0x20u8..0x7F,
3283 ) {
3284 let mut parser = InputParser::new();
3285
3286 let mut partial = vec![0x1B, b'['];
3288 partial.extend_from_slice(¶ms);
3289 let _ = parser.parse(&partial);
3290
3291 let events = parser.parse(&[0x1B, b'[', b'A']); let _ = events;
3296
3297 let events = parser.parse(&[valid_char]);
3299 let _ = events;
3300 }
3301
3302 #[test]
3304 fn truncated_osc_then_valid(
3305 content in prop::collection::vec(0x20u8..=0x7E, 1..=32),
3306 ) {
3307 let mut parser = InputParser::new();
3308
3309 let mut partial = vec![0x1B, b']'];
3311 partial.extend_from_slice(&content);
3312 let _ = parser.parse(&partial);
3313
3314 let events = parser.parse(b"\x1bz");
3316 let _ = events;
3317 }
3318
3319 #[test]
3323 fn csi_near_limit_produces_event(
3324 fill_byte in 0x30u8..=0x39, ) {
3326 let mut parser = InputParser::new();
3327
3328 let mut input = vec![0x1B, b'['];
3329 input.extend(std::iter::repeat_n(fill_byte, MAX_CSI_LEN - 1));
3331 input.push(b'm'); let events = parser.parse(&input);
3334 let _ = events;
3337
3338 let events = parser.parse(b"a");
3340 prop_assert!(!events.is_empty(), "Parser stuck after near-limit CSI");
3341 }
3342
3343 #[test]
3345 fn osc_near_limit_processes(
3346 fill_byte in 0x20u8..=0x7E,
3347 ) {
3348 let mut parser = InputParser::new();
3349
3350 let mut input = vec![0x1B, b']'];
3351 input.extend(std::iter::repeat_n(fill_byte, MAX_OSC_LEN - 1));
3352 input.push(0x07); let _ = parser.parse(&input);
3355
3356 let events = parser.parse(b"b");
3358 prop_assert!(!events.is_empty(), "Parser stuck after near-limit OSC");
3359 }
3360
3361 #[test]
3365 fn consecutive_pastes_emit_events(count in 2usize..=5) {
3366 let mut parser = InputParser::new();
3367 let mut input = Vec::new();
3368
3369 for i in 0..count {
3370 input.extend_from_slice(b"\x1b[200~");
3371 input.extend_from_slice(format!("paste_{i}").as_bytes());
3372 input.extend_from_slice(b"\x1b[201~");
3373 }
3374
3375 let events = parser.parse(&input);
3376 let paste_events: Vec<_> = events.iter()
3377 .filter(|e| matches!(e, Event::Paste(_)))
3378 .collect();
3379
3380 prop_assert_eq!(paste_events.len(), count,
3381 "Expected {} paste events, got {}", count, paste_events.len());
3382 }
3383
3384 #[test]
3386 fn paste_with_invalid_utf8(content in arb_byte_vec(256)) {
3387 let mut parser = InputParser::new();
3388 let mut input = b"\x1b[200~".to_vec();
3389 input.extend_from_slice(&content);
3390 input.extend_from_slice(b"\x1b[201~");
3391
3392 let events = parser.parse(&input);
3393 for event in &events {
3394 if let Event::Paste(p) = event {
3395 prop_assert!(p.text.is_char_boundary(0), "Paste text is not valid UTF-8");
3397 }
3398 }
3399 }
3400
3401 #[test]
3405 fn recovery_via_esc_reset(garbage in arb_byte_vec(256)) {
3406 let mut parser = InputParser::new();
3407 let _ = parser.parse(&garbage);
3408
3409 let _ = parser.parse(b"\x07\x1b\\\x1b");
3412 let _ = parser.parse(b"\x1b");
3413
3414 let _ = parser.parse(b"z");
3416
3417 let events = parser.parse(b"q");
3419 prop_assert!(!events.is_empty(),
3422 "Parser did not recover after garbage + reset");
3423 }
3424 }
3425}