1#![forbid(unsafe_code)]
2
3use crate::event::{
27 ClipboardEvent, ClipboardSource, Event, KeyCode, KeyEvent, KeyEventKind, Modifiers,
28 MouseButton, MouseEvent, MouseEventKind, PasteEvent,
29};
30
31const MAX_CSI_LEN: usize = 256;
33
34const MAX_OSC_LEN: usize = 4096;
36
37const MAX_PASTE_LEN: usize = 1024 * 1024; #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
42enum ParserState {
43 #[default]
45 Ground,
46 Escape,
48 Csi,
50 CsiParam,
52 CsiIgnore,
54 Ss3,
56 Osc,
58 OscContent,
60 OscEscape,
62 OscIgnore,
64 Utf8 {
66 collected: u8,
68 expected: u8,
70 },
71}
72
73#[derive(Debug)]
83pub struct InputParser {
84 state: ParserState,
86 buffer: Vec<u8>,
88 paste_buffer: Vec<u8>,
90 utf8_buffer: [u8; 4],
92 in_paste: bool,
94 pending_event: Option<Event>,
96}
97
98impl Default for InputParser {
99 fn default() -> Self {
100 Self::new()
101 }
102}
103
104impl InputParser {
105 #[must_use]
107 pub fn new() -> Self {
108 Self {
109 state: ParserState::Ground,
110 buffer: Vec::with_capacity(64),
111 paste_buffer: Vec::new(),
112 utf8_buffer: [0; 4],
113 in_paste: false,
114 pending_event: None,
115 }
116 }
117
118 pub fn parse(&mut self, input: &[u8]) -> Vec<Event> {
120 let mut events = Vec::new();
121 for &byte in input {
122 if let Some(event) = self.process_byte(byte) {
123 events.push(event);
124 }
125 if let Some(pending) = self.pending_event.take() {
126 events.push(pending);
127 }
128 }
129 events
130 }
131
132 fn process_byte(&mut self, byte: u8) -> Option<Event> {
134 if self.in_paste {
136 return self.process_paste_byte(byte);
137 }
138
139 match self.state {
140 ParserState::Ground => self.process_ground(byte),
141 ParserState::Escape => self.process_escape(byte),
142 ParserState::Csi => self.process_csi(byte),
143 ParserState::CsiParam => self.process_csi_param(byte),
144 ParserState::CsiIgnore => self.process_csi_ignore(byte),
145 ParserState::Ss3 => self.process_ss3(byte),
146 ParserState::Osc => self.process_osc(byte),
147 ParserState::OscContent => self.process_osc_content(byte),
148 ParserState::OscEscape => self.process_osc_escape(byte),
149 ParserState::OscIgnore => self.process_osc_ignore(byte),
150 ParserState::Utf8 {
151 collected,
152 expected,
153 } => self.process_utf8(byte, collected, expected),
154 }
155 }
156
157 fn process_ground(&mut self, byte: u8) -> Option<Event> {
159 match byte {
160 0x1B => {
162 self.state = ParserState::Escape;
163 None
164 }
165 0x00 => Some(Event::Key(KeyEvent::new(KeyCode::Null))),
167 0x08 => Some(Event::Key(KeyEvent::new(KeyCode::Backspace))),
169 0x09 => Some(Event::Key(KeyEvent::new(KeyCode::Tab))),
171 0x0D => Some(Event::Key(KeyEvent::new(KeyCode::Enter))),
173 0x01..=0x07 | 0x0A..=0x0C | 0x0E..=0x1A => {
175 let c = (byte + b'a' - 1) as char;
176 Some(Event::Key(
177 KeyEvent::new(KeyCode::Char(c)).with_modifiers(Modifiers::CTRL),
178 ))
179 }
180 0x7F => Some(Event::Key(KeyEvent::new(KeyCode::Backspace))),
182 0x20..=0x7E => Some(Event::Key(KeyEvent::new(KeyCode::Char(byte as char)))),
184 0xC2..=0xDF => {
186 self.utf8_buffer[0] = byte;
187 self.state = ParserState::Utf8 {
188 collected: 1,
189 expected: 2,
190 };
191 None
192 }
193 0xE0..=0xEF => {
194 self.utf8_buffer[0] = byte;
195 self.state = ParserState::Utf8 {
196 collected: 1,
197 expected: 3,
198 };
199 None
200 }
201 0xF0..=0xF4 => {
202 self.utf8_buffer[0] = byte;
203 self.state = ParserState::Utf8 {
204 collected: 1,
205 expected: 4,
206 };
207 None
208 }
209 0xC0..=0xC1 | 0xF5..=0xFF => Some(Event::Key(KeyEvent::new(KeyCode::Char(
211 std::char::REPLACEMENT_CHARACTER,
212 )))),
213 _ => None,
215 }
216 }
217
218 fn process_escape(&mut self, byte: u8) -> Option<Event> {
220 match byte {
221 b'[' => {
223 self.state = ParserState::Csi;
224 self.buffer.clear();
225 None
226 }
227 b'O' => {
229 self.state = ParserState::Ss3;
230 None
231 }
232 b']' => {
234 self.state = ParserState::Osc;
235 self.buffer.clear();
236 None
237 }
238 0x1B => {
241 self.state = ParserState::Ground;
242 Some(Event::Key(
243 KeyEvent::new(KeyCode::Escape).with_modifiers(Modifiers::ALT),
244 ))
245 }
246 0x20..=0x7E => {
248 self.state = ParserState::Ground;
249 Some(Event::Key(
250 KeyEvent::new(KeyCode::Char(byte as char)).with_modifiers(Modifiers::ALT),
251 ))
252 }
253 0x7F => {
255 self.state = ParserState::Ground;
256 Some(Event::Key(
257 KeyEvent::new(KeyCode::Backspace).with_modifiers(Modifiers::ALT),
258 ))
259 }
260 _ => {
262 self.state = ParserState::Ground;
263 None
264 }
265 }
266 }
267
268 fn process_csi(&mut self, byte: u8) -> Option<Event> {
270 if byte == 0x1B {
272 self.state = ParserState::Escape;
273 self.buffer.clear();
274 return None;
275 }
276
277 self.buffer.push(byte);
278
279 match byte {
280 0x20..=0x3F => {
282 self.state = ParserState::CsiParam;
283 None
284 }
285 0x40..=0x7E => {
287 self.state = ParserState::Ground;
288 self.parse_csi_sequence()
289 }
290 _ => {
292 self.state = ParserState::Ground;
293 self.buffer.clear();
294 None
295 }
296 }
297 }
298
299 fn process_csi_param(&mut self, byte: u8) -> Option<Event> {
301 if byte == 0x1B {
303 self.state = ParserState::Escape;
304 self.buffer.clear();
305 return None;
306 }
307
308 if self.buffer.len() >= MAX_CSI_LEN {
310 self.state = ParserState::CsiIgnore;
311 self.buffer.clear();
312 return None;
313 }
314
315 self.buffer.push(byte);
316
317 match byte {
318 0x20..=0x3F => None,
320 0x40..=0x7E => {
322 self.state = ParserState::Ground;
323 self.parse_csi_sequence()
324 }
325 _ => {
327 self.state = ParserState::Ground;
328 self.buffer.clear();
329 None
330 }
331 }
332 }
333
334 fn process_csi_ignore(&mut self, byte: u8) -> Option<Event> {
336 if byte == 0x1B {
338 self.state = ParserState::Escape;
339 return None;
340 }
341
342 if (0x40..=0x7E).contains(&byte) {
345 self.state = ParserState::Ground;
346 } else if !(0x20..=0x7E).contains(&byte) {
347 self.state = ParserState::Ground;
349 }
350 None
351 }
352
353 fn parse_csi_sequence(&mut self) -> Option<Event> {
355 let seq = std::mem::take(&mut self.buffer);
356 if seq.is_empty() {
357 return None;
358 }
359
360 let final_byte = *seq.last()?;
361 let params = &seq[..seq.len() - 1];
362
363 match (params, final_byte) {
365 ([], b'I') => return Some(Event::Focus(true)),
367 ([], b'O') => return Some(Event::Focus(false)),
368
369 (b"200", b'~') => {
371 self.in_paste = true;
372 self.paste_buffer.clear();
373 self.buffer.clear(); return None;
375 }
376 (b"201", b'~') => {
377 self.in_paste = false;
378 let content = String::from_utf8_lossy(&self.paste_buffer).into_owned();
379 self.paste_buffer.clear();
380 return Some(Event::Paste(PasteEvent::bracketed(content)));
381 }
382
383 _ if params.starts_with(b"<") && (final_byte == b'M' || final_byte == b'm') => {
385 return self.parse_sgr_mouse(params, final_byte);
386 }
387
388 _ => {}
389 }
390
391 match final_byte {
393 b'A' => Some(Event::Key(self.key_with_modifiers(KeyCode::Up, params))),
394 b'B' => Some(Event::Key(self.key_with_modifiers(KeyCode::Down, params))),
395 b'C' => Some(Event::Key(self.key_with_modifiers(KeyCode::Right, params))),
396 b'D' => Some(Event::Key(self.key_with_modifiers(KeyCode::Left, params))),
397 b'H' => Some(Event::Key(self.key_with_modifiers(KeyCode::Home, params))),
398 b'F' => Some(Event::Key(self.key_with_modifiers(KeyCode::End, params))),
399 b'Z' => Some(Event::Key(
400 self.key_with_modifiers(KeyCode::BackTab, params),
401 )),
402 b'~' => self.parse_csi_tilde(params),
403 b'u' => self.parse_kitty_keyboard(params),
404 _ => None,
405 }
406 }
407
408 fn parse_csi_tilde(&self, params: &[u8]) -> Option<Event> {
410 let num = self.parse_first_param(params)?;
411 let mods = self.parse_modifier_param(params);
412
413 let code = match num {
414 1 => KeyCode::Home,
415 2 => KeyCode::Insert,
416 3 => KeyCode::Delete,
417 4 => KeyCode::End,
418 5 => KeyCode::PageUp,
419 6 => KeyCode::PageDown,
420 15 => KeyCode::F(5),
421 17 => KeyCode::F(6),
422 18 => KeyCode::F(7),
423 19 => KeyCode::F(8),
424 20 => KeyCode::F(9),
425 21 => KeyCode::F(10),
426 23 => KeyCode::F(11),
427 24 => KeyCode::F(12),
428 _ => return None,
429 };
430
431 Some(Event::Key(KeyEvent::new(code).with_modifiers(mods)))
432 }
433
434 fn parse_first_param(&self, params: &[u8]) -> Option<u32> {
436 let s = std::str::from_utf8(params).ok()?;
437 let first = s.split(';').next()?;
438 first.parse().ok()
439 }
440
441 fn parse_modifier_param(&self, params: &[u8]) -> Modifiers {
443 let s = match std::str::from_utf8(params) {
444 Ok(s) => s,
445 Err(_) => return Modifiers::NONE,
446 };
447
448 let modifier_value: u32 = s
449 .split(';')
450 .nth(1)
451 .and_then(|s| s.parse().ok())
452 .unwrap_or(1);
453
454 Self::modifiers_from_xterm(modifier_value)
455 }
456
457 fn parse_kitty_keyboard(&self, params: &[u8]) -> Option<Event> {
461 let s = std::str::from_utf8(params).ok()?;
462 if s.is_empty() {
463 return None;
464 }
465
466 let mut parts = s.split(';');
467 let key_part = parts.next().unwrap_or("");
468 let key_code_str = key_part.split(':').next().unwrap_or("");
469 let key_code: u32 = key_code_str.parse().ok()?;
470
471 let mod_part = parts.next().unwrap_or("");
472 let (modifiers, kind) = Self::kitty_modifiers_and_kind(mod_part);
473
474 let code = Self::kitty_keycode_to_keycode(key_code)?;
475 Some(Event::Key(
476 KeyEvent::new(code)
477 .with_modifiers(modifiers)
478 .with_kind(kind),
479 ))
480 }
481
482 fn kitty_modifiers_and_kind(mod_part: &str) -> (Modifiers, KeyEventKind) {
483 if mod_part.is_empty() {
484 return (Modifiers::NONE, KeyEventKind::Press);
485 }
486
487 let mut parts = mod_part.split(':');
488 let mod_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
489 let kind_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
490
491 let modifiers = Self::modifiers_from_xterm(mod_value);
492 let kind = match kind_value {
493 2 => KeyEventKind::Repeat,
494 3 => KeyEventKind::Release,
495 _ => KeyEventKind::Press,
496 };
497
498 (modifiers, kind)
499 }
500
501 fn kitty_keycode_to_keycode(key_code: u32) -> Option<KeyCode> {
502 match key_code {
503 9 => Some(KeyCode::Tab),
505 13 => Some(KeyCode::Enter),
506 27 => Some(KeyCode::Escape),
507 8 | 127 => Some(KeyCode::Backspace),
508 57_344 => Some(KeyCode::Escape),
510 57_345 => Some(KeyCode::Enter),
511 57_346 => Some(KeyCode::Tab),
512 57_347 => Some(KeyCode::Backspace),
513 57_348 => Some(KeyCode::Insert),
514 57_349 => Some(KeyCode::Delete),
515 57_350 => Some(KeyCode::Left),
516 57_351 => Some(KeyCode::Right),
517 57_352 => Some(KeyCode::Up),
518 57_353 => Some(KeyCode::Down),
519 57_354 => Some(KeyCode::PageUp),
520 57_355 => Some(KeyCode::PageDown),
521 57_356 => Some(KeyCode::Home),
522 57_357 => Some(KeyCode::End),
523 57_364..=57_387 => {
525 let f_num = key_code - 57_364 + 1;
528 debug_assert!(f_num <= 24, "F-key number {f_num} exceeds F24");
529 Some(KeyCode::F(f_num as u8))
530 }
531 57_358..=57_363 | 57_388..=63_743 => None,
533 _ => char::from_u32(key_code).map(KeyCode::Char),
535 }
536 }
537
538 fn modifiers_from_xterm(value: u32) -> Modifiers {
539 let bits = value.saturating_sub(1);
542 let mut mods = Modifiers::NONE;
543 if bits & 1 != 0 {
544 mods |= Modifiers::SHIFT;
545 }
546 if bits & 2 != 0 {
547 mods |= Modifiers::ALT;
548 }
549 if bits & 4 != 0 {
550 mods |= Modifiers::CTRL;
551 }
552 if bits & 8 != 0 {
553 mods |= Modifiers::SUPER;
554 }
555 mods
556 }
557
558 fn key_with_modifiers(&self, code: KeyCode, params: &[u8]) -> KeyEvent {
560 KeyEvent::new(code).with_modifiers(self.parse_modifier_param(params))
561 }
562
563 fn parse_sgr_mouse(&self, params: &[u8], final_byte: u8) -> Option<Event> {
565 let params = ¶ms[1..];
568 let s = std::str::from_utf8(params).ok()?;
569 let mut parts = s.split(';');
570
571 let button_code: u16 = parts.next()?.parse().ok()?;
572 let x: u16 = parts.next()?.parse().ok()?;
573 let y: u16 = parts.next()?.parse().ok()?;
574
575 let (button, mods) = self.decode_mouse_button(button_code);
577
578 let kind = if final_byte == b'M' {
579 if button_code & 64 != 0 {
580 match button_code & 3 {
583 0 => MouseEventKind::ScrollUp,
584 1 => MouseEventKind::ScrollDown,
585 2 => MouseEventKind::ScrollLeft,
586 _ => MouseEventKind::ScrollRight,
587 }
588 } else if button_code & 32 != 0 {
589 if button_code & 3 == 3 {
592 MouseEventKind::Moved
593 } else {
594 MouseEventKind::Drag(button)
595 }
596 } else {
597 MouseEventKind::Down(button)
598 }
599 } else {
600 MouseEventKind::Up(button)
601 };
602
603 Some(Event::Mouse(MouseEvent {
604 kind,
605 x: x.saturating_sub(1), y: y.saturating_sub(1),
607 modifiers: mods,
608 }))
609 }
610
611 fn decode_mouse_button(&self, code: u16) -> (MouseButton, Modifiers) {
613 let button = match code & 0b11 {
614 0 => MouseButton::Left,
615 1 => MouseButton::Middle,
616 2 => MouseButton::Right,
617 _ => MouseButton::Left,
618 };
619
620 let mut mods = Modifiers::NONE;
621 if code & 4 != 0 {
622 mods |= Modifiers::SHIFT;
623 }
624 if code & 8 != 0 {
625 mods |= Modifiers::ALT;
626 }
627 if code & 16 != 0 {
628 mods |= Modifiers::CTRL;
629 }
630
631 (button, mods)
632 }
633
634 fn process_ss3(&mut self, byte: u8) -> Option<Event> {
636 if byte == 0x1B {
638 self.state = ParserState::Escape;
639 return None;
640 }
641
642 self.state = ParserState::Ground;
643
644 let code = match byte {
645 b'P' => KeyCode::F(1),
646 b'Q' => KeyCode::F(2),
647 b'R' => KeyCode::F(3),
648 b'S' => KeyCode::F(4),
649 b'A' => KeyCode::Up,
650 b'B' => KeyCode::Down,
651 b'C' => KeyCode::Right,
652 b'D' => KeyCode::Left,
653 b'H' => KeyCode::Home,
654 b'F' => KeyCode::End,
655 _ => return None,
656 };
657
658 Some(Event::Key(KeyEvent::new(code)))
659 }
660
661 fn process_osc(&mut self, byte: u8) -> Option<Event> {
663 if byte == 0x1B {
665 self.state = ParserState::OscEscape;
666 return None;
667 }
668
669 self.buffer.push(byte);
670
671 match byte {
672 0x07 => {
674 self.state = ParserState::Ground;
675 self.parse_osc_sequence()
676 }
677 _ => {
679 self.state = ParserState::OscContent;
680 None
681 }
682 }
683 }
684
685 fn process_osc_content(&mut self, byte: u8) -> Option<Event> {
687 if byte == 0x1B {
689 self.state = ParserState::OscEscape;
690 return None;
691 }
692
693 if byte < 0x20 && byte != 0x07 {
695 self.state = ParserState::Ground;
696 self.buffer.clear();
697 return None;
698 }
699
700 if self.buffer.len() >= MAX_OSC_LEN {
702 self.state = ParserState::OscIgnore;
703 self.buffer.clear();
704 return None;
705 }
706
707 match byte {
708 0x07 => {
710 self.state = ParserState::Ground;
711 self.parse_osc_sequence()
712 }
713 _ => {
715 self.buffer.push(byte);
716 None
717 }
718 }
719 }
720
721 fn process_osc_escape(&mut self, byte: u8) -> Option<Event> {
723 if byte == b'\\' {
724 self.state = ParserState::Ground;
726 self.parse_osc_sequence()
727 } else if byte == 0x1B {
728 self.state = ParserState::Escape;
730 self.buffer.clear();
731 None
732 } else {
733 self.buffer.clear();
739 self.state = ParserState::Escape;
740 self.process_escape(byte)
741 }
742 }
743
744 fn process_osc_ignore(&mut self, byte: u8) -> Option<Event> {
746 match byte {
747 0x07 => {
749 self.state = ParserState::Ground;
750 None
751 }
752 0x1B => {
754 self.state = ParserState::OscEscape;
755 None
756 }
757 _ if byte < 0x20 => {
759 self.state = ParserState::Ground;
760 None
761 }
762 _ => None,
764 }
765 }
766
767 fn parse_osc_sequence(&mut self) -> Option<Event> {
769 let seq = std::mem::take(&mut self.buffer);
770
771 if seq.starts_with(b"52;") {
773 return self.parse_osc52_clipboard(&seq);
774 }
775
776 None
778 }
779
780 fn parse_osc52_clipboard(&self, seq: &[u8]) -> Option<Event> {
782 let content = &seq[3..]; if content.is_empty() {
785 return None;
786 }
787
788 let source = ClipboardSource::Osc52;
791
792 let base64_start = content.iter().position(|&b| b == b';').map(|i| i + 1)?;
794 let base64_data = &content[base64_start..];
795
796 let decoded = self.decode_base64(base64_data)?;
798
799 Some(Event::Clipboard(ClipboardEvent::new(
800 String::from_utf8_lossy(&decoded).into_owned(),
801 source,
802 )))
803 }
804
805 fn decode_base64(&self, input: &[u8]) -> Option<Vec<u8>> {
807 const DECODE_TABLE: [i8; 256] = {
808 let mut table = [-1i8; 256];
809 let mut i = 0u8;
810 while i < 26 {
811 table[(b'A' + i) as usize] = i as i8;
812 table[(b'a' + i) as usize] = (i + 26) as i8;
813 i += 1;
814 }
815 let mut i = 0u8;
816 while i < 10 {
817 table[(b'0' + i) as usize] = (i + 52) as i8;
818 i += 1;
819 }
820 table[b'+' as usize] = 62;
821 table[b'/' as usize] = 63;
822 table
823 };
824
825 let mut output = Vec::with_capacity(input.len() * 3 / 4);
826 let mut buffer = 0u32;
827 let mut bits = 0u8;
828
829 for &byte in input {
830 if byte == b'=' {
831 break;
832 }
833 let value = DECODE_TABLE[byte as usize];
834 if value < 0 {
835 continue; }
837 buffer = (buffer << 6) | (value as u32);
838 bits += 6;
839 if bits >= 8 {
840 bits -= 8;
841 output.push((buffer >> bits) as u8);
842 buffer &= (1 << bits) - 1;
843 }
844 }
845
846 Some(output)
847 }
848
849 fn process_utf8(&mut self, byte: u8, collected: u8, expected: u8) -> Option<Event> {
851 if (byte & 0xC0) != 0x80 {
853 self.state = ParserState::Ground;
856
857 self.pending_event = self.process_ground(byte);
859
860 return Some(Event::Key(KeyEvent::new(KeyCode::Char(
861 std::char::REPLACEMENT_CHARACTER,
862 ))));
863 }
864
865 self.utf8_buffer[collected as usize] = byte;
866 let new_collected = collected + 1;
867
868 if new_collected == expected {
869 self.state = ParserState::Ground;
871 let s = std::str::from_utf8(&self.utf8_buffer[..expected as usize]).ok()?;
872 let c = s.chars().next()?;
873 Some(Event::Key(KeyEvent::new(KeyCode::Char(c))))
874 } else {
875 self.state = ParserState::Utf8 {
877 collected: new_collected,
878 expected,
879 };
880 None
881 }
882 }
883
884 fn process_paste_byte(&mut self, byte: u8) -> Option<Event> {
886 const END_SEQ: &[u8] = b"\x1b[201~";
887
888 if self.paste_buffer.len() < MAX_PASTE_LEN {
894 self.paste_buffer.push(byte);
895
896 if self.paste_buffer.ends_with(END_SEQ) {
898 self.in_paste = false;
899 let content_len = self.paste_buffer.len() - END_SEQ.len();
901 let content =
902 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
903 self.paste_buffer.clear();
904 return Some(Event::Paste(PasteEvent::bracketed(content)));
905 }
906 } else {
907 self.buffer.push(byte);
912 if self.buffer.len() > END_SEQ.len() {
913 self.buffer.remove(0);
914 }
915
916 let mut last_bytes = [0u8; 6];
923 let tail_len = self.buffer.len();
924 let paste_len = self.paste_buffer.len();
925
926 if tail_len + paste_len >= 6 {
927 for i in 0..tail_len {
929 last_bytes[6 - tail_len + i] = self.buffer[i];
930 }
931 let remaining = 6 - tail_len;
933 if remaining > 0 {
934 let start = paste_len - remaining;
935 last_bytes[..remaining]
936 .copy_from_slice(&self.paste_buffer[start..(remaining + start)]);
937 }
938
939 if last_bytes == END_SEQ {
940 self.in_paste = false;
941
942 let content_len = paste_len - remaining;
947 let content =
948 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
949
950 self.paste_buffer.clear();
951 self.buffer.clear();
952
953 return Some(Event::Paste(PasteEvent::bracketed(content)));
954 }
955 }
956 }
957
958 None
959 }
960}
961
962#[cfg(test)]
963mod tests {
964 use super::*;
965
966 #[test]
967 fn csi_ignore_handles_final_bytes() {
968 let mut parser = InputParser::new();
969
970 let mut seq = vec![0x1B, b'['];
973 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100)); seq.push(b'@'); let events = parser.parse(&seq);
977 assert_eq!(events.len(), 0);
978
979 let events = parser.parse(b"a");
982 assert_eq!(events.len(), 1, "Subsequent char 'a' was swallowed");
983 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
984 }
985
986 #[test]
987 fn ascii_characters_parsed() {
988 let mut parser = InputParser::new();
989
990 let events = parser.parse(b"abc");
991 assert_eq!(events.len(), 3);
992 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
993 assert!(matches!(events[1], Event::Key(k) if k.code == KeyCode::Char('b')));
994 assert!(matches!(events[2], Event::Key(k) if k.code == KeyCode::Char('c')));
995 }
996
997 #[test]
998 fn control_characters() {
999 let mut parser = InputParser::new();
1000
1001 let events = parser.parse(&[0x01]);
1003 assert_eq!(events.len(), 1);
1004 assert!(matches!(
1005 events[0],
1006 Event::Key(k) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::CTRL)
1007 ));
1008
1009 let events = parser.parse(&[0x7F]);
1011 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace));
1012 }
1013
1014 #[test]
1015 fn arrow_keys() {
1016 let mut parser = InputParser::new();
1017
1018 assert!(matches!(
1019 parser.parse(b"\x1b[A").first(),
1020 Some(Event::Key(k)) if k.code == KeyCode::Up
1021 ));
1022 assert!(matches!(
1023 parser.parse(b"\x1b[B").first(),
1024 Some(Event::Key(k)) if k.code == KeyCode::Down
1025 ));
1026 assert!(matches!(
1027 parser.parse(b"\x1b[C").first(),
1028 Some(Event::Key(k)) if k.code == KeyCode::Right
1029 ));
1030 assert!(matches!(
1031 parser.parse(b"\x1b[D").first(),
1032 Some(Event::Key(k)) if k.code == KeyCode::Left
1033 ));
1034 }
1035
1036 #[test]
1037 fn function_keys_ss3() {
1038 let mut parser = InputParser::new();
1039
1040 assert!(matches!(
1041 parser.parse(b"\x1bOP").first(),
1042 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1043 ));
1044 assert!(matches!(
1045 parser.parse(b"\x1bOQ").first(),
1046 Some(Event::Key(k)) if k.code == KeyCode::F(2)
1047 ));
1048 assert!(matches!(
1049 parser.parse(b"\x1bOR").first(),
1050 Some(Event::Key(k)) if k.code == KeyCode::F(3)
1051 ));
1052 assert!(matches!(
1053 parser.parse(b"\x1bOS").first(),
1054 Some(Event::Key(k)) if k.code == KeyCode::F(4)
1055 ));
1056 }
1057
1058 #[test]
1059 fn function_keys_csi() {
1060 let mut parser = InputParser::new();
1061
1062 assert!(matches!(
1063 parser.parse(b"\x1b[15~").first(),
1064 Some(Event::Key(k)) if k.code == KeyCode::F(5)
1065 ));
1066 assert!(matches!(
1067 parser.parse(b"\x1b[17~").first(),
1068 Some(Event::Key(k)) if k.code == KeyCode::F(6)
1069 ));
1070 }
1071
1072 #[test]
1073 fn modifiers_in_csi() {
1074 let mut parser = InputParser::new();
1075
1076 let events = parser.parse(b"\x1b[1;2A");
1078 assert!(matches!(
1079 events.first(),
1080 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::SHIFT)
1081 ));
1082
1083 let events = parser.parse(b"\x1b[1;5A");
1085 assert!(matches!(
1086 events.first(),
1087 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::CTRL)
1088 ));
1089 }
1090
1091 #[test]
1092 fn modifiers_in_csi_alt_ctrl() {
1093 let mut parser = InputParser::new();
1094
1095 let events = parser.parse(b"\x1b[1;7A");
1097 assert!(matches!(
1098 events.first(),
1099 Some(Event::Key(k))
1100 if k.code == KeyCode::Up
1101 && k.modifiers.contains(Modifiers::ALT)
1102 && k.modifiers.contains(Modifiers::CTRL)
1103 ));
1104 }
1105
1106 #[test]
1107 fn kitty_keyboard_basic_char() {
1108 let mut parser = InputParser::new();
1109
1110 let events = parser.parse(b"\x1b[97u");
1111 assert!(matches!(
1112 events.first(),
1113 Some(Event::Key(k))
1114 if k.code == KeyCode::Char('a')
1115 && k.modifiers == Modifiers::NONE
1116 && k.kind == KeyEventKind::Press
1117 ));
1118 }
1119
1120 #[test]
1121 fn kitty_keyboard_with_modifiers_and_kind() {
1122 let mut parser = InputParser::new();
1123
1124 let events = parser.parse(b"\x1b[97;5:2u");
1126 assert!(matches!(
1127 events.first(),
1128 Some(Event::Key(k))
1129 if k.code == KeyCode::Char('a')
1130 && k.modifiers.contains(Modifiers::CTRL)
1131 && k.kind == KeyEventKind::Repeat
1132 ));
1133 }
1134
1135 #[test]
1136 fn kitty_keyboard_function_key() {
1137 let mut parser = InputParser::new();
1138
1139 let events = parser.parse(b"\x1b[57364;1u");
1140 assert!(matches!(
1141 events.first(),
1142 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1143 ));
1144 }
1145
1146 #[test]
1147 fn alt_key_escapes() {
1148 let mut parser = InputParser::new();
1149
1150 let events = parser.parse(b"\x1ba");
1151 assert!(matches!(
1152 events.first(),
1153 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::ALT)
1154 ));
1155 }
1156
1157 #[test]
1158 fn alt_backspace() {
1159 let mut parser = InputParser::new();
1160
1161 let events = parser.parse(b"\x1b\x7f");
1162 assert!(matches!(
1163 events.first(),
1164 Some(Event::Key(k))
1165 if k.code == KeyCode::Backspace && k.modifiers.contains(Modifiers::ALT)
1166 ));
1167 }
1168
1169 #[test]
1170 fn escape_escape_resets_state() {
1171 let mut parser = InputParser::new();
1172
1173 let events = parser.parse(b"\x1b\x1b");
1174 assert!(matches!(
1175 events.first(),
1176 Some(Event::Key(k)) if k.code == KeyCode::Escape && k.modifiers.contains(Modifiers::ALT)
1177 ));
1178
1179 let events = parser.parse(b"a");
1180 assert!(matches!(
1181 events.first(),
1182 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers == Modifiers::NONE
1183 ));
1184 }
1185
1186 #[test]
1187 fn focus_events() {
1188 let mut parser = InputParser::new();
1189
1190 assert!(matches!(
1191 parser.parse(b"\x1b[I").first(),
1192 Some(Event::Focus(true))
1193 ));
1194 assert!(matches!(
1195 parser.parse(b"\x1b[O").first(),
1196 Some(Event::Focus(false))
1197 ));
1198 }
1199
1200 #[test]
1201 fn bracketed_paste() {
1202 let mut parser = InputParser::new();
1203
1204 let events = parser.parse(b"\x1b[200~hello world\x1b[201~");
1206 assert_eq!(events.len(), 1);
1207 assert!(matches!(
1208 &events[0],
1209 Event::Paste(p) if p.text == "hello world"
1210 ));
1211 }
1212
1213 #[test]
1214 fn mouse_sgr_protocol() {
1215 let mut parser = InputParser::new();
1216
1217 let events = parser.parse(b"\x1b[<0;10;20M");
1219 assert!(matches!(
1220 events.first(),
1221 Some(Event::Mouse(m)) if m.x == 9 && m.y == 19 ));
1223 }
1224
1225 #[test]
1226 fn mouse_sgr_modifiers() {
1227 let mut parser = InputParser::new();
1228
1229 let events = parser.parse(b"\x1b[<28;3;4M");
1231 assert!(matches!(
1232 events.first(),
1233 Some(Event::Mouse(m))
1234 if m.modifiers.contains(Modifiers::SHIFT)
1235 && m.modifiers.contains(Modifiers::ALT)
1236 && m.modifiers.contains(Modifiers::CTRL)
1237 ));
1238 }
1239
1240 #[test]
1241 fn mouse_sgr_scroll_up() {
1242 let mut parser = InputParser::new();
1243
1244 let events = parser.parse(b"\x1b[<64;5;5M");
1246 assert!(matches!(
1247 events.first(),
1248 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollUp)
1249 ));
1250 }
1251
1252 #[test]
1253 fn mouse_sgr_scroll_down() {
1254 let mut parser = InputParser::new();
1255
1256 let events = parser.parse(b"\x1b[<65;5;5M");
1258 assert!(matches!(
1259 events.first(),
1260 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollDown)
1261 ));
1262 }
1263
1264 #[test]
1265 fn mouse_sgr_scroll_left() {
1266 let mut parser = InputParser::new();
1267
1268 let events = parser.parse(b"\x1b[<66;5;5M");
1270 assert!(matches!(
1271 events.first(),
1272 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollLeft)
1273 ));
1274 }
1275
1276 #[test]
1277 fn mouse_sgr_scroll_right() {
1278 let mut parser = InputParser::new();
1279
1280 let events = parser.parse(b"\x1b[<67;5;5M");
1282 assert!(matches!(
1283 events.first(),
1284 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollRight)
1285 ));
1286 }
1287
1288 #[test]
1289 fn mouse_sgr_drag_left() {
1290 let mut parser = InputParser::new();
1291
1292 let events = parser.parse(b"\x1b[<32;10;20M");
1294 assert!(matches!(
1295 events.first(),
1296 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Drag(MouseButton::Left))
1297 ));
1298 }
1299
1300 #[test]
1301 fn utf8_characters() {
1302 let mut parser = InputParser::new();
1303
1304 let events = parser.parse(&[0xC3, 0xA9]);
1306 assert!(matches!(
1307 events.first(),
1308 Some(Event::Key(k)) if k.code == KeyCode::Char('é')
1309 ));
1310 }
1311
1312 #[test]
1313 fn invalid_utf8_emits_replacement_then_reprocesses_byte() {
1314 let mut parser = InputParser::new();
1315
1316 let events = parser.parse(&[0xE2, 0x28]);
1318 assert_eq!(events.len(), 2);
1319 assert!(matches!(
1320 events[0],
1321 Event::Key(k) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)
1322 ));
1323 assert!(matches!(
1324 events[1],
1325 Event::Key(k) if k.code == KeyCode::Char('(')
1326 ));
1327 }
1328
1329 #[test]
1330 fn dos_protection_csi() {
1331 let mut parser = InputParser::new();
1332
1333 let mut seq = vec![0x1B, b'['];
1335 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
1336 seq.push(b'A');
1337
1338 let events = parser.parse(&seq);
1341 assert_eq!(
1342 events.len(),
1343 0,
1344 "Oversized CSI sequence should produce no events"
1345 );
1346
1347 let events = parser.parse(b"\x1b[A");
1350 assert!(matches!(
1351 events.first(),
1352 Some(Event::Key(k)) if k.code == KeyCode::Up
1353 ));
1354 }
1355
1356 #[test]
1357 fn incomplete_csi_sequence_emits_no_event() {
1358 let mut parser = InputParser::new();
1359 let events = parser.parse(b"\x1b[");
1360 assert!(events.is_empty());
1361 }
1362
1363 #[test]
1364 fn dos_protection_paste() {
1365 let mut parser = InputParser::new();
1366
1367 parser.parse(b"\x1b[200~");
1369
1370 let content = vec![b'x'; MAX_PASTE_LEN - 100]; parser.parse(&content);
1373
1374 let events = parser.parse(b"\x1b[201~");
1376
1377 assert!(matches!(
1379 events.first(),
1380 Some(Event::Paste(p)) if p.text.len() <= MAX_PASTE_LEN
1381 ));
1382 }
1383
1384 #[test]
1385 fn dos_protection_paste_overflow_terminator() {
1386 let mut parser = InputParser::new();
1387
1388 parser.parse(b"\x1b[200~");
1390
1391 let overflow = 100;
1395 let content = vec![b'a'; MAX_PASTE_LEN + overflow];
1396 parser.parse(&content);
1397
1398 let events = parser.parse(b"\x1b[201~");
1401
1402 assert_eq!(events.len(), 1, "Should emit paste event");
1403 match &events[0] {
1404 Event::Paste(p) => {
1405 assert_eq!(
1408 p.text.len(),
1409 MAX_PASTE_LEN,
1410 "Paste should be capped at MAX_PASTE_LEN bytes"
1411 );
1412 assert!(p.text.chars().all(|c| c == 'a'));
1414 }
1415 _ => unreachable!("Expected Paste event"),
1416 }
1417
1418 let events = parser.parse(b"b");
1420 assert_eq!(events.len(), 1);
1421 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('b')));
1422 }
1423
1424 #[test]
1425 fn no_panic_on_invalid_input() {
1426 let mut parser = InputParser::new();
1427
1428 let garbage = [0xFF, 0xFE, 0x00, 0x1B, 0x1B, 0x1B, b'[', 0xFF, b']', 0x00];
1430
1431 let _ = parser.parse(&garbage);
1433 }
1434
1435 #[test]
1436 fn dos_protection_paste_boundary() {
1437 let mut parser = InputParser::new();
1438 parser.parse(b"\x1b[200~");
1440
1441 let content = vec![b'x'; MAX_PASTE_LEN];
1443 parser.parse(&content);
1444
1445 let events = parser.parse(b"\x1b[201~");
1448
1449 assert!(
1450 !events.is_empty(),
1451 "Parser trapped in paste mode after hitting limit"
1452 );
1453 assert!(matches!(events[0], Event::Paste(_)));
1454 }
1455
1456 #[test]
1459 fn csi_tilde_home() {
1460 let mut parser = InputParser::new();
1461 let events = parser.parse(b"\x1b[1~");
1462 assert!(matches!(
1463 events.first(),
1464 Some(Event::Key(k)) if k.code == KeyCode::Home
1465 ));
1466 }
1467
1468 #[test]
1469 fn csi_tilde_insert() {
1470 let mut parser = InputParser::new();
1471 let events = parser.parse(b"\x1b[2~");
1472 assert!(matches!(
1473 events.first(),
1474 Some(Event::Key(k)) if k.code == KeyCode::Insert
1475 ));
1476 }
1477
1478 #[test]
1479 fn csi_tilde_delete() {
1480 let mut parser = InputParser::new();
1481 let events = parser.parse(b"\x1b[3~");
1482 assert!(matches!(
1483 events.first(),
1484 Some(Event::Key(k)) if k.code == KeyCode::Delete
1485 ));
1486 }
1487
1488 #[test]
1489 fn csi_tilde_end() {
1490 let mut parser = InputParser::new();
1491 let events = parser.parse(b"\x1b[4~");
1492 assert!(matches!(
1493 events.first(),
1494 Some(Event::Key(k)) if k.code == KeyCode::End
1495 ));
1496 }
1497
1498 #[test]
1499 fn csi_tilde_page_up() {
1500 let mut parser = InputParser::new();
1501 let events = parser.parse(b"\x1b[5~");
1502 assert!(matches!(
1503 events.first(),
1504 Some(Event::Key(k)) if k.code == KeyCode::PageUp
1505 ));
1506 }
1507
1508 #[test]
1509 fn csi_tilde_page_down() {
1510 let mut parser = InputParser::new();
1511 let events = parser.parse(b"\x1b[6~");
1512 assert!(matches!(
1513 events.first(),
1514 Some(Event::Key(k)) if k.code == KeyCode::PageDown
1515 ));
1516 }
1517
1518 #[test]
1521 fn csi_home_and_end() {
1522 let mut parser = InputParser::new();
1523 assert!(matches!(
1524 parser.parse(b"\x1b[H").first(),
1525 Some(Event::Key(k)) if k.code == KeyCode::Home
1526 ));
1527 assert!(matches!(
1528 parser.parse(b"\x1b[F").first(),
1529 Some(Event::Key(k)) if k.code == KeyCode::End
1530 ));
1531 }
1532
1533 #[test]
1536 fn ss3_home_and_end() {
1537 let mut parser = InputParser::new();
1538 assert!(matches!(
1539 parser.parse(b"\x1bOH").first(),
1540 Some(Event::Key(k)) if k.code == KeyCode::Home
1541 ));
1542 assert!(matches!(
1543 parser.parse(b"\x1bOF").first(),
1544 Some(Event::Key(k)) if k.code == KeyCode::End
1545 ));
1546 }
1547
1548 #[test]
1551 fn backtab_csi_z() {
1552 let mut parser = InputParser::new();
1553 let events = parser.parse(b"\x1b[Z");
1554 assert!(matches!(
1555 events.first(),
1556 Some(Event::Key(k)) if k.code == KeyCode::BackTab
1557 ));
1558 }
1559
1560 #[test]
1563 fn function_keys_f7_to_f12() {
1564 let mut parser = InputParser::new();
1565 assert!(matches!(
1566 parser.parse(b"\x1b[18~").first(),
1567 Some(Event::Key(k)) if k.code == KeyCode::F(7)
1568 ));
1569 assert!(matches!(
1570 parser.parse(b"\x1b[19~").first(),
1571 Some(Event::Key(k)) if k.code == KeyCode::F(8)
1572 ));
1573 assert!(matches!(
1574 parser.parse(b"\x1b[20~").first(),
1575 Some(Event::Key(k)) if k.code == KeyCode::F(9)
1576 ));
1577 assert!(matches!(
1578 parser.parse(b"\x1b[21~").first(),
1579 Some(Event::Key(k)) if k.code == KeyCode::F(10)
1580 ));
1581 assert!(matches!(
1582 parser.parse(b"\x1b[23~").first(),
1583 Some(Event::Key(k)) if k.code == KeyCode::F(11)
1584 ));
1585 assert!(matches!(
1586 parser.parse(b"\x1b[24~").first(),
1587 Some(Event::Key(k)) if k.code == KeyCode::F(12)
1588 ));
1589 }
1590
1591 #[test]
1594 fn ctrl_home_and_alt_end() {
1595 let mut parser = InputParser::new();
1596
1597 let events = parser.parse(b"\x1b[1;5H");
1599 assert!(matches!(
1600 events.first(),
1601 Some(Event::Key(k)) if k.code == KeyCode::Home && k.modifiers.contains(Modifiers::CTRL)
1602 ));
1603
1604 let events = parser.parse(b"\x1b[1;3F");
1606 assert!(matches!(
1607 events.first(),
1608 Some(Event::Key(k)) if k.code == KeyCode::End && k.modifiers.contains(Modifiers::ALT)
1609 ));
1610 }
1611
1612 #[test]
1613 fn shift_ctrl_arrow() {
1614 let mut parser = InputParser::new();
1615
1616 let events = parser.parse(b"\x1b[1;6C");
1618 assert!(matches!(
1619 events.first(),
1620 Some(Event::Key(k)) if k.code == KeyCode::Right
1621 && k.modifiers.contains(Modifiers::SHIFT)
1622 && k.modifiers.contains(Modifiers::CTRL)
1623 ));
1624 }
1625
1626 #[test]
1627 fn modifiers_on_tilde_keys() {
1628 let mut parser = InputParser::new();
1629
1630 let events = parser.parse(b"\x1b[3;5~");
1632 assert!(matches!(
1633 events.first(),
1634 Some(Event::Key(k)) if k.code == KeyCode::Delete && k.modifiers.contains(Modifiers::CTRL)
1635 ));
1636
1637 let events = parser.parse(b"\x1b[5;2~");
1639 assert!(matches!(
1640 events.first(),
1641 Some(Event::Key(k)) if k.code == KeyCode::PageUp && k.modifiers.contains(Modifiers::SHIFT)
1642 ));
1643 }
1644
1645 #[test]
1648 fn mouse_sgr_right_click() {
1649 let mut parser = InputParser::new();
1650 let events = parser.parse(b"\x1b[<2;15;10M");
1652 assert!(matches!(
1653 events.first(),
1654 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Right))
1655 && m.x == 14 && m.y == 9
1656 ));
1657 }
1658
1659 #[test]
1660 fn mouse_sgr_middle_click() {
1661 let mut parser = InputParser::new();
1662 let events = parser.parse(b"\x1b[<1;5;5M");
1664 assert!(matches!(
1665 events.first(),
1666 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Middle))
1667 ));
1668 }
1669
1670 #[test]
1671 fn mouse_sgr_button_release() {
1672 let mut parser = InputParser::new();
1673 let events = parser.parse(b"\x1b[<0;10;20m");
1675 assert!(matches!(
1676 events.first(),
1677 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Up(MouseButton::Left))
1678 ));
1679 }
1680
1681 #[test]
1682 fn mouse_sgr_moved() {
1683 let mut parser = InputParser::new();
1684 let events = parser.parse(b"\x1b[<35;10;20M");
1686 assert!(matches!(
1687 events.first(),
1688 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Moved)
1689 ));
1690 }
1691
1692 #[test]
1693 fn mouse_sgr_with_modifiers() {
1694 let mut parser = InputParser::new();
1695 let events = parser.parse(b"\x1b[<4;5;5M");
1697 assert!(matches!(
1698 events.first(),
1699 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1700 && m.modifiers.contains(Modifiers::SHIFT)
1701 ));
1702
1703 let events = parser.parse(b"\x1b[<16;5;5M");
1705 assert!(matches!(
1706 events.first(),
1707 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1708 && m.modifiers.contains(Modifiers::CTRL)
1709 ));
1710
1711 let events = parser.parse(b"\x1b[<8;5;5M");
1713 assert!(matches!(
1714 events.first(),
1715 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1716 && m.modifiers.contains(Modifiers::ALT)
1717 ));
1718 }
1719
1720 #[test]
1723 fn kitty_keyboard_release_event() {
1724 let mut parser = InputParser::new();
1725 let events = parser.parse(b"\x1b[97;1:3u");
1727 assert!(matches!(
1728 events.first(),
1729 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.kind == KeyEventKind::Release
1730 ));
1731
1732 let events = parser.parse(b"\x1b[65;5:3u");
1734 assert!(matches!(
1735 events.first(),
1736 Some(Event::Key(k))
1737 if k.code == KeyCode::Char('A')
1738 && k.modifiers.contains(Modifiers::CTRL)
1739 && k.kind == KeyEventKind::Release
1740 ));
1741 }
1742
1743 #[test]
1744 fn kitty_keyboard_special_keys() {
1745 let mut parser = InputParser::new();
1746
1747 assert!(matches!(
1749 parser.parse(b"\x1b[57344u").first(),
1750 Some(Event::Key(k)) if k.code == KeyCode::Escape
1751 ));
1752
1753 assert!(matches!(
1755 parser.parse(b"\x1b[57345u").first(),
1756 Some(Event::Key(k)) if k.code == KeyCode::Enter
1757 ));
1758
1759 assert!(matches!(
1761 parser.parse(b"\x1b[57346u").first(),
1762 Some(Event::Key(k)) if k.code == KeyCode::Tab
1763 ));
1764
1765 assert!(matches!(
1767 parser.parse(b"\x1b[57347u").first(),
1768 Some(Event::Key(k)) if k.code == KeyCode::Backspace
1769 ));
1770
1771 assert!(matches!(
1773 parser.parse(b"\x1b[57348u").first(),
1774 Some(Event::Key(k)) if k.code == KeyCode::Insert
1775 ));
1776
1777 assert!(matches!(
1779 parser.parse(b"\x1b[57349u").first(),
1780 Some(Event::Key(k)) if k.code == KeyCode::Delete
1781 ));
1782 }
1783
1784 #[test]
1785 fn kitty_keyboard_navigation_keys() {
1786 let mut parser = InputParser::new();
1787
1788 assert!(matches!(
1790 parser.parse(b"\x1b[57350u").first(),
1791 Some(Event::Key(k)) if k.code == KeyCode::Left
1792 ));
1793 assert!(matches!(
1795 parser.parse(b"\x1b[57351u").first(),
1796 Some(Event::Key(k)) if k.code == KeyCode::Right
1797 ));
1798 assert!(matches!(
1800 parser.parse(b"\x1b[57352u").first(),
1801 Some(Event::Key(k)) if k.code == KeyCode::Up
1802 ));
1803 assert!(matches!(
1805 parser.parse(b"\x1b[57353u").first(),
1806 Some(Event::Key(k)) if k.code == KeyCode::Down
1807 ));
1808 assert!(matches!(
1810 parser.parse(b"\x1b[57354u").first(),
1811 Some(Event::Key(k)) if k.code == KeyCode::PageUp
1812 ));
1813 assert!(matches!(
1815 parser.parse(b"\x1b[57355u").first(),
1816 Some(Event::Key(k)) if k.code == KeyCode::PageDown
1817 ));
1818 assert!(matches!(
1820 parser.parse(b"\x1b[57356u").first(),
1821 Some(Event::Key(k)) if k.code == KeyCode::Home
1822 ));
1823 assert!(matches!(
1825 parser.parse(b"\x1b[57357u").first(),
1826 Some(Event::Key(k)) if k.code == KeyCode::End
1827 ));
1828 }
1829
1830 #[test]
1831 fn kitty_keyboard_f_keys() {
1832 let mut parser = InputParser::new();
1833 assert!(matches!(
1835 parser.parse(b"\x1b[57364u").first(),
1836 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1837 ));
1838 assert!(matches!(
1840 parser.parse(b"\x1b[57375u").first(),
1841 Some(Event::Key(k)) if k.code == KeyCode::F(12)
1842 ));
1843 assert!(matches!(
1845 parser.parse(b"\x1b[57387u").first(),
1846 Some(Event::Key(k)) if k.code == KeyCode::F(24)
1847 ));
1848 }
1849
1850 #[test]
1851 fn kitty_keyboard_ascii_as_standard() {
1852 let mut parser = InputParser::new();
1853 assert!(matches!(
1855 parser.parse(b"\x1b[9u").first(),
1856 Some(Event::Key(k)) if k.code == KeyCode::Tab
1857 ));
1858 assert!(matches!(
1859 parser.parse(b"\x1b[13u").first(),
1860 Some(Event::Key(k)) if k.code == KeyCode::Enter
1861 ));
1862 assert!(matches!(
1863 parser.parse(b"\x1b[27u").first(),
1864 Some(Event::Key(k)) if k.code == KeyCode::Escape
1865 ));
1866 assert!(matches!(
1867 parser.parse(b"\x1b[127u").first(),
1868 Some(Event::Key(k)) if k.code == KeyCode::Backspace
1869 ));
1870 assert!(matches!(
1872 parser.parse(b"\x1b[8u").first(),
1873 Some(Event::Key(k)) if k.code == KeyCode::Backspace
1874 ));
1875 }
1876
1877 #[test]
1880 fn osc52_clipboard_bel_terminated() {
1881 let mut parser = InputParser::new();
1882 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x07");
1885 assert!(matches!(
1886 events.first(),
1887 Some(Event::Clipboard(c)) if c.content == "hello" && c.source == ClipboardSource::Osc52
1888 ));
1889 }
1890
1891 #[test]
1892 fn osc52_clipboard_st_terminated() {
1893 let mut parser = InputParser::new();
1894 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x1b\\");
1896 assert!(matches!(
1897 events.first(),
1898 Some(Event::Clipboard(c)) if c.content == "hello"
1899 ));
1900 }
1901
1902 #[test]
1903 fn osc52_clipboard_primary_selection() {
1904 let mut parser = InputParser::new();
1905 let events = parser.parse(b"\x1b]52;p;YWJj\x07");
1908 assert!(matches!(
1909 events.first(),
1910 Some(Event::Clipboard(c)) if c.content == "abc"
1911 ));
1912 }
1913
1914 #[test]
1917 fn ctrl_space_is_null() {
1918 let mut parser = InputParser::new();
1919 let events = parser.parse(&[0x00]);
1920 assert!(matches!(
1921 events.first(),
1922 Some(Event::Key(k)) if k.code == KeyCode::Null
1923 ));
1924 }
1925
1926 #[test]
1927 fn all_ctrl_letter_keys() {
1928 let mut parser = InputParser::new();
1929 for byte in 0x01..=0x1Au8 {
1931 let events = parser.parse(&[byte]);
1932 assert_eq!(
1933 events.len(),
1934 1,
1935 "Ctrl+{} should produce one event",
1936 (byte + b'a' - 1) as char
1937 );
1938 match byte {
1939 0x08 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace)),
1940 0x09 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Tab)),
1941 0x0D => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Enter)),
1942 _ => {
1943 let expected_char = (byte + b'a' - 1) as char;
1944 match &events[0] {
1945 Event::Key(k) => {
1946 assert_eq!(
1947 k.code,
1948 KeyCode::Char(expected_char),
1949 "Byte 0x{byte:02X} should produce Ctrl+{expected_char}"
1950 );
1951 assert!(
1952 k.modifiers.contains(Modifiers::CTRL),
1953 "Byte 0x{byte:02X} should have Ctrl modifier"
1954 );
1955 }
1956 other => {
1957 panic!("Byte 0x{byte:02X}: expected Key event, got {other:?}");
1958 }
1959 }
1960 }
1961 }
1962 }
1963 }
1964
1965 #[test]
1968 fn utf8_3byte_cjk() {
1969 let mut parser = InputParser::new();
1970 let events = parser.parse(&[0xE4, 0xB8, 0xAD]);
1972 assert!(matches!(
1973 events.first(),
1974 Some(Event::Key(k)) if k.code == KeyCode::Char('中')
1975 ));
1976 }
1977
1978 #[test]
1979 fn utf8_4byte_emoji() {
1980 let mut parser = InputParser::new();
1981 let events = parser.parse(&[0xF0, 0x9F, 0xA6, 0x80]);
1983 assert!(matches!(
1984 events.first(),
1985 Some(Event::Key(k)) if k.code == KeyCode::Char('🦀')
1986 ));
1987 }
1988
1989 #[test]
1992 fn empty_input_returns_no_events() {
1993 let mut parser = InputParser::new();
1994 let events = parser.parse(b"");
1995 assert!(events.is_empty());
1996 }
1997
1998 #[test]
2001 fn unknown_csi_tilde_ignored() {
2002 let mut parser = InputParser::new();
2003 let events = parser.parse(b"\x1b[99~");
2005 assert!(events.is_empty());
2006
2007 let events = parser.parse(b"a");
2009 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('a')));
2010 }
2011
2012 #[test]
2015 fn alt_special_chars() {
2016 let mut parser = InputParser::new();
2017
2018 let events = parser.parse(b"\x1b ");
2020 assert!(matches!(
2021 events.first(),
2022 Some(Event::Key(k)) if k.code == KeyCode::Char(' ') && k.modifiers.contains(Modifiers::ALT)
2023 ));
2024
2025 let events = parser.parse(b"\x1b5");
2027 assert!(matches!(
2028 events.first(),
2029 Some(Event::Key(k)) if k.code == KeyCode::Char('5') && k.modifiers.contains(Modifiers::ALT)
2030 ));
2031
2032 let events = parser.parse(b"\x1b}");
2034 assert!(matches!(
2035 events.first(),
2036 Some(Event::Key(k)) if k.code == KeyCode::Char('}') && k.modifiers.contains(Modifiers::ALT)
2037 ));
2038 }
2039
2040 #[test]
2043 fn ss3_arrow_keys() {
2044 let mut parser = InputParser::new();
2045 assert!(matches!(
2046 parser.parse(b"\x1bOA").first(),
2047 Some(Event::Key(k)) if k.code == KeyCode::Up
2048 ));
2049 assert!(matches!(
2050 parser.parse(b"\x1bOB").first(),
2051 Some(Event::Key(k)) if k.code == KeyCode::Down
2052 ));
2053 assert!(matches!(
2054 parser.parse(b"\x1bOC").first(),
2055 Some(Event::Key(k)) if k.code == KeyCode::Right
2056 ));
2057 assert!(matches!(
2058 parser.parse(b"\x1bOD").first(),
2059 Some(Event::Key(k)) if k.code == KeyCode::Left
2060 ));
2061 }
2062
2063 #[test]
2066 fn xterm_modifier_encoding() {
2067 assert_eq!(InputParser::modifiers_from_xterm(1), Modifiers::NONE);
2069 assert_eq!(InputParser::modifiers_from_xterm(2), Modifiers::SHIFT);
2070 assert_eq!(InputParser::modifiers_from_xterm(3), Modifiers::ALT);
2071 assert_eq!(
2072 InputParser::modifiers_from_xterm(4),
2073 Modifiers::SHIFT | Modifiers::ALT
2074 );
2075 assert_eq!(InputParser::modifiers_from_xterm(5), Modifiers::CTRL);
2076 assert_eq!(
2077 InputParser::modifiers_from_xterm(6),
2078 Modifiers::SHIFT | Modifiers::CTRL
2079 );
2080 assert_eq!(InputParser::modifiers_from_xterm(9), Modifiers::SUPER);
2081 }
2082
2083 #[test]
2086 fn ss3_interrupted_by_esc() {
2087 let mut parser = InputParser::new();
2088 let events = parser.parse(b"\x1bO\x1b[A");
2090 assert!(matches!(
2092 events.first(),
2093 Some(Event::Key(k)) if k.code == KeyCode::Up
2094 ));
2095 }
2096
2097 #[test]
2100 fn kitty_keyboard_reserved_keycode_ignored() {
2101 let mut parser = InputParser::new();
2102 let events = parser.parse(b"\x1b[57360u");
2104 assert!(events.is_empty());
2105
2106 let events = parser.parse(b"x");
2108 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('x')));
2109 }
2110 #[test]
2111 fn utf8_invalid_sequence_emits_replacement() {
2112 let mut parser = InputParser::new();
2113
2114 let events = parser.parse(&[0xE0, 0x41]);
2118 assert_eq!(events.len(), 2);
2119
2120 match &events[0] {
2121 Event::Key(k) => assert_eq!(k.code, KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2122 _ => panic!("Expected replacement character"),
2123 }
2124
2125 match &events[1] {
2126 Event::Key(k) => assert_eq!(k.code, KeyCode::Char('A')),
2127 _ => panic!("Expected character 'A'"),
2128 }
2129 }
2130
2131 #[test]
2132 fn utf8_invalid_lead_emits_replacement() {
2133 let mut parser = InputParser::new();
2134
2135 let events = parser.parse(&[0xC0, b'a']);
2137 assert!(
2138 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2139 "Expected replacement for invalid lead"
2140 );
2141 assert!(
2142 events
2143 .iter()
2144 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('a'))),
2145 "Expected subsequent ASCII to be preserved"
2146 );
2147
2148 let events = parser.parse(&[0xF5, b'b']);
2150 assert!(
2151 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2152 "Expected replacement for out-of-range lead"
2153 );
2154 assert!(
2155 events
2156 .iter()
2157 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('b'))),
2158 "Expected subsequent ASCII to be preserved"
2159 );
2160 }
2161}
2162
2163#[cfg(test)]
2164mod proptest_fuzz {
2165 use super::*;
2166 use proptest::prelude::*;
2167
2168 fn arb_byte() -> impl Strategy<Value = u8> {
2172 any::<u8>()
2173 }
2174
2175 fn arb_byte_vec(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
2176 prop::collection::vec(arb_byte(), 0..=max_len)
2177 }
2178
2179 fn csi_sequence() -> impl Strategy<Value = Vec<u8>> {
2181 let params = prop::collection::vec(0x30u8..=0x3F, 0..=20);
2182 let final_byte = 0x40u8..=0x7E;
2183 (params, final_byte).prop_map(|(p, f)| {
2184 let mut buf = vec![0x1B, b'['];
2185 buf.extend_from_slice(&p);
2186 buf.push(f);
2187 buf
2188 })
2189 }
2190
2191 fn osc_sequence() -> impl Strategy<Value = Vec<u8>> {
2193 let content = prop::collection::vec(0x20u8..=0x7E, 0..=64);
2194 let terminator = prop_oneof![
2195 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
2198 (content, terminator).prop_map(|(c, t)| {
2199 let mut buf = vec![0x1B, b']'];
2200 buf.extend_from_slice(&c);
2201 buf.extend_from_slice(&t);
2202 buf
2203 })
2204 }
2205
2206 fn ss3_sequence() -> impl Strategy<Value = Vec<u8>> {
2208 (0x40u8..=0x7E).prop_map(|f| vec![0x1B, b'O', f])
2209 }
2210
2211 fn paste_sequence() -> impl Strategy<Value = Vec<u8>> {
2213 prop::collection::vec(0x20u8..=0x7E, 0..=128).prop_map(|content| {
2214 let mut buf = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
2215 buf.extend_from_slice(&content);
2216 buf.extend_from_slice(b"\x1b[201~");
2217 buf
2218 })
2219 }
2220
2221 fn mixed_adversarial() -> impl Strategy<Value = Vec<u8>> {
2223 let fragment = prop_oneof![
2224 csi_sequence(),
2225 osc_sequence(),
2226 ss3_sequence(),
2227 paste_sequence(),
2228 arb_byte_vec(16), Just(vec![0x1B]), Just(vec![0x1B, b'[']), Just(vec![0x1B, b']']), prop::collection::vec(0x80u8..=0xFF, 1..=4), ];
2234 prop::collection::vec(fragment, 1..=8)
2235 .prop_map(|frags| frags.into_iter().flatten().collect())
2236 }
2237
2238 proptest! {
2241 #[test]
2243 fn random_bytes_never_panic(input in arb_byte_vec(512)) {
2244 let mut parser = InputParser::new();
2245 let _ = parser.parse(&input);
2246 }
2247
2248 #[test]
2250 fn parser_recovers_after_garbage(input in arb_byte_vec(256)) {
2251 let mut parser = InputParser::new();
2252 let _ = parser.parse(&input);
2253
2254 let events = parser.parse(b"z");
2256 let _ = events;
2259 }
2260
2261 #[test]
2263 fn mixed_sequences_never_panic(input in mixed_adversarial()) {
2264 let mut parser = InputParser::new();
2265 let _ = parser.parse(&input);
2266 }
2267
2268 #[test]
2270 fn events_are_well_formed(input in arb_byte_vec(256)) {
2271 let mut parser = InputParser::new();
2272 let events = parser.parse(&input);
2273 for event in &events {
2274 let _ = format!("{event:?}");
2276 }
2277 }
2278
2279 #[test]
2281 fn csi_event_count_bounded(seq in csi_sequence()) {
2282 let mut parser = InputParser::new();
2283 let events = parser.parse(&seq);
2284 prop_assert!(events.len() <= seq.len(),
2285 "Got {} events from {} bytes", events.len(), seq.len());
2286 }
2287
2288 #[test]
2290 fn osc_event_count_bounded(seq in osc_sequence()) {
2291 let mut parser = InputParser::new();
2292 let events = parser.parse(&seq);
2293 prop_assert!(events.len() <= seq.len(),
2294 "Got {} events from {} bytes", events.len(), seq.len());
2295 }
2296
2297 #[test]
2299 fn paste_content_bounded(content in prop::collection::vec(arb_byte(), 0..=2048)) {
2300 let mut parser = InputParser::new();
2301 let mut input = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
2302 input.extend_from_slice(&content);
2303 input.extend_from_slice(b"\x1b[201~");
2304
2305 let events = parser.parse(&input);
2306 for event in &events {
2307 if let Event::Paste(p) = event {
2308 prop_assert!(p.text.len() <= MAX_PASTE_LEN,
2309 "Paste text {} exceeds limit {}", p.text.len(), MAX_PASTE_LEN);
2310 }
2311 }
2312 }
2313
2314 #[test]
2316 fn incremental_matches_bulk(input in arb_byte_vec(128)) {
2317 let mut bulk_parser = InputParser::new();
2318 let bulk_events = bulk_parser.parse(&input);
2319
2320 let mut incr_parser = InputParser::new();
2321 let mut incr_events = Vec::new();
2322 for byte in &input {
2323 incr_events.extend(incr_parser.parse(std::slice::from_ref(byte)));
2324 }
2325
2326 let bulk_dbg: Vec<String> = bulk_events.iter().map(|e| format!("{e:?}")).collect();
2327 let incr_dbg: Vec<String> = incr_events.iter().map(|e| format!("{e:?}")).collect();
2328 prop_assert_eq!(bulk_dbg, incr_dbg,
2329 "Bulk vs incremental mismatch for input {:?}", input);
2330 }
2331
2332 #[test]
2335 fn deterministic_output(input in arb_byte_vec(128)) {
2336 let mut parser1 = InputParser::new();
2337 let events1 = parser1.parse(&input);
2338
2339 let mut parser2 = InputParser::new();
2340 let events2 = parser2.parse(&input);
2341
2342 let dbg1: Vec<String> = events1.iter().map(|e| format!("{e:?}")).collect();
2343 let dbg2: Vec<String> = events2.iter().map(|e| format!("{e:?}")).collect();
2344 prop_assert_eq!(dbg1, dbg2);
2345 }
2346 }
2347
2348 #[test]
2352 fn no_quadratic_blowup() {
2353 let mut parser = InputParser::new();
2354
2355 let garbage: Vec<u8> = (0..65536).map(|i| (i % 256) as u8).collect();
2357 let _ = parser.parse(&garbage);
2358
2359 let events = parser.parse(b"a");
2361 let _ = events; }
2363
2364 #[test]
2366 fn oversized_csi_transitions_to_ignore() {
2367 let mut parser = InputParser::new();
2368
2369 let mut input = vec![0x1B, b'['];
2371 input.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
2372 input.push(b'm');
2373
2374 let _ = parser.parse(&input);
2375
2376 let events = parser.parse(b"x");
2378 assert_eq!(events.len(), 1);
2379 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('x')));
2380 }
2381
2382 #[test]
2384 fn oversized_osc_transitions_to_ignore() {
2385 let mut parser = InputParser::new();
2386
2387 let mut input = vec![0x1B, b']'];
2389 input.extend(std::iter::repeat_n(b'a', MAX_OSC_LEN + 100));
2390 input.push(0x07); let _ = parser.parse(&input);
2393
2394 let events = parser.parse(b"y");
2396 assert_eq!(events.len(), 1);
2397 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('y')));
2398 }
2399
2400 #[test]
2402 fn rapid_esc_toggle() {
2403 let mut parser = InputParser::new();
2404
2405 let input: Vec<u8> = vec![0x1B; 1000];
2407 let _ = parser.parse(&input);
2408
2409 let events = parser.parse(b"k");
2411 assert!(!events.is_empty());
2412 }
2413
2414 #[test]
2416 fn unterminated_paste_recovery() {
2417 let mut parser = InputParser::new();
2418
2419 let mut input = b"\x1b[200~".to_vec();
2421 input.extend(std::iter::repeat_n(b'x', 2048));
2422
2423 let _ = parser.parse(&input);
2424
2425 let events = parser.parse(b"\x1b[201~");
2427 assert!(
2428 !events.is_empty(),
2429 "Parser should emit paste event on terminator"
2430 );
2431 }
2432
2433 #[test]
2435 fn truncated_utf8_lead_bytes() {
2436 let mut parser = InputParser::new();
2437
2438 for lead in [0xC2, 0xE0, 0xF0] {
2440 let _ = parser.parse(&[lead]);
2441 let events = parser.parse(b"a");
2443 let _ = events;
2445 }
2446 }
2447
2448 #[test]
2450 fn null_bytes_interleaved() {
2451 let mut parser = InputParser::new();
2452
2453 let input = b"\x00A\x00\x1b[A\x00B\x00";
2454 let events = parser.parse(input);
2455 assert!(
2457 events.len() >= 2,
2458 "Expected at least 2 events, got {}",
2459 events.len()
2460 );
2461 }
2462
2463 fn osc52_sequence() -> impl Strategy<Value = Vec<u8>> {
2467 let selector = prop_oneof![Just(b'c'), Just(b'p'), Just(b's')];
2468 let payload = prop::collection::vec(
2470 prop_oneof![
2471 0x41u8..=0x5A, 0x61u8..=0x7A, 0x30u8..=0x39, Just(b'+'),
2475 Just(b'/'),
2476 Just(b'='),
2477 ],
2478 0..=128,
2479 );
2480 let terminator = prop_oneof![
2481 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
2484 (selector, payload, terminator).prop_map(|(sel, pay, term)| {
2485 let mut buf = vec![0x1B, b']', b'5', b'2', b';', sel, b';'];
2486 buf.extend_from_slice(&pay);
2487 buf.extend_from_slice(&term);
2488 buf
2489 })
2490 }
2491
2492 fn sgr_mouse_sequence() -> impl Strategy<Value = Vec<u8>> {
2494 let button_code = 0u16..128;
2495 let x = 1u16..300;
2496 let y = 1u16..100;
2497 let final_byte = prop_oneof![Just(b'M'), Just(b'm')];
2498 (button_code, x, y, final_byte)
2499 .prop_map(|(btn, x, y, fb)| format!("\x1b[<{btn};{x};{y}{}", fb as char).into_bytes())
2500 }
2501
2502 fn kitty_keyboard_sequence() -> impl Strategy<Value = Vec<u8>> {
2504 let keycode = prop_oneof![
2505 0x20u32..0x7F, 0x57344u32..0x57400, 0x100u32..0x200, ];
2509 let modifier = 1u32..16;
2510 let kind = prop_oneof![Just(1u32), Just(2u32), Just(3u32)]; (keycode, prop::option::of(modifier), prop::option::of(kind)).prop_map(
2512 |(kc, mods, kind)| match (mods, kind) {
2513 (Some(m), Some(k)) => format!("\x1b[{kc};{m}:{k}u").into_bytes(),
2514 (Some(m), None) => format!("\x1b[{kc};{m}u").into_bytes(),
2515 _ => format!("\x1b[{kc}u").into_bytes(),
2516 },
2517 )
2518 }
2519
2520 proptest! {
2521 #[test]
2525 fn osc52_never_panics(seq in osc52_sequence()) {
2526 let mut parser = InputParser::new();
2527 let events = parser.parse(&seq);
2528 for event in &events {
2530 if let Event::Clipboard(c) = event {
2531 prop_assert!(!c.content.is_empty() || c.content.is_empty(),
2532 "Clipboard event must have a content field");
2533 }
2534 }
2535 }
2536
2537 #[test]
2539 fn osc52_corrupt_base64_safe(payload in arb_byte_vec(128)) {
2540 let mut parser = InputParser::new();
2541 let mut input = b"\x1b]52;c;".to_vec();
2542 input.extend_from_slice(&payload);
2543 input.push(0x07); let _ = parser.parse(&input);
2545 }
2546
2547 #[test]
2551 fn sgr_mouse_never_panics(seq in sgr_mouse_sequence()) {
2552 let mut parser = InputParser::new();
2553 let events = parser.parse(&seq);
2554 for event in &events {
2555 let _ = format!("{event:?}");
2557 }
2558 }
2559
2560 #[test]
2562 fn sgr_mouse_extreme_coords(
2563 btn in 0u16..128,
2564 x in 0u16..=65535,
2565 y in 0u16..=65535,
2566 ) {
2567 let mut parser = InputParser::new();
2568 let input = format!("\x1b[<{btn};{x};{y}M").into_bytes();
2569 let events = parser.parse(&input);
2570 for event in &events {
2571 if let Event::Mouse(m) = event {
2572 prop_assert!(m.x <= x, "Mouse x {} > input x {}", m.x, x);
2573 prop_assert!(m.y <= y, "Mouse y {} > input y {}", m.y, y);
2574 }
2575 }
2576 }
2577
2578 #[test]
2582 fn kitty_keyboard_never_panics(seq in kitty_keyboard_sequence()) {
2583 let mut parser = InputParser::new();
2584 let _ = parser.parse(&seq);
2585 }
2586
2587 #[test]
2591 fn truncated_csi_then_valid(
2592 params in prop::collection::vec(0x30u8..=0x3F, 1..=10),
2593 valid_char in 0x20u8..0x7F,
2594 ) {
2595 let mut parser = InputParser::new();
2596
2597 let mut partial = vec![0x1B, b'['];
2599 partial.extend_from_slice(¶ms);
2600 let _ = parser.parse(&partial);
2601
2602 let events = parser.parse(&[0x1B, b'[', b'A']); let _ = events;
2607
2608 let events = parser.parse(&[valid_char]);
2610 let _ = events;
2611 }
2612
2613 #[test]
2615 fn truncated_osc_then_valid(
2616 content in prop::collection::vec(0x20u8..=0x7E, 1..=32),
2617 ) {
2618 let mut parser = InputParser::new();
2619
2620 let mut partial = vec![0x1B, b']'];
2622 partial.extend_from_slice(&content);
2623 let _ = parser.parse(&partial);
2624
2625 let events = parser.parse(b"\x1bz");
2627 let _ = events;
2628 }
2629
2630 #[test]
2634 fn csi_near_limit_produces_event(
2635 fill_byte in 0x30u8..=0x39, ) {
2637 let mut parser = InputParser::new();
2638
2639 let mut input = vec![0x1B, b'['];
2640 input.extend(std::iter::repeat_n(fill_byte, MAX_CSI_LEN - 1));
2642 input.push(b'm'); let events = parser.parse(&input);
2645 let _ = events;
2648
2649 let events = parser.parse(b"a");
2651 prop_assert!(!events.is_empty(), "Parser stuck after near-limit CSI");
2652 }
2653
2654 #[test]
2656 fn osc_near_limit_processes(
2657 fill_byte in 0x20u8..=0x7E,
2658 ) {
2659 let mut parser = InputParser::new();
2660
2661 let mut input = vec![0x1B, b']'];
2662 input.extend(std::iter::repeat_n(fill_byte, MAX_OSC_LEN - 1));
2663 input.push(0x07); let _ = parser.parse(&input);
2666
2667 let events = parser.parse(b"b");
2669 prop_assert!(!events.is_empty(), "Parser stuck after near-limit OSC");
2670 }
2671
2672 #[test]
2676 fn consecutive_pastes_emit_events(count in 2usize..=5) {
2677 let mut parser = InputParser::new();
2678 let mut input = Vec::new();
2679
2680 for i in 0..count {
2681 input.extend_from_slice(b"\x1b[200~");
2682 input.extend_from_slice(format!("paste_{i}").as_bytes());
2683 input.extend_from_slice(b"\x1b[201~");
2684 }
2685
2686 let events = parser.parse(&input);
2687 let paste_events: Vec<_> = events.iter()
2688 .filter(|e| matches!(e, Event::Paste(_)))
2689 .collect();
2690
2691 prop_assert_eq!(paste_events.len(), count,
2692 "Expected {} paste events, got {}", count, paste_events.len());
2693 }
2694
2695 #[test]
2697 fn paste_with_invalid_utf8(content in arb_byte_vec(256)) {
2698 let mut parser = InputParser::new();
2699 let mut input = b"\x1b[200~".to_vec();
2700 input.extend_from_slice(&content);
2701 input.extend_from_slice(b"\x1b[201~");
2702
2703 let events = parser.parse(&input);
2704 for event in &events {
2705 if let Event::Paste(p) = event {
2706 prop_assert!(p.text.is_char_boundary(0), "Paste text is not valid UTF-8");
2708 }
2709 }
2710 }
2711
2712 #[test]
2716 fn recovery_via_esc_reset(garbage in arb_byte_vec(256)) {
2717 let mut parser = InputParser::new();
2718 let _ = parser.parse(&garbage);
2719
2720 let _ = parser.parse(b"\x07\x1b\\\x1b");
2723 let _ = parser.parse(b"\x1b");
2724
2725 let _ = parser.parse(b"z");
2727
2728 let events = parser.parse(b"q");
2730 prop_assert!(!events.is_empty(),
2733 "Parser did not recover after garbage + reset");
2734 }
2735 }
2736}