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 = 4096;
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 Utf8 {
77 collected: u8,
79 expected: u8,
81 },
82 MouseX10 { collected: u8, buffer: [u8; 3] },
84}
85
86#[derive(Debug)]
96pub struct InputParser {
97 state: ParserState,
99 buffer: Vec<u8>,
101 paste_buffer: Vec<u8>,
103 utf8_buffer: [u8; 4],
105 in_paste: bool,
107 pending_event: Option<Event>,
109 expect_x10_mouse: bool,
118 allow_legacy_mouse: bool,
125}
126
127impl Default for InputParser {
128 fn default() -> Self {
129 Self::new()
130 }
131}
132
133impl InputParser {
134 #[inline]
135 fn event_reserve_hint(input_len: usize) -> usize {
136 input_len.saturating_add(1).min(MAX_EVENT_RESERVE_HINT)
137 }
138
139 #[must_use]
141 pub fn new() -> Self {
142 Self {
143 state: ParserState::Ground,
144 buffer: Vec::with_capacity(64),
145 paste_buffer: Vec::new(),
146 utf8_buffer: [0; 4],
147 in_paste: false,
148 pending_event: None,
149 expect_x10_mouse: false,
150 allow_legacy_mouse: false,
151 }
152 }
153
154 pub fn set_expect_x10_mouse(&mut self, enabled: bool) {
159 self.expect_x10_mouse = enabled;
160 }
161
162 pub fn set_allow_legacy_mouse(&mut self, enabled: bool) {
169 self.allow_legacy_mouse = enabled;
170 }
171
172 #[must_use]
175 pub const fn has_pending_timeout_state(&self) -> bool {
176 matches!(self.state, ParserState::Escape | ParserState::Utf8 { .. })
177 }
178
179 pub fn timeout(&mut self) -> Option<Event> {
184 match self.state {
185 ParserState::Escape => {
186 self.state = ParserState::Ground;
187 Some(Event::Key(KeyEvent::new(KeyCode::Escape)))
188 }
189 ParserState::Utf8 { .. } => {
190 self.state = ParserState::Ground;
192 self.utf8_buffer = [0; 4];
193 Some(Event::Key(KeyEvent::new(KeyCode::Char(
194 std::char::REPLACEMENT_CHARACTER,
195 ))))
196 }
197 _ => None,
198 }
199 }
200
201 pub fn parse(&mut self, input: &[u8]) -> Vec<Event> {
203 let mut events = Vec::with_capacity(Self::event_reserve_hint(input.len()));
204 self.parse_with(input, |event| events.push(event));
205 events
206 }
207
208 pub fn parse_with<F>(&mut self, input: &[u8], mut emit: F)
210 where
211 F: FnMut(Event),
212 {
213 let span = debug_span!("event.normalize", raw_byte_count = input.len());
214 let _guard = span.enter();
215 trace!("raw input bytes: {} bytes", input.len());
216
217 for &byte in input {
218 if let Some(event) = self.process_byte(byte) {
219 debug!(event_type = event.event_type_label(), "normalized event");
220 emit(event);
221 }
222 if let Some(pending) = self.pending_event.take() {
223 debug!(event_type = pending.event_type_label(), "normalized event");
224 emit(pending);
225 }
226 }
227 }
228
229 pub fn parse_into(&mut self, input: &[u8], events: &mut Vec<Event>) {
234 let needed = Self::event_reserve_hint(input.len());
235 let available = events.capacity().saturating_sub(events.len());
236 if available < needed {
237 events.reserve(needed - available);
238 }
239 self.parse_with(input, |event| events.push(event));
240 }
241
242 fn process_byte(&mut self, byte: u8) -> Option<Event> {
244 if self.in_paste {
246 return self.process_paste_byte(byte);
247 }
248
249 match self.state {
250 ParserState::Ground => self.process_ground(byte),
251 ParserState::Escape => self.process_escape(byte),
252 ParserState::Csi => self.process_csi(byte),
253 ParserState::CsiParam => self.process_csi_param(byte),
254 ParserState::CsiIgnore => self.process_csi_ignore(byte),
255 ParserState::Ss3 => self.process_ss3(byte),
256 ParserState::Osc => self.process_osc(byte),
257 ParserState::OscContent => self.process_osc_content(byte),
258 ParserState::OscEscape => self.process_osc_escape(byte),
259 ParserState::OscIgnore => self.process_osc_ignore(byte),
260 ParserState::Utf8 {
261 collected,
262 expected,
263 } => self.process_utf8(byte, collected, expected),
264 ParserState::MouseX10 { .. } => self.process_mouse_x10(byte),
265 }
266 }
267
268 fn process_ground(&mut self, byte: u8) -> Option<Event> {
270 match byte {
271 0x1B => {
273 self.state = ParserState::Escape;
274 None
275 }
276 0x9B => {
278 self.state = ParserState::Csi;
279 self.buffer.clear();
280 None
281 }
282 0x8F => {
284 self.state = ParserState::Ss3;
285 None
286 }
287 0x9D => {
289 self.state = ParserState::Osc;
290 self.buffer.clear();
291 None
292 }
293 0x00 => Some(Event::Key(KeyEvent::new(KeyCode::Null))),
295 0x08 => Some(Event::Key(KeyEvent::new(KeyCode::Backspace))),
297 0x09 => Some(Event::Key(KeyEvent::new(KeyCode::Tab))),
299 0x0D => Some(Event::Key(KeyEvent::new(KeyCode::Enter))),
301 0x01..=0x07 | 0x0A..=0x0C | 0x0E..=0x1A => {
303 let c = (byte + b'a' - 1) as char;
304 Some(Event::Key(
305 KeyEvent::new(KeyCode::Char(c)).with_modifiers(Modifiers::CTRL),
306 ))
307 }
308 0x7F => Some(Event::Key(KeyEvent::new(KeyCode::Backspace))),
310 0x20..=0x7E => Some(Event::Key(KeyEvent::new(KeyCode::Char(byte as char)))),
312 0xC2..=0xDF => {
314 self.utf8_buffer[0] = byte;
315 self.state = ParserState::Utf8 {
316 collected: 1,
317 expected: 2,
318 };
319 None
320 }
321 0xE0..=0xEF => {
322 self.utf8_buffer[0] = byte;
323 self.state = ParserState::Utf8 {
324 collected: 1,
325 expected: 3,
326 };
327 None
328 }
329 0xF0..=0xF4 => {
330 self.utf8_buffer[0] = byte;
331 self.state = ParserState::Utf8 {
332 collected: 1,
333 expected: 4,
334 };
335 None
336 }
337 0xC0..=0xC1 | 0xF5..=0xFF => Some(Event::Key(KeyEvent::new(KeyCode::Char(
339 std::char::REPLACEMENT_CHARACTER,
340 )))),
341 _ => None,
343 }
344 }
345
346 fn process_escape(&mut self, byte: u8) -> Option<Event> {
348 match byte {
349 b'[' => {
351 self.state = ParserState::Csi;
352 self.buffer.clear();
353 None
354 }
355 b'O' => {
357 self.state = ParserState::Ss3;
358 None
359 }
360 b']' => {
362 self.state = ParserState::Osc;
363 self.buffer.clear();
364 None
365 }
366 0x1B => {
369 self.state = ParserState::Ground;
370 Some(Event::Key(
371 KeyEvent::new(KeyCode::Escape).with_modifiers(Modifiers::ALT),
372 ))
373 }
374 0x00..=0x1F => {
376 self.state = ParserState::Ground;
377 if let Some(mut event) = self.process_ground(byte) {
380 if let Event::Key(ref mut key) = event {
381 key.modifiers |= Modifiers::ALT;
382 }
383 Some(event)
384 } else {
385 None
386 }
387 }
388 0x20..=0x7E => {
390 self.state = ParserState::Ground;
391 Some(Event::Key(
392 KeyEvent::new(KeyCode::Char(byte as char)).with_modifiers(Modifiers::ALT),
393 ))
394 }
395 0x7F => {
397 self.state = ParserState::Ground;
398 Some(Event::Key(
399 KeyEvent::new(KeyCode::Backspace).with_modifiers(Modifiers::ALT),
400 ))
401 }
402 _ => {
404 self.state = ParserState::Ground;
405 None
406 }
407 }
408 }
409
410 fn process_csi(&mut self, byte: u8) -> Option<Event> {
412 if byte == 0x1B {
414 self.state = ParserState::Escape;
415 self.buffer.clear();
416 return None;
417 }
418
419 self.buffer.push(byte);
420
421 match byte {
422 0x20..=0x3F => {
424 self.state = ParserState::CsiParam;
425 None
426 }
427 0x40..=0x7E => {
429 if self.expect_x10_mouse && byte == b'M' && self.buffer.len() == 1 {
433 self.state = ParserState::MouseX10 {
434 collected: 0,
435 buffer: [0; 3],
436 };
437 self.buffer.clear();
438 return None;
439 }
440
441 self.state = ParserState::Ground;
442 self.parse_csi_sequence()
443 }
444 _ => {
446 self.state = ParserState::Ground;
447 self.buffer.clear();
448 None
449 }
450 }
451 }
452
453 fn process_csi_param(&mut self, byte: u8) -> Option<Event> {
455 if byte == 0x1B {
457 self.state = ParserState::Escape;
458 self.buffer.clear();
459 return None;
460 }
461
462 if self.buffer.len() >= MAX_CSI_LEN {
464 self.state = ParserState::CsiIgnore;
465 self.buffer.clear();
466 return None;
467 }
468
469 self.buffer.push(byte);
470
471 match byte {
472 0x20..=0x3F => None,
474 0x40..=0x7E => {
476 self.state = ParserState::Ground;
477 self.parse_csi_sequence()
478 }
479 _ => {
481 self.state = ParserState::Ground;
482 self.buffer.clear();
483 None
484 }
485 }
486 }
487
488 fn process_csi_ignore(&mut self, byte: u8) -> Option<Event> {
490 if byte == 0x1B {
492 self.state = ParserState::Escape;
493 return None;
494 }
495
496 if (0x40..=0x7E).contains(&byte) {
498 self.state = ParserState::Ground;
499 } else if !(0x20..=0x7E).contains(&byte) {
500 self.state = ParserState::Ground;
502 }
503 None
504 }
505
506 fn parse_csi_sequence(&mut self) -> Option<Event> {
508 let seq = std::mem::take(&mut self.buffer);
509 if seq.is_empty() {
510 return None;
511 }
512
513 let final_byte = *seq.last()?;
514 let params = &seq[..seq.len() - 1];
515
516 match (params, final_byte) {
518 ([], b'I') => return Some(Event::Focus(true)),
520 ([], b'O') => return Some(Event::Focus(false)),
521
522 (b"200", b'~') => {
524 self.in_paste = true;
525 self.paste_buffer.clear();
526 self.buffer.clear(); return None;
528 }
529 (b"201", b'~') => {
530 self.in_paste = false;
531 let content = String::from_utf8_lossy(&self.paste_buffer).into_owned();
532 self.paste_buffer.clear();
533 return Some(Event::Paste(PasteEvent::bracketed(content)));
534 }
535
536 _ if params.starts_with(b"<") && (final_byte == b'M' || final_byte == b'm') => {
538 return self.parse_sgr_mouse(params, final_byte);
539 }
540 _ if (self.allow_legacy_mouse || self.expect_x10_mouse) && final_byte == b'M' => {
547 if let Some(event) = self.parse_legacy_mouse(params) {
548 return Some(event);
549 }
550 }
551
552 _ => {}
553 }
554
555 match final_byte {
557 b'A' => Some(Event::Key(self.key_with_modifiers(KeyCode::Up, params))),
558 b'B' => Some(Event::Key(self.key_with_modifiers(KeyCode::Down, params))),
559 b'C' => Some(Event::Key(self.key_with_modifiers(KeyCode::Right, params))),
560 b'D' => Some(Event::Key(self.key_with_modifiers(KeyCode::Left, params))),
561 b'H' => Some(Event::Key(self.key_with_modifiers(KeyCode::Home, params))),
562 b'F' => Some(Event::Key(self.key_with_modifiers(KeyCode::End, params))),
563 b'Z' => Some(Event::Key(
564 self.key_with_modifiers(KeyCode::BackTab, params),
565 )),
566 b'~' => self.parse_csi_tilde(params),
567 b'u' => self.parse_kitty_keyboard(params),
568 _ => None,
569 }
570 }
571
572 fn parse_csi_tilde(&self, params: &[u8]) -> Option<Event> {
574 let num = self.parse_first_param(params)?;
575 let mods = self.parse_modifier_param(params);
576
577 let code = match num {
578 1 => KeyCode::Home,
579 2 => KeyCode::Insert,
580 3 => KeyCode::Delete,
581 4 => KeyCode::End,
582 5 => KeyCode::PageUp,
583 6 => KeyCode::PageDown,
584 15 => KeyCode::F(5),
585 17 => KeyCode::F(6),
586 18 => KeyCode::F(7),
587 19 => KeyCode::F(8),
588 20 => KeyCode::F(9),
589 21 => KeyCode::F(10),
590 23 => KeyCode::F(11),
591 24 => KeyCode::F(12),
592 _ => return None,
593 };
594
595 Some(Event::Key(KeyEvent::new(code).with_modifiers(mods)))
596 }
597
598 fn parse_first_param(&self, params: &[u8]) -> Option<u32> {
600 let s = std::str::from_utf8(params).ok()?;
601 let first = s.split(';').next()?;
602 first.parse().ok()
603 }
604
605 fn parse_modifier_param(&self, params: &[u8]) -> Modifiers {
607 let s = match std::str::from_utf8(params) {
608 Ok(s) => s,
609 Err(_) => return Modifiers::NONE,
610 };
611
612 let modifier_value: u32 = s
613 .split(';')
614 .nth(1)
615 .and_then(|s| s.parse().ok())
616 .unwrap_or(1);
617
618 Self::modifiers_from_xterm(modifier_value)
619 }
620
621 fn parse_kitty_keyboard(&self, params: &[u8]) -> Option<Event> {
625 let s = std::str::from_utf8(params).ok()?;
626 if s.is_empty() {
627 return None;
628 }
629
630 let mut parts = s.split(';');
631 let key_part = parts.next().unwrap_or("");
632 let key_code_str = key_part.split(':').next().unwrap_or("");
633 let key_code: u32 = key_code_str.parse().ok()?;
634
635 let mod_part = parts.next().unwrap_or("");
636 let (modifiers, kind) = Self::kitty_modifiers_and_kind(mod_part);
637
638 let code = Self::kitty_keycode_to_keycode(key_code)?;
639 Some(Event::Key(
640 KeyEvent::new(code)
641 .with_modifiers(modifiers)
642 .with_kind(kind),
643 ))
644 }
645
646 fn kitty_modifiers_and_kind(mod_part: &str) -> (Modifiers, KeyEventKind) {
647 if mod_part.is_empty() {
648 return (Modifiers::NONE, KeyEventKind::Press);
649 }
650
651 let mut parts = mod_part.split(':');
652 let mod_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
653 let kind_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
654
655 let modifiers = Self::modifiers_from_xterm(mod_value);
656 let kind = match kind_value {
657 2 => KeyEventKind::Repeat,
658 3 => KeyEventKind::Release,
659 _ => KeyEventKind::Press,
660 };
661
662 (modifiers, kind)
663 }
664
665 fn kitty_keycode_to_keycode(key_code: u32) -> Option<KeyCode> {
666 match key_code {
667 9 => Some(KeyCode::Tab),
669 13 => Some(KeyCode::Enter),
670 27 => Some(KeyCode::Escape),
671 8 | 127 => Some(KeyCode::Backspace),
672 57_344 => Some(KeyCode::Escape),
674 57_345 => Some(KeyCode::Enter),
675 57_346 => Some(KeyCode::Tab),
676 57_347 => Some(KeyCode::Backspace),
677 57_348 => Some(KeyCode::Insert),
678 57_349 => Some(KeyCode::Delete),
679 57_350 => Some(KeyCode::Left),
680 57_351 => Some(KeyCode::Right),
681 57_352 => Some(KeyCode::Up),
682 57_353 => Some(KeyCode::Down),
683 57_354 => Some(KeyCode::PageUp),
684 57_355 => Some(KeyCode::PageDown),
685 57_356 => Some(KeyCode::Home),
686 57_357 => Some(KeyCode::End),
687 57_364..=57_387 => {
689 let f_num = key_code - 57_364 + 1;
692 debug_assert!(f_num <= 24, "F-key number {f_num} exceeds F24");
693 Some(KeyCode::F(f_num as u8))
694 }
695 57_358..=57_363 | 57_388..=63_743 => None,
697 _ => char::from_u32(key_code).map(KeyCode::Char),
699 }
700 }
701
702 fn modifiers_from_xterm(value: u32) -> Modifiers {
703 let bits = value.saturating_sub(1);
706 let mut mods = Modifiers::NONE;
707 if bits & 1 != 0 {
708 mods |= Modifiers::SHIFT;
709 }
710 if bits & 2 != 0 {
711 mods |= Modifiers::ALT;
712 }
713 if bits & 4 != 0 {
714 mods |= Modifiers::CTRL;
715 }
716 if bits & 8 != 0 {
717 mods |= Modifiers::SUPER;
718 }
719 mods
720 }
721
722 fn key_with_modifiers(&self, code: KeyCode, params: &[u8]) -> KeyEvent {
724 KeyEvent::new(code).with_modifiers(self.parse_modifier_param(params))
725 }
726
727 fn parse_sgr_mouse(&self, params: &[u8], final_byte: u8) -> Option<Event> {
729 let params = ¶ms[1..];
732 let s = std::str::from_utf8(params).ok()?;
733 let mut parts = s.split(';');
734
735 let button_code_u32 = Self::parse_u32_prefix(parts.next()?)?;
738 let button_code = button_code_u32.min(u16::MAX as u32) as u16;
739 let x_raw = Self::parse_i32_prefix(parts.next()?)?;
740 let y_raw = Self::parse_i32_prefix(parts.next()?)?;
741
742 let (button, mods) = self.decode_mouse_button(button_code);
744
745 let kind = if final_byte == b'M' {
746 if button_code & 64 != 0 {
747 match button_code & 3 {
750 0 => MouseEventKind::ScrollUp,
751 1 => MouseEventKind::ScrollDown,
752 2 => MouseEventKind::ScrollLeft,
753 _ => MouseEventKind::ScrollRight,
754 }
755 } else if button_code & 32 != 0 {
756 if button_code & 3 == 3 {
759 MouseEventKind::Moved
760 } else {
761 MouseEventKind::Drag(button)
762 }
763 } else if (button_code & 3) == 3 {
764 MouseEventKind::Up(MouseButton::Left)
767 } else {
768 MouseEventKind::Down(button)
769 }
770 } else {
771 MouseEventKind::Up(button)
772 };
773
774 Some(Event::Mouse(MouseEvent {
775 kind,
776 x: Self::normalize_sgr_coord(x_raw),
777 y: Self::normalize_sgr_coord(y_raw),
778 modifiers: mods,
779 }))
780 }
781
782 #[inline]
783 fn parse_u32_prefix(token: &str) -> Option<u32> {
784 let bytes = token.as_bytes();
785 let digits = bytes.iter().take_while(|b| b.is_ascii_digit()).count();
786 if digits == 0 {
787 return None;
788 }
789 token[..digits].parse().ok()
790 }
791
792 #[inline]
793 fn parse_i32_prefix(token: &str) -> Option<i32> {
794 let bytes = token.as_bytes();
795 if bytes.is_empty() {
796 return None;
797 }
798 let start = if bytes[0] == b'-' || bytes[0] == b'+' {
799 1
800 } else {
801 0
802 };
803 let digits = bytes[start..]
804 .iter()
805 .take_while(|b| b.is_ascii_digit())
806 .count();
807 if digits == 0 {
808 return None;
809 }
810 token[..start + digits].parse().ok()
811 }
812
813 #[inline]
814 fn normalize_sgr_coord(raw: i32) -> u16 {
815 if raw <= 1 {
816 return 0;
817 }
818 let zero_indexed = raw - 1;
819 zero_indexed.min(i32::from(u16::MAX)) as u16
820 }
821
822 fn parse_legacy_mouse(&self, params: &[u8]) -> Option<Event> {
827 if params.is_empty() || params.starts_with(b"<") {
828 return None;
829 }
830
831 let s = std::str::from_utf8(params).ok()?;
832 let mut parts = s.split(';');
833 let button_code: u16 = parts.next()?.parse().ok()?;
834 let x: u16 = parts.next()?.parse().ok()?;
835 let y: u16 = parts.next()?.parse().ok()?;
836 if parts.next().is_some() {
838 return None;
839 }
840
841 let (button, mods) = self.decode_mouse_button(button_code);
842 let kind = if button_code & 64 != 0 {
843 match button_code & 3 {
845 0 => MouseEventKind::ScrollUp,
846 1 => MouseEventKind::ScrollDown,
847 2 => MouseEventKind::ScrollLeft,
848 _ => MouseEventKind::ScrollRight,
849 }
850 } else if button_code & 32 != 0 {
851 if button_code & 3 == 3 {
853 MouseEventKind::Moved
854 } else {
855 MouseEventKind::Drag(button)
856 }
857 } else if (button_code & 3) == 3 {
858 MouseEventKind::Up(MouseButton::Left)
860 } else {
861 MouseEventKind::Down(button)
862 };
863
864 Some(Event::Mouse(MouseEvent {
865 kind,
866 x: x.saturating_sub(1),
867 y: y.saturating_sub(1),
868 modifiers: mods,
869 }))
870 }
871
872 fn decode_mouse_button(&self, code: u16) -> (MouseButton, Modifiers) {
874 let button = match code & 0b11 {
875 0 => MouseButton::Left,
876 1 => MouseButton::Middle,
877 2 => MouseButton::Right,
878 _ => MouseButton::Left,
879 };
880
881 let mut mods = Modifiers::NONE;
882 if code & 4 != 0 {
883 mods |= Modifiers::SHIFT;
884 }
885 if code & 8 != 0 {
886 mods |= Modifiers::ALT;
887 }
888 if code & 16 != 0 {
889 mods |= Modifiers::CTRL;
890 }
891
892 (button, mods)
893 }
894
895 fn process_ss3(&mut self, byte: u8) -> Option<Event> {
897 if byte == 0x1B {
899 self.state = ParserState::Escape;
900 return None;
901 }
902
903 self.state = ParserState::Ground;
904
905 let code = match byte {
906 b'P' => KeyCode::F(1),
907 b'Q' => KeyCode::F(2),
908 b'R' => KeyCode::F(3),
909 b'S' => KeyCode::F(4),
910 b'A' => KeyCode::Up,
911 b'B' => KeyCode::Down,
912 b'C' => KeyCode::Right,
913 b'D' => KeyCode::Left,
914 b'H' => KeyCode::Home,
915 b'F' => KeyCode::End,
916 _ => return None,
917 };
918
919 Some(Event::Key(KeyEvent::new(code)))
920 }
921
922 fn process_osc(&mut self, byte: u8) -> Option<Event> {
924 if byte == 0x1B {
926 self.state = ParserState::OscEscape;
927 return None;
928 }
929
930 self.buffer.push(byte);
931
932 match byte {
933 0x07 => {
935 self.state = ParserState::Ground;
936 self.parse_osc_sequence()
937 }
938 _ => {
940 self.state = ParserState::OscContent;
941 None
942 }
943 }
944 }
945
946 fn process_osc_content(&mut self, byte: u8) -> Option<Event> {
948 if byte == 0x1B {
950 self.state = ParserState::OscEscape;
951 return None;
952 }
953
954 if byte < 0x20 && byte != 0x07 {
956 self.state = ParserState::Ground;
957 self.buffer.clear();
958 return None;
959 }
960
961 if self.buffer.len() >= MAX_OSC_LEN {
963 self.state = ParserState::OscIgnore;
964 self.buffer.clear();
965 return None;
966 }
967
968 match byte {
969 0x07 => {
971 self.state = ParserState::Ground;
972 self.parse_osc_sequence()
973 }
974 _ => {
976 self.buffer.push(byte);
977 None
978 }
979 }
980 }
981
982 fn process_osc_escape(&mut self, byte: u8) -> Option<Event> {
984 if byte == b'\\' {
985 self.state = ParserState::Ground;
987 self.parse_osc_sequence()
988 } else if byte == 0x1B {
989 self.state = ParserState::Escape;
991 self.buffer.clear();
992 None
993 } else {
994 self.buffer.clear();
1000 self.state = ParserState::Escape;
1001 self.process_escape(byte)
1002 }
1003 }
1004
1005 fn process_osc_ignore(&mut self, byte: u8) -> Option<Event> {
1007 match byte {
1008 0x07 => {
1010 self.state = ParserState::Ground;
1011 None
1012 }
1013 0x1B => {
1015 self.state = ParserState::OscEscape;
1016 None
1017 }
1018 _ if byte < 0x20 => {
1020 self.state = ParserState::Ground;
1021 None
1022 }
1023 _ => None,
1025 }
1026 }
1027
1028 fn parse_osc_sequence(&mut self) -> Option<Event> {
1030 let seq = std::mem::take(&mut self.buffer);
1031
1032 if seq.starts_with(b"52;") {
1034 return self.parse_osc52_clipboard(&seq);
1035 }
1036
1037 None
1039 }
1040
1041 fn parse_osc52_clipboard(&self, seq: &[u8]) -> Option<Event> {
1043 let content = &seq[3..]; if content.is_empty() {
1046 return None;
1047 }
1048
1049 let source = ClipboardSource::Osc52;
1052
1053 let base64_start = content.iter().position(|&b| b == b';').map(|i| i + 1)?;
1055 let base64_data = &content[base64_start..];
1056
1057 let decoded = self.decode_base64(base64_data)?;
1059
1060 Some(Event::Clipboard(ClipboardEvent::new(
1061 String::from_utf8_lossy(&decoded).into_owned(),
1062 source,
1063 )))
1064 }
1065
1066 fn decode_base64(&self, input: &[u8]) -> Option<Vec<u8>> {
1068 const DECODE_TABLE: [i8; 256] = {
1069 let mut table = [-1i8; 256];
1070 let mut i = 0u8;
1071 while i < 26 {
1072 table[(b'A' + i) as usize] = i as i8;
1073 table[(b'a' + i) as usize] = (i + 26) as i8;
1074 i += 1;
1075 }
1076 let mut i = 0u8;
1077 while i < 10 {
1078 table[(b'0' + i) as usize] = (i + 52) as i8;
1079 i += 1;
1080 }
1081 table[b'+' as usize] = 62;
1082 table[b'/' as usize] = 63;
1083 table
1084 };
1085
1086 let mut output = Vec::with_capacity(input.len() * 3 / 4);
1087 let mut buffer = 0u32;
1088 let mut bits = 0u8;
1089
1090 for &byte in input {
1091 if byte == b'=' {
1092 break;
1093 }
1094 let value = DECODE_TABLE[byte as usize];
1095 if value < 0 {
1096 continue; }
1098 buffer = (buffer << 6) | (value as u32);
1099 bits += 6;
1100 if bits >= 8 {
1101 bits -= 8;
1102 output.push((buffer >> bits) as u8);
1103 buffer &= (1 << bits) - 1;
1104 }
1105 }
1106
1107 Some(output)
1108 }
1109
1110 fn process_utf8(&mut self, byte: u8, collected: u8, expected: u8) -> Option<Event> {
1112 if (byte & 0xC0) != 0x80 {
1114 self.state = ParserState::Ground;
1117
1118 self.pending_event = self.process_ground(byte);
1120
1121 return Some(Event::Key(KeyEvent::new(KeyCode::Char(
1122 std::char::REPLACEMENT_CHARACTER,
1123 ))));
1124 }
1125
1126 self.utf8_buffer[collected as usize] = byte;
1127 let new_collected = collected + 1;
1128
1129 if new_collected == expected {
1130 self.state = ParserState::Ground;
1132 let s = std::str::from_utf8(&self.utf8_buffer[..expected as usize]).ok()?;
1133 let c = s.chars().next()?;
1134 Some(Event::Key(KeyEvent::new(KeyCode::Char(c))))
1135 } else {
1136 self.state = ParserState::Utf8 {
1138 collected: new_collected,
1139 expected,
1140 };
1141 None
1142 }
1143 }
1144
1145 fn process_mouse_x10(&mut self, byte: u8) -> Option<Event> {
1147 if let ParserState::MouseX10 {
1148 ref mut collected,
1149 ref mut buffer,
1150 } = self.state
1151 {
1152 buffer[*collected as usize] = byte;
1153 *collected += 1;
1154
1155 if *collected == 3 {
1156 let buf = *buffer;
1158 self.state = ParserState::Ground;
1159
1160 if buf[0] < 32 || buf[1] < 33 || buf[2] < 33 {
1164 return None;
1165 }
1166 let cb = buf[0].saturating_sub(32) as u16;
1167 let cx = buf[1].saturating_sub(33) as u16; let cy = buf[2].saturating_sub(33) as u16;
1169
1170 let (button, mods) = self.decode_mouse_button(cb);
1171
1172 let kind = if (cb & 3) == 3 {
1178 MouseEventKind::Up(MouseButton::Left)
1180 } else if cb & 64 != 0 {
1181 match cb & 3 {
1183 0 => MouseEventKind::ScrollUp,
1184 1 => MouseEventKind::ScrollDown,
1185 _ => MouseEventKind::ScrollUp,
1187 }
1188 } else {
1189 MouseEventKind::Down(button)
1191 };
1192
1193 return Some(Event::Mouse(MouseEvent {
1194 kind,
1195 x: cx,
1196 y: cy,
1197 modifiers: mods,
1198 }));
1199 }
1200 }
1201 None
1202 }
1203
1204 fn process_paste_byte(&mut self, byte: u8) -> Option<Event> {
1206 const END_SEQ: &[u8] = b"\x1b[201~";
1207
1208 if self.paste_buffer.len() < MAX_PASTE_LEN {
1214 self.paste_buffer.push(byte);
1215
1216 if self.paste_buffer.ends_with(END_SEQ) {
1218 self.in_paste = false;
1219 let content_len = self.paste_buffer.len() - END_SEQ.len();
1221 let content =
1222 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
1223 self.paste_buffer.clear();
1224 return Some(Event::Paste(PasteEvent::bracketed(content)));
1225 }
1226 } else {
1227 self.buffer.push(byte);
1232 if self.buffer.len() > END_SEQ.len() {
1233 self.buffer.remove(0);
1234 }
1235
1236 let mut last_bytes = [0u8; 6];
1243 let tail_len = self.buffer.len();
1244 let paste_len = self.paste_buffer.len();
1245
1246 if tail_len + paste_len >= 6 {
1247 for i in 0..tail_len {
1249 last_bytes[6 - tail_len + i] = self.buffer[i];
1250 }
1251 let remaining = 6 - tail_len;
1253 if remaining > 0 {
1254 let start = paste_len - remaining;
1255 last_bytes[..remaining]
1256 .copy_from_slice(&self.paste_buffer[start..(remaining + start)]);
1257 }
1258
1259 if last_bytes == END_SEQ {
1260 self.in_paste = false;
1261
1262 let content_len = paste_len - remaining;
1267 let content =
1268 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
1269
1270 self.paste_buffer.clear();
1271 self.buffer.clear();
1272
1273 return Some(Event::Paste(PasteEvent::bracketed(content)));
1274 }
1275 }
1276 }
1277
1278 None
1279 }
1280}
1281
1282#[cfg(test)]
1283mod tests {
1284 use super::*;
1285
1286 #[test]
1287 fn csi_ignore_handles_final_bytes() {
1288 let mut parser = InputParser::new();
1289
1290 let mut seq = vec![0x1B, b'['];
1293 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100)); seq.push(b'@'); let events = parser.parse(&seq);
1297 assert_eq!(events.len(), 0);
1298
1299 let events = parser.parse(b"a");
1302 assert_eq!(events.len(), 1, "Subsequent char 'a' was swallowed");
1303 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1304 }
1305
1306 #[test]
1307 fn ascii_characters_parsed() {
1308 let mut parser = InputParser::new();
1309
1310 let events = parser.parse(b"abc");
1311 assert_eq!(events.len(), 3);
1312 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1313 assert!(matches!(events[1], Event::Key(k) if k.code == KeyCode::Char('b')));
1314 assert!(matches!(events[2], Event::Key(k) if k.code == KeyCode::Char('c')));
1315 }
1316
1317 #[test]
1318 fn control_characters() {
1319 let mut parser = InputParser::new();
1320
1321 let events = parser.parse(&[0x01]);
1323 assert_eq!(events.len(), 1);
1324 assert!(matches!(
1325 events[0],
1326 Event::Key(k) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::CTRL)
1327 ));
1328
1329 let events = parser.parse(&[0x7F]);
1331 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace));
1332 }
1333
1334 #[test]
1335 fn arrow_keys() {
1336 let mut parser = InputParser::new();
1337
1338 assert!(matches!(
1339 parser.parse(b"\x1b[A").first(),
1340 Some(Event::Key(k)) if k.code == KeyCode::Up
1341 ));
1342 assert!(matches!(
1343 parser.parse(b"\x1b[B").first(),
1344 Some(Event::Key(k)) if k.code == KeyCode::Down
1345 ));
1346 assert!(matches!(
1347 parser.parse(b"\x1b[C").first(),
1348 Some(Event::Key(k)) if k.code == KeyCode::Right
1349 ));
1350 assert!(matches!(
1351 parser.parse(b"\x1b[D").first(),
1352 Some(Event::Key(k)) if k.code == KeyCode::Left
1353 ));
1354 }
1355
1356 #[test]
1357 fn c1_csi_arrow_keys() {
1358 let mut parser = InputParser::new();
1359
1360 assert!(matches!(
1361 parser.parse(&[0x9B, b'A']).first(),
1362 Some(Event::Key(k)) if k.code == KeyCode::Up
1363 ));
1364 assert!(matches!(
1365 parser.parse(&[0x9B, b'B']).first(),
1366 Some(Event::Key(k)) if k.code == KeyCode::Down
1367 ));
1368 }
1369
1370 #[test]
1371 fn c1_csi_mouse_sgr_protocol() {
1372 let mut parser = InputParser::new();
1373
1374 let events = parser.parse(&[0x9B, b'<', b'0', b';', b'1', b'0', b';', b'2', b'0', b'M']);
1375 assert!(matches!(
1376 events.first(),
1377 Some(Event::Mouse(m)) if m.x == 9 && m.y == 19
1378 ));
1379 }
1380
1381 #[test]
1382 fn function_keys_ss3() {
1383 let mut parser = InputParser::new();
1384
1385 assert!(matches!(
1386 parser.parse(b"\x1bOP").first(),
1387 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1388 ));
1389 assert!(matches!(
1390 parser.parse(b"\x1bOQ").first(),
1391 Some(Event::Key(k)) if k.code == KeyCode::F(2)
1392 ));
1393 assert!(matches!(
1394 parser.parse(b"\x1bOR").first(),
1395 Some(Event::Key(k)) if k.code == KeyCode::F(3)
1396 ));
1397 assert!(matches!(
1398 parser.parse(b"\x1bOS").first(),
1399 Some(Event::Key(k)) if k.code == KeyCode::F(4)
1400 ));
1401 }
1402
1403 #[test]
1404 fn function_keys_csi() {
1405 let mut parser = InputParser::new();
1406
1407 assert!(matches!(
1408 parser.parse(b"\x1b[15~").first(),
1409 Some(Event::Key(k)) if k.code == KeyCode::F(5)
1410 ));
1411 assert!(matches!(
1412 parser.parse(b"\x1b[17~").first(),
1413 Some(Event::Key(k)) if k.code == KeyCode::F(6)
1414 ));
1415 }
1416
1417 #[test]
1418 fn modifiers_in_csi() {
1419 let mut parser = InputParser::new();
1420
1421 let events = parser.parse(b"\x1b[1;2A");
1423 assert!(matches!(
1424 events.first(),
1425 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::SHIFT)
1426 ));
1427
1428 let events = parser.parse(b"\x1b[1;5A");
1430 assert!(matches!(
1431 events.first(),
1432 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::CTRL)
1433 ));
1434 }
1435
1436 #[test]
1437 fn modifiers_in_csi_alt_ctrl() {
1438 let mut parser = InputParser::new();
1439
1440 let events = parser.parse(b"\x1b[1;7A");
1442 assert!(matches!(
1443 events.first(),
1444 Some(Event::Key(k))
1445 if k.code == KeyCode::Up
1446 && k.modifiers.contains(Modifiers::ALT)
1447 && k.modifiers.contains(Modifiers::CTRL)
1448 ));
1449 }
1450
1451 #[test]
1452 fn kitty_keyboard_basic_char() {
1453 let mut parser = InputParser::new();
1454
1455 let events = parser.parse(b"\x1b[97u");
1456 assert!(matches!(
1457 events.first(),
1458 Some(Event::Key(k))
1459 if k.code == KeyCode::Char('a')
1460 && k.modifiers == Modifiers::NONE
1461 && k.kind == KeyEventKind::Press
1462 ));
1463 }
1464
1465 #[test]
1466 fn kitty_keyboard_with_modifiers_and_kind() {
1467 let mut parser = InputParser::new();
1468
1469 let events = parser.parse(b"\x1b[97;5:2u");
1471 assert!(matches!(
1472 events.first(),
1473 Some(Event::Key(k))
1474 if k.code == KeyCode::Char('a')
1475 && k.modifiers.contains(Modifiers::CTRL)
1476 && k.kind == KeyEventKind::Repeat
1477 ));
1478 }
1479
1480 #[test]
1481 fn kitty_keyboard_function_key() {
1482 let mut parser = InputParser::new();
1483
1484 let events = parser.parse(b"\x1b[57364;1u");
1485 assert!(matches!(
1486 events.first(),
1487 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1488 ));
1489 }
1490
1491 #[test]
1492 fn alt_key_escapes() {
1493 let mut parser = InputParser::new();
1494
1495 let events = parser.parse(b"\x1ba");
1496 assert!(matches!(
1497 events.first(),
1498 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::ALT)
1499 ));
1500 }
1501
1502 #[test]
1503 fn alt_backspace() {
1504 let mut parser = InputParser::new();
1505
1506 let events = parser.parse(b"\x1b\x7f");
1507 assert!(matches!(
1508 events.first(),
1509 Some(Event::Key(k))
1510 if k.code == KeyCode::Backspace && k.modifiers.contains(Modifiers::ALT)
1511 ));
1512 }
1513
1514 #[test]
1515 fn escape_escape_resets_state() {
1516 let mut parser = InputParser::new();
1517
1518 let events = parser.parse(b"\x1b\x1b");
1519 assert!(matches!(
1520 events.first(),
1521 Some(Event::Key(k)) if k.code == KeyCode::Escape && k.modifiers.contains(Modifiers::ALT)
1522 ));
1523
1524 let events = parser.parse(b"a");
1525 assert!(matches!(
1526 events.first(),
1527 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers == Modifiers::NONE
1528 ));
1529 }
1530
1531 #[test]
1532 fn focus_events() {
1533 let mut parser = InputParser::new();
1534
1535 assert!(matches!(
1536 parser.parse(b"\x1b[I").first(),
1537 Some(Event::Focus(true))
1538 ));
1539 assert!(matches!(
1540 parser.parse(b"\x1b[O").first(),
1541 Some(Event::Focus(false))
1542 ));
1543 }
1544
1545 #[test]
1546 fn bracketed_paste() {
1547 let mut parser = InputParser::new();
1548
1549 let events = parser.parse(b"\x1b[200~hello world\x1b[201~");
1551 assert_eq!(events.len(), 1);
1552 assert!(matches!(
1553 &events[0],
1554 Event::Paste(p) if p.text == "hello world"
1555 ));
1556 }
1557
1558 #[test]
1559 fn mouse_sgr_protocol() {
1560 let mut parser = InputParser::new();
1561
1562 let events = parser.parse(b"\x1b[<0;10;20M");
1564 assert!(matches!(
1565 events.first(),
1566 Some(Event::Mouse(m)) if m.x == 9 && m.y == 19 ));
1568 }
1569
1570 #[test]
1571 fn mouse_sgr_protocol_with_subparams() {
1572 let mut parser = InputParser::new();
1573
1574 let events = parser.parse(b"\x1b[<0:0;10:0;20:0M");
1576 assert!(matches!(
1577 events.first(),
1578 Some(Event::Mouse(m))
1579 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1580 && m.x == 9
1581 && m.y == 19
1582 ));
1583 }
1584
1585 #[test]
1586 fn mouse_sgr_protocol_large_coords_clamped() {
1587 let mut parser = InputParser::new();
1588
1589 let events = parser.parse(b"\x1b[<0;70000;80000M");
1591 assert!(matches!(
1592 events.first(),
1593 Some(Event::Mouse(m))
1594 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1595 && m.x == u16::MAX
1596 && m.y == u16::MAX
1597 ));
1598 }
1599
1600 #[test]
1601 fn mouse_sgr_protocol_negative_coords_clamped() {
1602 let mut parser = InputParser::new();
1603
1604 let events = parser.parse(b"\x1b[<0;-12;-3M");
1607 assert!(matches!(
1608 events.first(),
1609 Some(Event::Mouse(m))
1610 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1611 && m.x == 0
1612 && m.y == 0
1613 ));
1614 }
1615
1616 #[test]
1617 fn mouse_sgr_modifiers() {
1618 let mut parser = InputParser::new();
1619
1620 let events = parser.parse(b"\x1b[<28;3;4M");
1622 assert!(matches!(
1623 events.first(),
1624 Some(Event::Mouse(m))
1625 if m.modifiers.contains(Modifiers::SHIFT)
1626 && m.modifiers.contains(Modifiers::ALT)
1627 && m.modifiers.contains(Modifiers::CTRL)
1628 ));
1629 }
1630
1631 #[test]
1632 fn mouse_sgr_scroll_up() {
1633 let mut parser = InputParser::new();
1634
1635 let events = parser.parse(b"\x1b[<64;5;5M");
1637 assert!(matches!(
1638 events.first(),
1639 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollUp)
1640 ));
1641 }
1642
1643 #[test]
1644 fn mouse_sgr_scroll_down() {
1645 let mut parser = InputParser::new();
1646
1647 let events = parser.parse(b"\x1b[<65;5;5M");
1649 assert!(matches!(
1650 events.first(),
1651 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollDown)
1652 ));
1653 }
1654
1655 #[test]
1656 fn mouse_sgr_scroll_left() {
1657 let mut parser = InputParser::new();
1658
1659 let events = parser.parse(b"\x1b[<66;5;5M");
1661 assert!(matches!(
1662 events.first(),
1663 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollLeft)
1664 ));
1665 }
1666
1667 #[test]
1668 fn mouse_sgr_scroll_right() {
1669 let mut parser = InputParser::new();
1670
1671 let events = parser.parse(b"\x1b[<67;5;5M");
1673 assert!(matches!(
1674 events.first(),
1675 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollRight)
1676 ));
1677 }
1678
1679 #[test]
1680 fn mouse_sgr_drag_left() {
1681 let mut parser = InputParser::new();
1682
1683 let events = parser.parse(b"\x1b[<32;10;20M");
1685 assert!(matches!(
1686 events.first(),
1687 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Drag(MouseButton::Left))
1688 ));
1689 }
1690
1691 #[test]
1692 fn utf8_characters() {
1693 let mut parser = InputParser::new();
1694
1695 let events = parser.parse(&[0xC3, 0xA9]);
1697 assert!(matches!(
1698 events.first(),
1699 Some(Event::Key(k)) if k.code == KeyCode::Char('é')
1700 ));
1701 }
1702
1703 #[test]
1704 fn invalid_utf8_emits_replacement_then_reprocesses_byte() {
1705 let mut parser = InputParser::new();
1706
1707 let events = parser.parse(&[0xE2, 0x28]);
1709 assert_eq!(events.len(), 2);
1710 assert!(matches!(
1711 events[0],
1712 Event::Key(k) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)
1713 ));
1714 assert!(matches!(
1715 events[1],
1716 Event::Key(k) if k.code == KeyCode::Char('(')
1717 ));
1718 }
1719
1720 #[test]
1721 fn dos_protection_csi() {
1722 let mut parser = InputParser::new();
1723
1724 let mut seq = vec![0x1B, b'['];
1726 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
1727 seq.push(b'A');
1728
1729 let events = parser.parse(&seq);
1732 assert_eq!(
1733 events.len(),
1734 0,
1735 "Oversized CSI sequence should produce no events"
1736 );
1737
1738 let events = parser.parse(b"\x1b[A");
1741 assert!(matches!(
1742 events.first(),
1743 Some(Event::Key(k)) if k.code == KeyCode::Up
1744 ));
1745 }
1746
1747 #[test]
1748 fn incomplete_csi_sequence_emits_no_event() {
1749 let mut parser = InputParser::new();
1750 let events = parser.parse(b"\x1b[");
1751 assert!(events.is_empty());
1752 }
1753
1754 #[test]
1755 fn dos_protection_paste() {
1756 let mut parser = InputParser::new();
1757
1758 parser.parse(b"\x1b[200~");
1760
1761 let content = vec![b'x'; MAX_PASTE_LEN - 100]; parser.parse(&content);
1764
1765 let events = parser.parse(b"\x1b[201~");
1767
1768 assert!(matches!(
1770 events.first(),
1771 Some(Event::Paste(p)) if p.text.len() <= MAX_PASTE_LEN
1772 ));
1773 }
1774
1775 #[test]
1776 fn dos_protection_paste_overflow_terminator() {
1777 let mut parser = InputParser::new();
1778
1779 parser.parse(b"\x1b[200~");
1781
1782 let overflow = 100;
1786 let content = vec![b'a'; MAX_PASTE_LEN + overflow];
1787 parser.parse(&content);
1788
1789 let events = parser.parse(b"\x1b[201~");
1792
1793 assert_eq!(events.len(), 1, "Should emit paste event");
1794 match &events[0] {
1795 Event::Paste(p) => {
1796 assert_eq!(
1799 p.text.len(),
1800 MAX_PASTE_LEN,
1801 "Paste should be capped at MAX_PASTE_LEN bytes"
1802 );
1803 assert!(p.text.chars().all(|c| c == 'a'));
1805 }
1806 _ => unreachable!("Expected Paste event"),
1807 }
1808
1809 let events = parser.parse(b"b");
1811 assert_eq!(events.len(), 1);
1812 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('b')));
1813 }
1814
1815 #[test]
1816 fn no_panic_on_invalid_input() {
1817 let mut parser = InputParser::new();
1818
1819 let garbage = [0xFF, 0xFE, 0x00, 0x1B, 0x1B, 0x1B, b'[', 0xFF, b']', 0x00];
1821
1822 let _ = parser.parse(&garbage);
1824 }
1825
1826 #[test]
1827 fn dos_protection_paste_boundary() {
1828 let mut parser = InputParser::new();
1829 parser.parse(b"\x1b[200~");
1831
1832 let content = vec![b'x'; MAX_PASTE_LEN];
1834 parser.parse(&content);
1835
1836 let events = parser.parse(b"\x1b[201~");
1839
1840 assert!(
1841 !events.is_empty(),
1842 "Parser trapped in paste mode after hitting limit"
1843 );
1844 assert!(matches!(events[0], Event::Paste(_)));
1845 }
1846
1847 #[test]
1850 fn csi_tilde_home() {
1851 let mut parser = InputParser::new();
1852 let events = parser.parse(b"\x1b[1~");
1853 assert!(matches!(
1854 events.first(),
1855 Some(Event::Key(k)) if k.code == KeyCode::Home
1856 ));
1857 }
1858
1859 #[test]
1860 fn csi_tilde_insert() {
1861 let mut parser = InputParser::new();
1862 let events = parser.parse(b"\x1b[2~");
1863 assert!(matches!(
1864 events.first(),
1865 Some(Event::Key(k)) if k.code == KeyCode::Insert
1866 ));
1867 }
1868
1869 #[test]
1870 fn csi_tilde_delete() {
1871 let mut parser = InputParser::new();
1872 let events = parser.parse(b"\x1b[3~");
1873 assert!(matches!(
1874 events.first(),
1875 Some(Event::Key(k)) if k.code == KeyCode::Delete
1876 ));
1877 }
1878
1879 #[test]
1880 fn csi_tilde_end() {
1881 let mut parser = InputParser::new();
1882 let events = parser.parse(b"\x1b[4~");
1883 assert!(matches!(
1884 events.first(),
1885 Some(Event::Key(k)) if k.code == KeyCode::End
1886 ));
1887 }
1888
1889 #[test]
1890 fn csi_tilde_page_up() {
1891 let mut parser = InputParser::new();
1892 let events = parser.parse(b"\x1b[5~");
1893 assert!(matches!(
1894 events.first(),
1895 Some(Event::Key(k)) if k.code == KeyCode::PageUp
1896 ));
1897 }
1898
1899 #[test]
1900 fn csi_tilde_page_down() {
1901 let mut parser = InputParser::new();
1902 let events = parser.parse(b"\x1b[6~");
1903 assert!(matches!(
1904 events.first(),
1905 Some(Event::Key(k)) if k.code == KeyCode::PageDown
1906 ));
1907 }
1908
1909 #[test]
1912 fn csi_home_and_end() {
1913 let mut parser = InputParser::new();
1914 assert!(matches!(
1915 parser.parse(b"\x1b[H").first(),
1916 Some(Event::Key(k)) if k.code == KeyCode::Home
1917 ));
1918 assert!(matches!(
1919 parser.parse(b"\x1b[F").first(),
1920 Some(Event::Key(k)) if k.code == KeyCode::End
1921 ));
1922 }
1923
1924 #[test]
1927 fn ss3_home_and_end() {
1928 let mut parser = InputParser::new();
1929 assert!(matches!(
1930 parser.parse(b"\x1bOH").first(),
1931 Some(Event::Key(k)) if k.code == KeyCode::Home
1932 ));
1933 assert!(matches!(
1934 parser.parse(b"\x1bOF").first(),
1935 Some(Event::Key(k)) if k.code == KeyCode::End
1936 ));
1937 }
1938
1939 #[test]
1942 fn backtab_csi_z() {
1943 let mut parser = InputParser::new();
1944 let events = parser.parse(b"\x1b[Z");
1945 assert!(matches!(
1946 events.first(),
1947 Some(Event::Key(k)) if k.code == KeyCode::BackTab
1948 ));
1949 }
1950
1951 #[test]
1954 fn function_keys_f7_to_f12() {
1955 let mut parser = InputParser::new();
1956 assert!(matches!(
1957 parser.parse(b"\x1b[18~").first(),
1958 Some(Event::Key(k)) if k.code == KeyCode::F(7)
1959 ));
1960 assert!(matches!(
1961 parser.parse(b"\x1b[19~").first(),
1962 Some(Event::Key(k)) if k.code == KeyCode::F(8)
1963 ));
1964 assert!(matches!(
1965 parser.parse(b"\x1b[20~").first(),
1966 Some(Event::Key(k)) if k.code == KeyCode::F(9)
1967 ));
1968 assert!(matches!(
1969 parser.parse(b"\x1b[21~").first(),
1970 Some(Event::Key(k)) if k.code == KeyCode::F(10)
1971 ));
1972 assert!(matches!(
1973 parser.parse(b"\x1b[23~").first(),
1974 Some(Event::Key(k)) if k.code == KeyCode::F(11)
1975 ));
1976 assert!(matches!(
1977 parser.parse(b"\x1b[24~").first(),
1978 Some(Event::Key(k)) if k.code == KeyCode::F(12)
1979 ));
1980 }
1981
1982 #[test]
1985 fn ctrl_home_and_alt_end() {
1986 let mut parser = InputParser::new();
1987
1988 let events = parser.parse(b"\x1b[1;5H");
1990 assert!(matches!(
1991 events.first(),
1992 Some(Event::Key(k)) if k.code == KeyCode::Home && k.modifiers.contains(Modifiers::CTRL)
1993 ));
1994
1995 let events = parser.parse(b"\x1b[1;3F");
1997 assert!(matches!(
1998 events.first(),
1999 Some(Event::Key(k)) if k.code == KeyCode::End && k.modifiers.contains(Modifiers::ALT)
2000 ));
2001 }
2002
2003 #[test]
2004 fn shift_ctrl_arrow() {
2005 let mut parser = InputParser::new();
2006
2007 let events = parser.parse(b"\x1b[1;6C");
2009 assert!(matches!(
2010 events.first(),
2011 Some(Event::Key(k)) if k.code == KeyCode::Right
2012 && k.modifiers.contains(Modifiers::SHIFT)
2013 && k.modifiers.contains(Modifiers::CTRL)
2014 ));
2015 }
2016
2017 #[test]
2018 fn modifiers_on_tilde_keys() {
2019 let mut parser = InputParser::new();
2020
2021 let events = parser.parse(b"\x1b[3;5~");
2023 assert!(matches!(
2024 events.first(),
2025 Some(Event::Key(k)) if k.code == KeyCode::Delete && k.modifiers.contains(Modifiers::CTRL)
2026 ));
2027
2028 let events = parser.parse(b"\x1b[5;2~");
2030 assert!(matches!(
2031 events.first(),
2032 Some(Event::Key(k)) if k.code == KeyCode::PageUp && k.modifiers.contains(Modifiers::SHIFT)
2033 ));
2034 }
2035
2036 #[test]
2039 fn mouse_sgr_right_click() {
2040 let mut parser = InputParser::new();
2041 let events = parser.parse(b"\x1b[<2;15;10M");
2043 assert!(matches!(
2044 events.first(),
2045 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Right))
2046 && m.x == 14 && m.y == 9
2047 ));
2048 }
2049
2050 #[test]
2051 fn mouse_sgr_middle_click() {
2052 let mut parser = InputParser::new();
2053 let events = parser.parse(b"\x1b[<1;5;5M");
2055 assert!(matches!(
2056 events.first(),
2057 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Middle))
2058 ));
2059 }
2060
2061 #[test]
2062 fn mouse_sgr_button_release() {
2063 let mut parser = InputParser::new();
2064 let events = parser.parse(b"\x1b[<0;10;20m");
2066 assert!(matches!(
2067 events.first(),
2068 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Up(MouseButton::Left))
2069 ));
2070 }
2071
2072 #[test]
2073 fn mouse_sgr_button_release_uppercase_m_compat() {
2074 let mut parser = InputParser::new();
2075 let events = parser.parse(b"\x1b[<3;10;20M");
2078 assert!(matches!(
2079 events.first(),
2080 Some(Event::Mouse(m))
2081 if matches!(m.kind, MouseEventKind::Up(MouseButton::Left))
2082 && m.x == 9
2083 && m.y == 19
2084 ));
2085 }
2086
2087 #[test]
2088 fn mouse_sgr_moved() {
2089 let mut parser = InputParser::new();
2090 let events = parser.parse(b"\x1b[<35;10;20M");
2092 assert!(matches!(
2093 events.first(),
2094 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Moved)
2095 ));
2096 }
2097
2098 #[test]
2099 fn mouse_sgr_with_modifiers() {
2100 let mut parser = InputParser::new();
2101 let events = parser.parse(b"\x1b[<4;5;5M");
2103 assert!(matches!(
2104 events.first(),
2105 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2106 && m.modifiers.contains(Modifiers::SHIFT)
2107 ));
2108
2109 let events = parser.parse(b"\x1b[<16;5;5M");
2111 assert!(matches!(
2112 events.first(),
2113 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2114 && m.modifiers.contains(Modifiers::CTRL)
2115 ));
2116
2117 let events = parser.parse(b"\x1b[<8;5;5M");
2119 assert!(matches!(
2120 events.first(),
2121 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2122 && m.modifiers.contains(Modifiers::ALT)
2123 ));
2124 }
2125
2126 #[test]
2127 fn mouse_legacy_1015_when_enabled() {
2128 let mut parser = InputParser::new();
2129 parser.set_expect_x10_mouse(true);
2130
2131 let events = parser.parse(b"\x1b[0;10;20M");
2132 assert!(matches!(
2133 events.first(),
2134 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2135 && m.x == 9 && m.y == 19
2136 ));
2137 }
2138
2139 #[test]
2140 fn mouse_legacy_1015_with_fallback_enabled() {
2141 let mut parser = InputParser::new();
2142 parser.set_allow_legacy_mouse(true);
2143
2144 let events = parser.parse(b"\x1b[0;10;20M");
2145 assert!(matches!(
2146 events.first(),
2147 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2148 && m.x == 9 && m.y == 19
2149 ));
2150 }
2151
2152 #[test]
2153 fn mouse_legacy_1015_ignored_when_disabled() {
2154 let mut parser = InputParser::new();
2155 let events = parser.parse(b"\x1b[0;10;20M");
2156 assert!(
2157 events.is_empty(),
2158 "legacy mouse should require explicit opt-in"
2159 );
2160 }
2161
2162 #[test]
2163 fn mouse_x10_when_enabled() {
2164 let mut parser = InputParser::new();
2165 parser.set_expect_x10_mouse(true);
2166
2167 let events = parser.parse(&[0x1B, b'[', b'M', 32, 42, 52]);
2168 assert!(matches!(
2169 events.first(),
2170 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2171 && m.x == 9 && m.y == 19
2172 ));
2173 }
2174
2175 #[test]
2176 fn mouse_x10_malformed_packet_ignored() {
2177 let mut parser = InputParser::new();
2178 parser.set_expect_x10_mouse(true);
2179
2180 let events = parser.parse(&[0x1B, b'[', b'M', 31, 0, 10]);
2182 assert!(
2183 events.iter().all(|event| !matches!(event, Event::Mouse(_))),
2184 "malformed X10 payload must not emit mouse events"
2185 );
2186 }
2187
2188 #[test]
2191 fn kitty_keyboard_release_event() {
2192 let mut parser = InputParser::new();
2193 let events = parser.parse(b"\x1b[97;1:3u");
2195 assert!(matches!(
2196 events.first(),
2197 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.kind == KeyEventKind::Release
2198 ));
2199
2200 let events = parser.parse(b"\x1b[65;5:3u");
2202 assert!(matches!(
2203 events.first(),
2204 Some(Event::Key(k))
2205 if k.code == KeyCode::Char('A')
2206 && k.modifiers.contains(Modifiers::CTRL)
2207 && k.kind == KeyEventKind::Release
2208 ));
2209 }
2210
2211 #[test]
2212 fn kitty_keyboard_special_keys() {
2213 let mut parser = InputParser::new();
2214
2215 assert!(matches!(
2217 parser.parse(b"\x1b[57344u").first(),
2218 Some(Event::Key(k)) if k.code == KeyCode::Escape
2219 ));
2220
2221 assert!(matches!(
2223 parser.parse(b"\x1b[57345u").first(),
2224 Some(Event::Key(k)) if k.code == KeyCode::Enter
2225 ));
2226
2227 assert!(matches!(
2229 parser.parse(b"\x1b[57346u").first(),
2230 Some(Event::Key(k)) if k.code == KeyCode::Tab
2231 ));
2232
2233 assert!(matches!(
2235 parser.parse(b"\x1b[57347u").first(),
2236 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2237 ));
2238
2239 assert!(matches!(
2241 parser.parse(b"\x1b[57348u").first(),
2242 Some(Event::Key(k)) if k.code == KeyCode::Insert
2243 ));
2244
2245 assert!(matches!(
2247 parser.parse(b"\x1b[57349u").first(),
2248 Some(Event::Key(k)) if k.code == KeyCode::Delete
2249 ));
2250 }
2251
2252 #[test]
2253 fn kitty_keyboard_navigation_keys() {
2254 let mut parser = InputParser::new();
2255
2256 assert!(matches!(
2258 parser.parse(b"\x1b[57350u").first(),
2259 Some(Event::Key(k)) if k.code == KeyCode::Left
2260 ));
2261 assert!(matches!(
2263 parser.parse(b"\x1b[57351u").first(),
2264 Some(Event::Key(k)) if k.code == KeyCode::Right
2265 ));
2266 assert!(matches!(
2268 parser.parse(b"\x1b[57352u").first(),
2269 Some(Event::Key(k)) if k.code == KeyCode::Up
2270 ));
2271 assert!(matches!(
2273 parser.parse(b"\x1b[57353u").first(),
2274 Some(Event::Key(k)) if k.code == KeyCode::Down
2275 ));
2276 assert!(matches!(
2278 parser.parse(b"\x1b[57354u").first(),
2279 Some(Event::Key(k)) if k.code == KeyCode::PageUp
2280 ));
2281 assert!(matches!(
2283 parser.parse(b"\x1b[57355u").first(),
2284 Some(Event::Key(k)) if k.code == KeyCode::PageDown
2285 ));
2286 assert!(matches!(
2288 parser.parse(b"\x1b[57356u").first(),
2289 Some(Event::Key(k)) if k.code == KeyCode::Home
2290 ));
2291 assert!(matches!(
2293 parser.parse(b"\x1b[57357u").first(),
2294 Some(Event::Key(k)) if k.code == KeyCode::End
2295 ));
2296 }
2297
2298 #[test]
2299 fn kitty_keyboard_f_keys() {
2300 let mut parser = InputParser::new();
2301 assert!(matches!(
2303 parser.parse(b"\x1b[57364u").first(),
2304 Some(Event::Key(k)) if k.code == KeyCode::F(1)
2305 ));
2306 assert!(matches!(
2308 parser.parse(b"\x1b[57375u").first(),
2309 Some(Event::Key(k)) if k.code == KeyCode::F(12)
2310 ));
2311 assert!(matches!(
2313 parser.parse(b"\x1b[57387u").first(),
2314 Some(Event::Key(k)) if k.code == KeyCode::F(24)
2315 ));
2316 }
2317
2318 #[test]
2319 fn kitty_keyboard_ascii_as_standard() {
2320 let mut parser = InputParser::new();
2321 assert!(matches!(
2323 parser.parse(b"\x1b[9u").first(),
2324 Some(Event::Key(k)) if k.code == KeyCode::Tab
2325 ));
2326 assert!(matches!(
2327 parser.parse(b"\x1b[13u").first(),
2328 Some(Event::Key(k)) if k.code == KeyCode::Enter
2329 ));
2330 assert!(matches!(
2331 parser.parse(b"\x1b[27u").first(),
2332 Some(Event::Key(k)) if k.code == KeyCode::Escape
2333 ));
2334 assert!(matches!(
2335 parser.parse(b"\x1b[127u").first(),
2336 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2337 ));
2338 assert!(matches!(
2340 parser.parse(b"\x1b[8u").first(),
2341 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2342 ));
2343 }
2344
2345 #[test]
2348 fn osc52_clipboard_bel_terminated() {
2349 let mut parser = InputParser::new();
2350 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x07");
2353 assert!(matches!(
2354 events.first(),
2355 Some(Event::Clipboard(c)) if c.content == "hello" && c.source == ClipboardSource::Osc52
2356 ));
2357 }
2358
2359 #[test]
2360 fn osc52_clipboard_st_terminated() {
2361 let mut parser = InputParser::new();
2362 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x1b\\");
2364 assert!(matches!(
2365 events.first(),
2366 Some(Event::Clipboard(c)) if c.content == "hello"
2367 ));
2368 }
2369
2370 #[test]
2371 fn osc52_clipboard_primary_selection() {
2372 let mut parser = InputParser::new();
2373 let events = parser.parse(b"\x1b]52;p;YWJj\x07");
2376 assert!(matches!(
2377 events.first(),
2378 Some(Event::Clipboard(c)) if c.content == "abc"
2379 ));
2380 }
2381
2382 #[test]
2385 fn ctrl_space_is_null() {
2386 let mut parser = InputParser::new();
2387 let events = parser.parse(&[0x00]);
2388 assert!(matches!(
2389 events.first(),
2390 Some(Event::Key(k)) if k.code == KeyCode::Null
2391 ));
2392 }
2393
2394 #[test]
2395 fn all_ctrl_letter_keys() {
2396 let mut parser = InputParser::new();
2397 for byte in 0x01..=0x1Au8 {
2399 let events = parser.parse(&[byte]);
2400 assert_eq!(
2401 events.len(),
2402 1,
2403 "Ctrl+{} should produce one event",
2404 (byte + b'a' - 1) as char
2405 );
2406 match byte {
2407 0x08 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace)),
2408 0x09 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Tab)),
2409 0x0D => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Enter)),
2410 _ => {
2411 let expected_char = (byte + b'a' - 1) as char;
2412 match &events[0] {
2413 Event::Key(k) => {
2414 assert_eq!(
2415 k.code,
2416 KeyCode::Char(expected_char),
2417 "Byte 0x{byte:02X} should produce Ctrl+{expected_char}"
2418 );
2419 assert!(
2420 k.modifiers.contains(Modifiers::CTRL),
2421 "Byte 0x{byte:02X} should have Ctrl modifier"
2422 );
2423 }
2424 other => {
2425 panic!("Byte 0x{byte:02X}: expected Key event, got {other:?}");
2426 }
2427 }
2428 }
2429 }
2430 }
2431 }
2432
2433 #[test]
2436 fn utf8_3byte_cjk() {
2437 let mut parser = InputParser::new();
2438 let events = parser.parse(&[0xE4, 0xB8, 0xAD]);
2440 assert!(matches!(
2441 events.first(),
2442 Some(Event::Key(k)) if k.code == KeyCode::Char('中')
2443 ));
2444 }
2445
2446 #[test]
2447 fn utf8_4byte_emoji() {
2448 let mut parser = InputParser::new();
2449 let events = parser.parse(&[0xF0, 0x9F, 0xA6, 0x80]);
2451 assert!(matches!(
2452 events.first(),
2453 Some(Event::Key(k)) if k.code == KeyCode::Char('🦀')
2454 ));
2455 }
2456
2457 #[test]
2460 fn empty_input_returns_no_events() {
2461 let mut parser = InputParser::new();
2462 let events = parser.parse(b"");
2463 assert!(events.is_empty());
2464 }
2465
2466 #[test]
2469 fn unknown_csi_tilde_ignored() {
2470 let mut parser = InputParser::new();
2471 let events = parser.parse(b"\x1b[99~");
2473 assert!(events.is_empty());
2474
2475 let events = parser.parse(b"a");
2477 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('a')));
2478 }
2479
2480 #[test]
2483 fn alt_special_chars() {
2484 let mut parser = InputParser::new();
2485
2486 let events = parser.parse(b"\x1b ");
2488 assert!(matches!(
2489 events.first(),
2490 Some(Event::Key(k)) if k.code == KeyCode::Char(' ') && k.modifiers.contains(Modifiers::ALT)
2491 ));
2492
2493 let events = parser.parse(b"\x1b5");
2495 assert!(matches!(
2496 events.first(),
2497 Some(Event::Key(k)) if k.code == KeyCode::Char('5') && k.modifiers.contains(Modifiers::ALT)
2498 ));
2499
2500 let events = parser.parse(b"\x1b}");
2502 assert!(matches!(
2503 events.first(),
2504 Some(Event::Key(k)) if k.code == KeyCode::Char('}') && k.modifiers.contains(Modifiers::ALT)
2505 ));
2506 }
2507
2508 #[test]
2509 fn alt_ctrl_key_combinations() {
2510 let mut parser = InputParser::new();
2511
2512 let events = parser.parse(&[0x1B, 0x01]);
2514 assert_eq!(events.len(), 1);
2515 match &events[0] {
2516 Event::Key(k) => {
2517 assert_eq!(k.code, KeyCode::Char('a'));
2518 assert!(k.modifiers.contains(Modifiers::ALT));
2519 assert!(k.modifiers.contains(Modifiers::CTRL));
2520 }
2521 _ => panic!("Expected Key event"),
2522 }
2523
2524 let events = parser.parse(&[0x1B, 0x08]);
2528 assert_eq!(events.len(), 1);
2529 match &events[0] {
2530 Event::Key(k) => {
2531 assert_eq!(k.code, KeyCode::Backspace);
2532 assert!(k.modifiers.contains(Modifiers::ALT));
2533 }
2534 _ => panic!("Expected Key event"),
2535 }
2536 }
2537
2538 #[test]
2541 fn ss3_arrow_keys() {
2542 let mut parser = InputParser::new();
2543 assert!(matches!(
2544 parser.parse(b"\x1bOA").first(),
2545 Some(Event::Key(k)) if k.code == KeyCode::Up
2546 ));
2547 assert!(matches!(
2548 parser.parse(b"\x1bOB").first(),
2549 Some(Event::Key(k)) if k.code == KeyCode::Down
2550 ));
2551 assert!(matches!(
2552 parser.parse(b"\x1bOC").first(),
2553 Some(Event::Key(k)) if k.code == KeyCode::Right
2554 ));
2555 assert!(matches!(
2556 parser.parse(b"\x1bOD").first(),
2557 Some(Event::Key(k)) if k.code == KeyCode::Left
2558 ));
2559 }
2560
2561 #[test]
2564 fn xterm_modifier_encoding() {
2565 assert_eq!(InputParser::modifiers_from_xterm(1), Modifiers::NONE);
2567 assert_eq!(InputParser::modifiers_from_xterm(2), Modifiers::SHIFT);
2568 assert_eq!(InputParser::modifiers_from_xterm(3), Modifiers::ALT);
2569 assert_eq!(
2570 InputParser::modifiers_from_xterm(4),
2571 Modifiers::SHIFT | Modifiers::ALT
2572 );
2573 assert_eq!(InputParser::modifiers_from_xterm(5), Modifiers::CTRL);
2574 assert_eq!(
2575 InputParser::modifiers_from_xterm(6),
2576 Modifiers::SHIFT | Modifiers::CTRL
2577 );
2578 assert_eq!(InputParser::modifiers_from_xterm(9), Modifiers::SUPER);
2579 }
2580
2581 #[test]
2584 fn ss3_interrupted_by_esc() {
2585 let mut parser = InputParser::new();
2586 let events = parser.parse(b"\x1bO\x1b[A");
2588 assert!(matches!(
2590 events.first(),
2591 Some(Event::Key(k)) if k.code == KeyCode::Up
2592 ));
2593 }
2594
2595 #[test]
2598 fn kitty_keyboard_reserved_keycode_ignored() {
2599 let mut parser = InputParser::new();
2600 let events = parser.parse(b"\x1b[57360u");
2602 assert!(events.is_empty());
2603
2604 let events = parser.parse(b"x");
2606 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('x')));
2607 }
2608 #[test]
2609 fn utf8_invalid_sequence_emits_replacement() {
2610 let mut parser = InputParser::new();
2611
2612 let events = parser.parse(&[0xE0, 0x41]);
2616 assert_eq!(events.len(), 2);
2617
2618 match &events[0] {
2619 Event::Key(k) => assert_eq!(k.code, KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2620 _ => panic!("Expected replacement character"),
2621 }
2622
2623 match &events[1] {
2624 Event::Key(k) => assert_eq!(k.code, KeyCode::Char('A')),
2625 _ => panic!("Expected character 'A'"),
2626 }
2627 }
2628
2629 #[test]
2630 fn utf8_invalid_lead_emits_replacement() {
2631 let mut parser = InputParser::new();
2632
2633 let events = parser.parse(&[0xC0, b'a']);
2635 assert!(
2636 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2637 "Expected replacement for invalid lead"
2638 );
2639 assert!(
2640 events
2641 .iter()
2642 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('a'))),
2643 "Expected subsequent ASCII to be preserved"
2644 );
2645
2646 let events = parser.parse(&[0xF5, b'b']);
2648 assert!(
2649 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2650 "Expected replacement for out-of-range lead"
2651 );
2652 assert!(
2653 events
2654 .iter()
2655 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('b'))),
2656 "Expected subsequent ASCII to be preserved"
2657 );
2658 }
2659}
2660
2661#[cfg(test)]
2662mod proptest_fuzz {
2663 use super::*;
2664 use proptest::prelude::*;
2665
2666 fn arb_byte() -> impl Strategy<Value = u8> {
2670 any::<u8>()
2671 }
2672
2673 fn arb_byte_vec(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
2674 prop::collection::vec(arb_byte(), 0..=max_len)
2675 }
2676
2677 fn csi_sequence() -> impl Strategy<Value = Vec<u8>> {
2679 let params = prop::collection::vec(0x30u8..=0x3F, 0..=20);
2680 let final_byte = 0x40u8..=0x7E;
2681 (params, final_byte).prop_map(|(p, f)| {
2682 let mut buf = vec![0x1B, b'['];
2683 buf.extend_from_slice(&p);
2684 buf.push(f);
2685 buf
2686 })
2687 }
2688
2689 fn osc_sequence() -> impl Strategy<Value = Vec<u8>> {
2691 let content = prop::collection::vec(0x20u8..=0x7E, 0..=64);
2692 let terminator = prop_oneof![
2693 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
2696 (content, terminator).prop_map(|(c, t)| {
2697 let mut buf = vec![0x1B, b']'];
2698 buf.extend_from_slice(&c);
2699 buf.extend_from_slice(&t);
2700 buf
2701 })
2702 }
2703
2704 fn ss3_sequence() -> impl Strategy<Value = Vec<u8>> {
2706 (0x40u8..=0x7E).prop_map(|f| vec![0x1B, b'O', f])
2707 }
2708
2709 fn paste_sequence() -> impl Strategy<Value = Vec<u8>> {
2711 prop::collection::vec(0x20u8..=0x7E, 0..=128).prop_map(|content| {
2712 let mut buf = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
2713 buf.extend_from_slice(&content);
2714 buf.extend_from_slice(b"\x1b[201~");
2715 buf
2716 })
2717 }
2718
2719 fn mixed_adversarial() -> impl Strategy<Value = Vec<u8>> {
2721 let fragment = prop_oneof![
2722 csi_sequence(),
2723 osc_sequence(),
2724 ss3_sequence(),
2725 paste_sequence(),
2726 arb_byte_vec(16), Just(vec![0x1B]), Just(vec![0x1B, b'[']), Just(vec![0x1B, b']']), prop::collection::vec(0x80u8..=0xFF, 1..=4), ];
2732 prop::collection::vec(fragment, 1..=8)
2733 .prop_map(|frags| frags.into_iter().flatten().collect())
2734 }
2735
2736 proptest! {
2739 #[test]
2741 fn random_bytes_never_panic(input in arb_byte_vec(512)) {
2742 let mut parser = InputParser::new();
2743 let _ = parser.parse(&input);
2744 }
2745
2746 #[test]
2748 fn parser_recovers_after_garbage(input in arb_byte_vec(256)) {
2749 let mut parser = InputParser::new();
2750 let _ = parser.parse(&input);
2751
2752 let events = parser.parse(b"z");
2754 let _ = events;
2757 }
2758
2759 #[test]
2761 fn mixed_sequences_never_panic(input in mixed_adversarial()) {
2762 let mut parser = InputParser::new();
2763 let _ = parser.parse(&input);
2764 }
2765
2766 #[test]
2768 fn events_are_well_formed(input in arb_byte_vec(256)) {
2769 let mut parser = InputParser::new();
2770 let events = parser.parse(&input);
2771 for event in &events {
2772 let _ = format!("{event:?}");
2774 }
2775 }
2776
2777 #[test]
2779 fn csi_event_count_bounded(seq in csi_sequence()) {
2780 let mut parser = InputParser::new();
2781 let events = parser.parse(&seq);
2782 prop_assert!(events.len() <= seq.len(),
2783 "Got {} events from {} bytes", events.len(), seq.len());
2784 }
2785
2786 #[test]
2788 fn osc_event_count_bounded(seq in osc_sequence()) {
2789 let mut parser = InputParser::new();
2790 let events = parser.parse(&seq);
2791 prop_assert!(events.len() <= seq.len(),
2792 "Got {} events from {} bytes", events.len(), seq.len());
2793 }
2794
2795 #[test]
2797 fn paste_content_bounded(content in prop::collection::vec(arb_byte(), 0..=2048)) {
2798 let mut parser = InputParser::new();
2799 let mut input = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
2800 input.extend_from_slice(&content);
2801 input.extend_from_slice(b"\x1b[201~");
2802
2803 let events = parser.parse(&input);
2804 for event in &events {
2805 if let Event::Paste(p) = event {
2806 prop_assert!(p.text.len() <= MAX_PASTE_LEN,
2807 "Paste text {} exceeds limit {}", p.text.len(), MAX_PASTE_LEN);
2808 }
2809 }
2810 }
2811
2812 #[test]
2814 fn incremental_matches_bulk(input in arb_byte_vec(128)) {
2815 let mut bulk_parser = InputParser::new();
2816 let bulk_events = bulk_parser.parse(&input);
2817
2818 let mut incr_parser = InputParser::new();
2819 let mut incr_events = Vec::new();
2820 for byte in &input {
2821 incr_events.extend(incr_parser.parse(std::slice::from_ref(byte)));
2822 }
2823
2824 let bulk_dbg: Vec<String> = bulk_events.iter().map(|e| format!("{e:?}")).collect();
2825 let incr_dbg: Vec<String> = incr_events.iter().map(|e| format!("{e:?}")).collect();
2826 prop_assert_eq!(bulk_dbg, incr_dbg,
2827 "Bulk vs incremental mismatch for input {:?}", input);
2828 }
2829
2830 #[test]
2833 fn deterministic_output(input in arb_byte_vec(128)) {
2834 let mut parser1 = InputParser::new();
2835 let events1 = parser1.parse(&input);
2836
2837 let mut parser2 = InputParser::new();
2838 let events2 = parser2.parse(&input);
2839
2840 let dbg1: Vec<String> = events1.iter().map(|e| format!("{e:?}")).collect();
2841 let dbg2: Vec<String> = events2.iter().map(|e| format!("{e:?}")).collect();
2842 prop_assert_eq!(dbg1, dbg2);
2843 }
2844 }
2845
2846 #[test]
2850 fn no_quadratic_blowup() {
2851 let mut parser = InputParser::new();
2852
2853 let garbage: Vec<u8> = (0..65536).map(|i| (i % 256) as u8).collect();
2855 let _ = parser.parse(&garbage);
2856
2857 let events = parser.parse(b"a");
2859 let _ = events; }
2861
2862 #[test]
2864 fn oversized_csi_transitions_to_ignore() {
2865 let mut parser = InputParser::new();
2866
2867 let mut input = vec![0x1B, b'['];
2869 input.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
2870 input.push(b'm');
2871
2872 let _ = parser.parse(&input);
2873
2874 let events = parser.parse(b"x");
2876 assert_eq!(events.len(), 1);
2877 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('x')));
2878 }
2879
2880 #[test]
2882 fn oversized_osc_transitions_to_ignore() {
2883 let mut parser = InputParser::new();
2884
2885 let mut input = vec![0x1B, b']'];
2887 input.extend(std::iter::repeat_n(b'a', MAX_OSC_LEN + 100));
2888 input.push(0x07); let _ = parser.parse(&input);
2891
2892 let events = parser.parse(b"y");
2894 assert_eq!(events.len(), 1);
2895 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('y')));
2896 }
2897
2898 #[test]
2900 fn rapid_esc_toggle() {
2901 let mut parser = InputParser::new();
2902
2903 let input: Vec<u8> = vec![0x1B; 1000];
2905 let _ = parser.parse(&input);
2906
2907 let events = parser.parse(b"k");
2909 assert!(!events.is_empty());
2910 }
2911
2912 #[test]
2914 fn unterminated_paste_recovery() {
2915 let mut parser = InputParser::new();
2916
2917 let mut input = b"\x1b[200~".to_vec();
2919 input.extend(std::iter::repeat_n(b'x', 2048));
2920
2921 let _ = parser.parse(&input);
2922
2923 let events = parser.parse(b"\x1b[201~");
2925 assert!(
2926 !events.is_empty(),
2927 "Parser should emit paste event on terminator"
2928 );
2929 }
2930
2931 #[test]
2933 fn truncated_utf8_lead_bytes() {
2934 let mut parser = InputParser::new();
2935
2936 for lead in [0xC2, 0xE0, 0xF0] {
2938 let _ = parser.parse(&[lead]);
2939 let events = parser.parse(b"a");
2941 let _ = events;
2943 }
2944 }
2945
2946 #[test]
2948 fn null_bytes_interleaved() {
2949 let mut parser = InputParser::new();
2950
2951 let input = b"\x00A\x00\x1b[A\x00B\x00";
2952 let events = parser.parse(input);
2953 assert!(
2955 events.len() >= 2,
2956 "Expected at least 2 events, got {}",
2957 events.len()
2958 );
2959 }
2960
2961 fn osc52_sequence() -> impl Strategy<Value = Vec<u8>> {
2965 let selector = prop_oneof![Just(b'c'), Just(b'p'), Just(b's')];
2966 let payload = prop::collection::vec(
2968 prop_oneof![
2969 0x41u8..=0x5A, 0x61u8..=0x7A, 0x30u8..=0x39, Just(b'+'),
2973 Just(b'/'),
2974 Just(b'='),
2975 ],
2976 0..=128,
2977 );
2978 let terminator = prop_oneof![
2979 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
2982 (selector, payload, terminator).prop_map(|(sel, pay, term)| {
2983 let mut buf = vec![0x1B, b']', b'5', b'2', b';', sel, b';'];
2984 buf.extend_from_slice(&pay);
2985 buf.extend_from_slice(&term);
2986 buf
2987 })
2988 }
2989
2990 fn sgr_mouse_sequence() -> impl Strategy<Value = Vec<u8>> {
2992 let button_code = 0u16..128;
2993 let x = 1u16..300;
2994 let y = 1u16..100;
2995 let final_byte = prop_oneof![Just(b'M'), Just(b'm')];
2996 (button_code, x, y, final_byte)
2997 .prop_map(|(btn, x, y, fb)| format!("\x1b[<{btn};{x};{y}{}", fb as char).into_bytes())
2998 }
2999
3000 fn kitty_keyboard_sequence() -> impl Strategy<Value = Vec<u8>> {
3002 let keycode = prop_oneof![
3003 0x20u32..0x7F, 0x57344u32..0x57400, 0x100u32..0x200, ];
3007 let modifier = 1u32..16;
3008 let kind = prop_oneof![Just(1u32), Just(2u32), Just(3u32)]; (keycode, prop::option::of(modifier), prop::option::of(kind)).prop_map(
3010 |(kc, mods, kind)| match (mods, kind) {
3011 (Some(m), Some(k)) => format!("\x1b[{kc};{m}:{k}u").into_bytes(),
3012 (Some(m), None) => format!("\x1b[{kc};{m}u").into_bytes(),
3013 _ => format!("\x1b[{kc}u").into_bytes(),
3014 },
3015 )
3016 }
3017
3018 proptest! {
3019 #[test]
3023 fn osc52_never_panics(seq in osc52_sequence()) {
3024 let mut parser = InputParser::new();
3025 let events = parser.parse(&seq);
3026 for event in &events {
3028 if let Event::Clipboard(c) = event {
3029 prop_assert!(!c.content.is_empty() || c.content.is_empty(),
3030 "Clipboard event must have a content field");
3031 }
3032 }
3033 }
3034
3035 #[test]
3037 fn osc52_corrupt_base64_safe(payload in arb_byte_vec(128)) {
3038 let mut parser = InputParser::new();
3039 let mut input = b"\x1b]52;c;".to_vec();
3040 input.extend_from_slice(&payload);
3041 input.push(0x07); let _ = parser.parse(&input);
3043 }
3044
3045 #[test]
3049 fn sgr_mouse_never_panics(seq in sgr_mouse_sequence()) {
3050 let mut parser = InputParser::new();
3051 let events = parser.parse(&seq);
3052 for event in &events {
3053 let _ = format!("{event:?}");
3055 }
3056 }
3057
3058 #[test]
3060 fn sgr_mouse_extreme_coords(
3061 btn in 0u16..128,
3062 x in 0u16..=65535,
3063 y in 0u16..=65535,
3064 ) {
3065 let mut parser = InputParser::new();
3066 let input = format!("\x1b[<{btn};{x};{y}M").into_bytes();
3067 let events = parser.parse(&input);
3068 for event in &events {
3069 if let Event::Mouse(m) = event {
3070 prop_assert!(m.x <= x, "Mouse x {} > input x {}", m.x, x);
3071 prop_assert!(m.y <= y, "Mouse y {} > input y {}", m.y, y);
3072 }
3073 }
3074 }
3075
3076 #[test]
3080 fn kitty_keyboard_never_panics(seq in kitty_keyboard_sequence()) {
3081 let mut parser = InputParser::new();
3082 let _ = parser.parse(&seq);
3083 }
3084
3085 #[test]
3089 fn truncated_csi_then_valid(
3090 params in prop::collection::vec(0x30u8..=0x3F, 1..=10),
3091 valid_char in 0x20u8..0x7F,
3092 ) {
3093 let mut parser = InputParser::new();
3094
3095 let mut partial = vec![0x1B, b'['];
3097 partial.extend_from_slice(¶ms);
3098 let _ = parser.parse(&partial);
3099
3100 let events = parser.parse(&[0x1B, b'[', b'A']); let _ = events;
3105
3106 let events = parser.parse(&[valid_char]);
3108 let _ = events;
3109 }
3110
3111 #[test]
3113 fn truncated_osc_then_valid(
3114 content in prop::collection::vec(0x20u8..=0x7E, 1..=32),
3115 ) {
3116 let mut parser = InputParser::new();
3117
3118 let mut partial = vec![0x1B, b']'];
3120 partial.extend_from_slice(&content);
3121 let _ = parser.parse(&partial);
3122
3123 let events = parser.parse(b"\x1bz");
3125 let _ = events;
3126 }
3127
3128 #[test]
3132 fn csi_near_limit_produces_event(
3133 fill_byte in 0x30u8..=0x39, ) {
3135 let mut parser = InputParser::new();
3136
3137 let mut input = vec![0x1B, b'['];
3138 input.extend(std::iter::repeat_n(fill_byte, MAX_CSI_LEN - 1));
3140 input.push(b'm'); let events = parser.parse(&input);
3143 let _ = events;
3146
3147 let events = parser.parse(b"a");
3149 prop_assert!(!events.is_empty(), "Parser stuck after near-limit CSI");
3150 }
3151
3152 #[test]
3154 fn osc_near_limit_processes(
3155 fill_byte in 0x20u8..=0x7E,
3156 ) {
3157 let mut parser = InputParser::new();
3158
3159 let mut input = vec![0x1B, b']'];
3160 input.extend(std::iter::repeat_n(fill_byte, MAX_OSC_LEN - 1));
3161 input.push(0x07); let _ = parser.parse(&input);
3164
3165 let events = parser.parse(b"b");
3167 prop_assert!(!events.is_empty(), "Parser stuck after near-limit OSC");
3168 }
3169
3170 #[test]
3174 fn consecutive_pastes_emit_events(count in 2usize..=5) {
3175 let mut parser = InputParser::new();
3176 let mut input = Vec::new();
3177
3178 for i in 0..count {
3179 input.extend_from_slice(b"\x1b[200~");
3180 input.extend_from_slice(format!("paste_{i}").as_bytes());
3181 input.extend_from_slice(b"\x1b[201~");
3182 }
3183
3184 let events = parser.parse(&input);
3185 let paste_events: Vec<_> = events.iter()
3186 .filter(|e| matches!(e, Event::Paste(_)))
3187 .collect();
3188
3189 prop_assert_eq!(paste_events.len(), count,
3190 "Expected {} paste events, got {}", count, paste_events.len());
3191 }
3192
3193 #[test]
3195 fn paste_with_invalid_utf8(content in arb_byte_vec(256)) {
3196 let mut parser = InputParser::new();
3197 let mut input = b"\x1b[200~".to_vec();
3198 input.extend_from_slice(&content);
3199 input.extend_from_slice(b"\x1b[201~");
3200
3201 let events = parser.parse(&input);
3202 for event in &events {
3203 if let Event::Paste(p) = event {
3204 prop_assert!(p.text.is_char_boundary(0), "Paste text is not valid UTF-8");
3206 }
3207 }
3208 }
3209
3210 #[test]
3214 fn recovery_via_esc_reset(garbage in arb_byte_vec(256)) {
3215 let mut parser = InputParser::new();
3216 let _ = parser.parse(&garbage);
3217
3218 let _ = parser.parse(b"\x07\x1b\\\x1b");
3221 let _ = parser.parse(b"\x1b");
3222
3223 let _ = parser.parse(b"z");
3225
3226 let events = parser.parse(b"q");
3228 prop_assert!(!events.is_empty(),
3231 "Parser did not recover after garbage + reset");
3232 }
3233 }
3234}