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 => Some(KeyCode::F((key_code - 57_364 + 1) as u8)),
525 57_358..=57_363 | 57_388..=63_743 => None,
527 _ => char::from_u32(key_code).map(KeyCode::Char),
529 }
530 }
531
532 fn modifiers_from_xterm(value: u32) -> Modifiers {
533 let bits = value.saturating_sub(1);
536 let mut mods = Modifiers::NONE;
537 if bits & 1 != 0 {
538 mods |= Modifiers::SHIFT;
539 }
540 if bits & 2 != 0 {
541 mods |= Modifiers::ALT;
542 }
543 if bits & 4 != 0 {
544 mods |= Modifiers::CTRL;
545 }
546 if bits & 8 != 0 {
547 mods |= Modifiers::SUPER;
548 }
549 mods
550 }
551
552 fn key_with_modifiers(&self, code: KeyCode, params: &[u8]) -> KeyEvent {
554 KeyEvent::new(code).with_modifiers(self.parse_modifier_param(params))
555 }
556
557 fn parse_sgr_mouse(&self, params: &[u8], final_byte: u8) -> Option<Event> {
559 let params = ¶ms[1..];
562 let s = std::str::from_utf8(params).ok()?;
563 let mut parts = s.split(';');
564
565 let button_code: u16 = parts.next()?.parse().ok()?;
566 let x: u16 = parts.next()?.parse().ok()?;
567 let y: u16 = parts.next()?.parse().ok()?;
568
569 let (button, mods) = self.decode_mouse_button(button_code);
571
572 let kind = if final_byte == b'M' {
573 if button_code & 64 != 0 {
574 match button_code & 3 {
577 0 => MouseEventKind::ScrollUp,
578 1 => MouseEventKind::ScrollDown,
579 2 => MouseEventKind::ScrollLeft,
580 _ => MouseEventKind::ScrollRight,
581 }
582 } else if button_code & 32 != 0 {
583 if button_code & 3 == 3 {
586 MouseEventKind::Moved
587 } else {
588 MouseEventKind::Drag(button)
589 }
590 } else {
591 MouseEventKind::Down(button)
592 }
593 } else {
594 MouseEventKind::Up(button)
595 };
596
597 Some(Event::Mouse(MouseEvent {
598 kind,
599 x: x.saturating_sub(1), y: y.saturating_sub(1),
601 modifiers: mods,
602 }))
603 }
604
605 fn decode_mouse_button(&self, code: u16) -> (MouseButton, Modifiers) {
607 let button = match code & 0b11 {
608 0 => MouseButton::Left,
609 1 => MouseButton::Middle,
610 2 => MouseButton::Right,
611 _ => MouseButton::Left,
612 };
613
614 let mut mods = Modifiers::NONE;
615 if code & 4 != 0 {
616 mods |= Modifiers::SHIFT;
617 }
618 if code & 8 != 0 {
619 mods |= Modifiers::ALT;
620 }
621 if code & 16 != 0 {
622 mods |= Modifiers::CTRL;
623 }
624
625 (button, mods)
626 }
627
628 fn process_ss3(&mut self, byte: u8) -> Option<Event> {
630 if byte == 0x1B {
632 self.state = ParserState::Escape;
633 return None;
634 }
635
636 self.state = ParserState::Ground;
637
638 let code = match byte {
639 b'P' => KeyCode::F(1),
640 b'Q' => KeyCode::F(2),
641 b'R' => KeyCode::F(3),
642 b'S' => KeyCode::F(4),
643 b'A' => KeyCode::Up,
644 b'B' => KeyCode::Down,
645 b'C' => KeyCode::Right,
646 b'D' => KeyCode::Left,
647 b'H' => KeyCode::Home,
648 b'F' => KeyCode::End,
649 _ => return None,
650 };
651
652 Some(Event::Key(KeyEvent::new(code)))
653 }
654
655 fn process_osc(&mut self, byte: u8) -> Option<Event> {
657 if byte == 0x1B {
659 self.state = ParserState::OscEscape;
660 return None;
661 }
662
663 self.buffer.push(byte);
664
665 match byte {
666 0x07 => {
668 self.state = ParserState::Ground;
669 self.parse_osc_sequence()
670 }
671 _ => {
673 self.state = ParserState::OscContent;
674 None
675 }
676 }
677 }
678
679 fn process_osc_content(&mut self, byte: u8) -> Option<Event> {
681 if byte == 0x1B {
683 self.state = ParserState::OscEscape;
684 return None;
685 }
686
687 if byte < 0x20 && byte != 0x07 {
689 self.state = ParserState::Ground;
690 self.buffer.clear();
691 return None;
692 }
693
694 if self.buffer.len() >= MAX_OSC_LEN {
696 self.state = ParserState::OscIgnore;
697 self.buffer.clear();
698 return None;
699 }
700
701 match byte {
702 0x07 => {
704 self.state = ParserState::Ground;
705 self.parse_osc_sequence()
706 }
707 _ => {
709 self.buffer.push(byte);
710 None
711 }
712 }
713 }
714
715 fn process_osc_escape(&mut self, byte: u8) -> Option<Event> {
717 if byte == b'\\' {
718 self.state = ParserState::Ground;
720 self.parse_osc_sequence()
721 } else if byte == 0x1B {
722 self.state = ParserState::Escape;
724 self.buffer.clear();
725 None
726 } else {
727 self.buffer.clear();
733 self.state = ParserState::Escape;
734 self.process_escape(byte)
735 }
736 }
737
738 fn process_osc_ignore(&mut self, byte: u8) -> Option<Event> {
740 match byte {
741 0x07 => {
743 self.state = ParserState::Ground;
744 None
745 }
746 0x1B => {
748 self.state = ParserState::OscEscape;
749 None
750 }
751 _ if byte < 0x20 => {
753 self.state = ParserState::Ground;
754 None
755 }
756 _ => None,
758 }
759 }
760
761 fn parse_osc_sequence(&mut self) -> Option<Event> {
763 let seq = std::mem::take(&mut self.buffer);
764
765 if seq.starts_with(b"52;") {
767 return self.parse_osc52_clipboard(&seq);
768 }
769
770 None
772 }
773
774 fn parse_osc52_clipboard(&self, seq: &[u8]) -> Option<Event> {
776 let content = &seq[3..]; if content.is_empty() {
779 return None;
780 }
781
782 let source = ClipboardSource::Osc52;
785
786 let base64_start = content.iter().position(|&b| b == b';').map(|i| i + 1)?;
788 let base64_data = &content[base64_start..];
789
790 let decoded = self.decode_base64(base64_data)?;
792
793 Some(Event::Clipboard(ClipboardEvent::new(
794 String::from_utf8_lossy(&decoded).into_owned(),
795 source,
796 )))
797 }
798
799 fn decode_base64(&self, input: &[u8]) -> Option<Vec<u8>> {
801 const DECODE_TABLE: [i8; 256] = {
802 let mut table = [-1i8; 256];
803 let mut i = 0u8;
804 while i < 26 {
805 table[(b'A' + i) as usize] = i as i8;
806 table[(b'a' + i) as usize] = (i + 26) as i8;
807 i += 1;
808 }
809 let mut i = 0u8;
810 while i < 10 {
811 table[(b'0' + i) as usize] = (i + 52) as i8;
812 i += 1;
813 }
814 table[b'+' as usize] = 62;
815 table[b'/' as usize] = 63;
816 table
817 };
818
819 let mut output = Vec::with_capacity(input.len() * 3 / 4);
820 let mut buffer = 0u32;
821 let mut bits = 0u8;
822
823 for &byte in input {
824 if byte == b'=' {
825 break;
826 }
827 let value = DECODE_TABLE[byte as usize];
828 if value < 0 {
829 continue; }
831 buffer = (buffer << 6) | (value as u32);
832 bits += 6;
833 if bits >= 8 {
834 bits -= 8;
835 output.push((buffer >> bits) as u8);
836 buffer &= (1 << bits) - 1;
837 }
838 }
839
840 Some(output)
841 }
842
843 fn process_utf8(&mut self, byte: u8, collected: u8, expected: u8) -> Option<Event> {
845 if (byte & 0xC0) != 0x80 {
847 self.state = ParserState::Ground;
850
851 self.pending_event = self.process_ground(byte);
853
854 return Some(Event::Key(KeyEvent::new(KeyCode::Char(
855 std::char::REPLACEMENT_CHARACTER,
856 ))));
857 }
858
859 self.utf8_buffer[collected as usize] = byte;
860 let new_collected = collected + 1;
861
862 if new_collected == expected {
863 self.state = ParserState::Ground;
865 let s = std::str::from_utf8(&self.utf8_buffer[..expected as usize]).ok()?;
866 let c = s.chars().next()?;
867 Some(Event::Key(KeyEvent::new(KeyCode::Char(c))))
868 } else {
869 self.state = ParserState::Utf8 {
871 collected: new_collected,
872 expected,
873 };
874 None
875 }
876 }
877
878 fn process_paste_byte(&mut self, byte: u8) -> Option<Event> {
880 const END_SEQ: &[u8] = b"\x1b[201~";
881
882 if self.paste_buffer.len() < MAX_PASTE_LEN {
888 self.paste_buffer.push(byte);
889
890 if self.paste_buffer.ends_with(END_SEQ) {
892 self.in_paste = false;
893 let content_len = self.paste_buffer.len() - END_SEQ.len();
895 let content =
896 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
897 self.paste_buffer.clear();
898 return Some(Event::Paste(PasteEvent::bracketed(content)));
899 }
900 } else {
901 self.buffer.push(byte);
906 if self.buffer.len() > END_SEQ.len() {
907 self.buffer.remove(0);
908 }
909
910 let mut last_bytes = [0u8; 6];
917 let tail_len = self.buffer.len();
918 let paste_len = self.paste_buffer.len();
919
920 if tail_len + paste_len >= 6 {
921 for i in 0..tail_len {
923 last_bytes[6 - tail_len + i] = self.buffer[i];
924 }
925 let remaining = 6 - tail_len;
927 if remaining > 0 {
928 let start = paste_len - remaining;
929 last_bytes[..remaining]
930 .copy_from_slice(&self.paste_buffer[start..(remaining + start)]);
931 }
932
933 if last_bytes == END_SEQ {
934 self.in_paste = false;
935
936 let content_len = paste_len - remaining;
941 let content =
942 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
943
944 self.paste_buffer.clear();
945 self.buffer.clear();
946
947 return Some(Event::Paste(PasteEvent::bracketed(content)));
948 }
949 }
950 }
951
952 None
953 }
954}
955
956#[cfg(test)]
957mod tests {
958 use super::*;
959
960 #[test]
961 fn csi_ignore_handles_final_bytes() {
962 let mut parser = InputParser::new();
963
964 let mut seq = vec![0x1B, b'['];
967 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100)); seq.push(b'@'); let events = parser.parse(&seq);
971 assert_eq!(events.len(), 0);
972
973 let events = parser.parse(b"a");
976 assert_eq!(events.len(), 1, "Subsequent char 'a' was swallowed");
977 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
978 }
979
980 #[test]
981 fn ascii_characters_parsed() {
982 let mut parser = InputParser::new();
983
984 let events = parser.parse(b"abc");
985 assert_eq!(events.len(), 3);
986 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
987 assert!(matches!(events[1], Event::Key(k) if k.code == KeyCode::Char('b')));
988 assert!(matches!(events[2], Event::Key(k) if k.code == KeyCode::Char('c')));
989 }
990
991 #[test]
992 fn control_characters() {
993 let mut parser = InputParser::new();
994
995 let events = parser.parse(&[0x01]);
997 assert_eq!(events.len(), 1);
998 assert!(matches!(
999 events[0],
1000 Event::Key(k) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::CTRL)
1001 ));
1002
1003 let events = parser.parse(&[0x7F]);
1005 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace));
1006 }
1007
1008 #[test]
1009 fn arrow_keys() {
1010 let mut parser = InputParser::new();
1011
1012 assert!(matches!(
1013 parser.parse(b"\x1b[A").first(),
1014 Some(Event::Key(k)) if k.code == KeyCode::Up
1015 ));
1016 assert!(matches!(
1017 parser.parse(b"\x1b[B").first(),
1018 Some(Event::Key(k)) if k.code == KeyCode::Down
1019 ));
1020 assert!(matches!(
1021 parser.parse(b"\x1b[C").first(),
1022 Some(Event::Key(k)) if k.code == KeyCode::Right
1023 ));
1024 assert!(matches!(
1025 parser.parse(b"\x1b[D").first(),
1026 Some(Event::Key(k)) if k.code == KeyCode::Left
1027 ));
1028 }
1029
1030 #[test]
1031 fn function_keys_ss3() {
1032 let mut parser = InputParser::new();
1033
1034 assert!(matches!(
1035 parser.parse(b"\x1bOP").first(),
1036 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1037 ));
1038 assert!(matches!(
1039 parser.parse(b"\x1bOQ").first(),
1040 Some(Event::Key(k)) if k.code == KeyCode::F(2)
1041 ));
1042 assert!(matches!(
1043 parser.parse(b"\x1bOR").first(),
1044 Some(Event::Key(k)) if k.code == KeyCode::F(3)
1045 ));
1046 assert!(matches!(
1047 parser.parse(b"\x1bOS").first(),
1048 Some(Event::Key(k)) if k.code == KeyCode::F(4)
1049 ));
1050 }
1051
1052 #[test]
1053 fn function_keys_csi() {
1054 let mut parser = InputParser::new();
1055
1056 assert!(matches!(
1057 parser.parse(b"\x1b[15~").first(),
1058 Some(Event::Key(k)) if k.code == KeyCode::F(5)
1059 ));
1060 assert!(matches!(
1061 parser.parse(b"\x1b[17~").first(),
1062 Some(Event::Key(k)) if k.code == KeyCode::F(6)
1063 ));
1064 }
1065
1066 #[test]
1067 fn modifiers_in_csi() {
1068 let mut parser = InputParser::new();
1069
1070 let events = parser.parse(b"\x1b[1;2A");
1072 assert!(matches!(
1073 events.first(),
1074 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::SHIFT)
1075 ));
1076
1077 let events = parser.parse(b"\x1b[1;5A");
1079 assert!(matches!(
1080 events.first(),
1081 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::CTRL)
1082 ));
1083 }
1084
1085 #[test]
1086 fn kitty_keyboard_basic_char() {
1087 let mut parser = InputParser::new();
1088
1089 let events = parser.parse(b"\x1b[97u");
1090 assert!(matches!(
1091 events.first(),
1092 Some(Event::Key(k))
1093 if k.code == KeyCode::Char('a')
1094 && k.modifiers == Modifiers::NONE
1095 && k.kind == KeyEventKind::Press
1096 ));
1097 }
1098
1099 #[test]
1100 fn kitty_keyboard_with_modifiers_and_kind() {
1101 let mut parser = InputParser::new();
1102
1103 let events = parser.parse(b"\x1b[97;5:2u");
1105 assert!(matches!(
1106 events.first(),
1107 Some(Event::Key(k))
1108 if k.code == KeyCode::Char('a')
1109 && k.modifiers.contains(Modifiers::CTRL)
1110 && k.kind == KeyEventKind::Repeat
1111 ));
1112 }
1113
1114 #[test]
1115 fn kitty_keyboard_function_key() {
1116 let mut parser = InputParser::new();
1117
1118 let events = parser.parse(b"\x1b[57364;1u");
1119 assert!(matches!(
1120 events.first(),
1121 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1122 ));
1123 }
1124
1125 #[test]
1126 fn alt_key_escapes() {
1127 let mut parser = InputParser::new();
1128
1129 let events = parser.parse(b"\x1ba");
1130 assert!(matches!(
1131 events.first(),
1132 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::ALT)
1133 ));
1134 }
1135
1136 #[test]
1137 fn escape_escape_resets_state() {
1138 let mut parser = InputParser::new();
1139
1140 let events = parser.parse(b"\x1b\x1b");
1141 assert!(matches!(
1142 events.first(),
1143 Some(Event::Key(k)) if k.code == KeyCode::Escape && k.modifiers.contains(Modifiers::ALT)
1144 ));
1145
1146 let events = parser.parse(b"a");
1147 assert!(matches!(
1148 events.first(),
1149 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers == Modifiers::NONE
1150 ));
1151 }
1152
1153 #[test]
1154 fn focus_events() {
1155 let mut parser = InputParser::new();
1156
1157 assert!(matches!(
1158 parser.parse(b"\x1b[I").first(),
1159 Some(Event::Focus(true))
1160 ));
1161 assert!(matches!(
1162 parser.parse(b"\x1b[O").first(),
1163 Some(Event::Focus(false))
1164 ));
1165 }
1166
1167 #[test]
1168 fn bracketed_paste() {
1169 let mut parser = InputParser::new();
1170
1171 let events = parser.parse(b"\x1b[200~hello world\x1b[201~");
1173 assert_eq!(events.len(), 1);
1174 assert!(matches!(
1175 &events[0],
1176 Event::Paste(p) if p.text == "hello world"
1177 ));
1178 }
1179
1180 #[test]
1181 fn mouse_sgr_protocol() {
1182 let mut parser = InputParser::new();
1183
1184 let events = parser.parse(b"\x1b[<0;10;20M");
1186 assert!(matches!(
1187 events.first(),
1188 Some(Event::Mouse(m)) if m.x == 9 && m.y == 19 ));
1190 }
1191
1192 #[test]
1193 fn mouse_sgr_scroll_up() {
1194 let mut parser = InputParser::new();
1195
1196 let events = parser.parse(b"\x1b[<64;5;5M");
1198 assert!(matches!(
1199 events.first(),
1200 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollUp)
1201 ));
1202 }
1203
1204 #[test]
1205 fn mouse_sgr_scroll_down() {
1206 let mut parser = InputParser::new();
1207
1208 let events = parser.parse(b"\x1b[<65;5;5M");
1210 assert!(matches!(
1211 events.first(),
1212 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollDown)
1213 ));
1214 }
1215
1216 #[test]
1217 fn mouse_sgr_scroll_left() {
1218 let mut parser = InputParser::new();
1219
1220 let events = parser.parse(b"\x1b[<66;5;5M");
1222 assert!(matches!(
1223 events.first(),
1224 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollLeft)
1225 ));
1226 }
1227
1228 #[test]
1229 fn mouse_sgr_scroll_right() {
1230 let mut parser = InputParser::new();
1231
1232 let events = parser.parse(b"\x1b[<67;5;5M");
1234 assert!(matches!(
1235 events.first(),
1236 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollRight)
1237 ));
1238 }
1239
1240 #[test]
1241 fn mouse_sgr_drag_left() {
1242 let mut parser = InputParser::new();
1243
1244 let events = parser.parse(b"\x1b[<32;10;20M");
1246 assert!(matches!(
1247 events.first(),
1248 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Drag(MouseButton::Left))
1249 ));
1250 }
1251
1252 #[test]
1253 fn utf8_characters() {
1254 let mut parser = InputParser::new();
1255
1256 let events = parser.parse(&[0xC3, 0xA9]);
1258 assert!(matches!(
1259 events.first(),
1260 Some(Event::Key(k)) if k.code == KeyCode::Char('é')
1261 ));
1262 }
1263
1264 #[test]
1265 fn dos_protection_csi() {
1266 let mut parser = InputParser::new();
1267
1268 let mut seq = vec![0x1B, b'['];
1270 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
1271 seq.push(b'A');
1272
1273 let events = parser.parse(&seq);
1276 assert_eq!(
1277 events.len(),
1278 0,
1279 "Oversized CSI sequence should produce no events"
1280 );
1281
1282 let events = parser.parse(b"\x1b[A");
1285 assert!(matches!(
1286 events.first(),
1287 Some(Event::Key(k)) if k.code == KeyCode::Up
1288 ));
1289 }
1290
1291 #[test]
1292 fn dos_protection_paste() {
1293 let mut parser = InputParser::new();
1294
1295 parser.parse(b"\x1b[200~");
1297
1298 let content = vec![b'x'; MAX_PASTE_LEN - 100]; parser.parse(&content);
1301
1302 let events = parser.parse(b"\x1b[201~");
1304
1305 assert!(matches!(
1307 events.first(),
1308 Some(Event::Paste(p)) if p.text.len() <= MAX_PASTE_LEN
1309 ));
1310 }
1311
1312 #[test]
1313 fn dos_protection_paste_overflow_terminator() {
1314 let mut parser = InputParser::new();
1315
1316 parser.parse(b"\x1b[200~");
1318
1319 let overflow = 100;
1323 let content = vec![b'a'; MAX_PASTE_LEN + overflow];
1324 parser.parse(&content);
1325
1326 let events = parser.parse(b"\x1b[201~");
1329
1330 assert_eq!(events.len(), 1, "Should emit paste event");
1331 match &events[0] {
1332 Event::Paste(p) => {
1333 assert_eq!(
1336 p.text.len(),
1337 MAX_PASTE_LEN,
1338 "Paste should be capped at MAX_PASTE_LEN bytes"
1339 );
1340 assert!(p.text.chars().all(|c| c == 'a'));
1342 }
1343 _ => unreachable!("Expected Paste event"),
1344 }
1345
1346 let events = parser.parse(b"b");
1348 assert_eq!(events.len(), 1);
1349 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('b')));
1350 }
1351
1352 #[test]
1353 fn no_panic_on_invalid_input() {
1354 let mut parser = InputParser::new();
1355
1356 let garbage = [0xFF, 0xFE, 0x00, 0x1B, 0x1B, 0x1B, b'[', 0xFF, b']', 0x00];
1358
1359 let _ = parser.parse(&garbage);
1361 }
1362
1363 #[test]
1364 fn dos_protection_paste_boundary() {
1365 let mut parser = InputParser::new();
1366 parser.parse(b"\x1b[200~");
1368
1369 let content = vec![b'x'; MAX_PASTE_LEN];
1371 parser.parse(&content);
1372
1373 let events = parser.parse(b"\x1b[201~");
1376
1377 assert!(
1378 !events.is_empty(),
1379 "Parser trapped in paste mode after hitting limit"
1380 );
1381 assert!(matches!(events[0], Event::Paste(_)));
1382 }
1383
1384 #[test]
1387 fn csi_tilde_home() {
1388 let mut parser = InputParser::new();
1389 let events = parser.parse(b"\x1b[1~");
1390 assert!(matches!(
1391 events.first(),
1392 Some(Event::Key(k)) if k.code == KeyCode::Home
1393 ));
1394 }
1395
1396 #[test]
1397 fn csi_tilde_insert() {
1398 let mut parser = InputParser::new();
1399 let events = parser.parse(b"\x1b[2~");
1400 assert!(matches!(
1401 events.first(),
1402 Some(Event::Key(k)) if k.code == KeyCode::Insert
1403 ));
1404 }
1405
1406 #[test]
1407 fn csi_tilde_delete() {
1408 let mut parser = InputParser::new();
1409 let events = parser.parse(b"\x1b[3~");
1410 assert!(matches!(
1411 events.first(),
1412 Some(Event::Key(k)) if k.code == KeyCode::Delete
1413 ));
1414 }
1415
1416 #[test]
1417 fn csi_tilde_end() {
1418 let mut parser = InputParser::new();
1419 let events = parser.parse(b"\x1b[4~");
1420 assert!(matches!(
1421 events.first(),
1422 Some(Event::Key(k)) if k.code == KeyCode::End
1423 ));
1424 }
1425
1426 #[test]
1427 fn csi_tilde_page_up() {
1428 let mut parser = InputParser::new();
1429 let events = parser.parse(b"\x1b[5~");
1430 assert!(matches!(
1431 events.first(),
1432 Some(Event::Key(k)) if k.code == KeyCode::PageUp
1433 ));
1434 }
1435
1436 #[test]
1437 fn csi_tilde_page_down() {
1438 let mut parser = InputParser::new();
1439 let events = parser.parse(b"\x1b[6~");
1440 assert!(matches!(
1441 events.first(),
1442 Some(Event::Key(k)) if k.code == KeyCode::PageDown
1443 ));
1444 }
1445
1446 #[test]
1449 fn csi_home_and_end() {
1450 let mut parser = InputParser::new();
1451 assert!(matches!(
1452 parser.parse(b"\x1b[H").first(),
1453 Some(Event::Key(k)) if k.code == KeyCode::Home
1454 ));
1455 assert!(matches!(
1456 parser.parse(b"\x1b[F").first(),
1457 Some(Event::Key(k)) if k.code == KeyCode::End
1458 ));
1459 }
1460
1461 #[test]
1464 fn ss3_home_and_end() {
1465 let mut parser = InputParser::new();
1466 assert!(matches!(
1467 parser.parse(b"\x1bOH").first(),
1468 Some(Event::Key(k)) if k.code == KeyCode::Home
1469 ));
1470 assert!(matches!(
1471 parser.parse(b"\x1bOF").first(),
1472 Some(Event::Key(k)) if k.code == KeyCode::End
1473 ));
1474 }
1475
1476 #[test]
1479 fn backtab_csi_z() {
1480 let mut parser = InputParser::new();
1481 let events = parser.parse(b"\x1b[Z");
1482 assert!(matches!(
1483 events.first(),
1484 Some(Event::Key(k)) if k.code == KeyCode::BackTab
1485 ));
1486 }
1487
1488 #[test]
1491 fn function_keys_f7_to_f12() {
1492 let mut parser = InputParser::new();
1493 assert!(matches!(
1494 parser.parse(b"\x1b[18~").first(),
1495 Some(Event::Key(k)) if k.code == KeyCode::F(7)
1496 ));
1497 assert!(matches!(
1498 parser.parse(b"\x1b[19~").first(),
1499 Some(Event::Key(k)) if k.code == KeyCode::F(8)
1500 ));
1501 assert!(matches!(
1502 parser.parse(b"\x1b[20~").first(),
1503 Some(Event::Key(k)) if k.code == KeyCode::F(9)
1504 ));
1505 assert!(matches!(
1506 parser.parse(b"\x1b[21~").first(),
1507 Some(Event::Key(k)) if k.code == KeyCode::F(10)
1508 ));
1509 assert!(matches!(
1510 parser.parse(b"\x1b[23~").first(),
1511 Some(Event::Key(k)) if k.code == KeyCode::F(11)
1512 ));
1513 assert!(matches!(
1514 parser.parse(b"\x1b[24~").first(),
1515 Some(Event::Key(k)) if k.code == KeyCode::F(12)
1516 ));
1517 }
1518
1519 #[test]
1522 fn ctrl_home_and_alt_end() {
1523 let mut parser = InputParser::new();
1524
1525 let events = parser.parse(b"\x1b[1;5H");
1527 assert!(matches!(
1528 events.first(),
1529 Some(Event::Key(k)) if k.code == KeyCode::Home && k.modifiers.contains(Modifiers::CTRL)
1530 ));
1531
1532 let events = parser.parse(b"\x1b[1;3F");
1534 assert!(matches!(
1535 events.first(),
1536 Some(Event::Key(k)) if k.code == KeyCode::End && k.modifiers.contains(Modifiers::ALT)
1537 ));
1538 }
1539
1540 #[test]
1541 fn shift_ctrl_arrow() {
1542 let mut parser = InputParser::new();
1543
1544 let events = parser.parse(b"\x1b[1;6C");
1546 assert!(matches!(
1547 events.first(),
1548 Some(Event::Key(k)) if k.code == KeyCode::Right
1549 && k.modifiers.contains(Modifiers::SHIFT)
1550 && k.modifiers.contains(Modifiers::CTRL)
1551 ));
1552 }
1553
1554 #[test]
1555 fn modifiers_on_tilde_keys() {
1556 let mut parser = InputParser::new();
1557
1558 let events = parser.parse(b"\x1b[3;5~");
1560 assert!(matches!(
1561 events.first(),
1562 Some(Event::Key(k)) if k.code == KeyCode::Delete && k.modifiers.contains(Modifiers::CTRL)
1563 ));
1564
1565 let events = parser.parse(b"\x1b[5;2~");
1567 assert!(matches!(
1568 events.first(),
1569 Some(Event::Key(k)) if k.code == KeyCode::PageUp && k.modifiers.contains(Modifiers::SHIFT)
1570 ));
1571 }
1572
1573 #[test]
1576 fn mouse_sgr_right_click() {
1577 let mut parser = InputParser::new();
1578 let events = parser.parse(b"\x1b[<2;15;10M");
1580 assert!(matches!(
1581 events.first(),
1582 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Right))
1583 && m.x == 14 && m.y == 9
1584 ));
1585 }
1586
1587 #[test]
1588 fn mouse_sgr_middle_click() {
1589 let mut parser = InputParser::new();
1590 let events = parser.parse(b"\x1b[<1;5;5M");
1592 assert!(matches!(
1593 events.first(),
1594 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Middle))
1595 ));
1596 }
1597
1598 #[test]
1599 fn mouse_sgr_button_release() {
1600 let mut parser = InputParser::new();
1601 let events = parser.parse(b"\x1b[<0;10;20m");
1603 assert!(matches!(
1604 events.first(),
1605 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Up(MouseButton::Left))
1606 ));
1607 }
1608
1609 #[test]
1610 fn mouse_sgr_moved() {
1611 let mut parser = InputParser::new();
1612 let events = parser.parse(b"\x1b[<35;10;20M");
1614 assert!(matches!(
1615 events.first(),
1616 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Moved)
1617 ));
1618 }
1619
1620 #[test]
1621 fn mouse_sgr_with_modifiers() {
1622 let mut parser = InputParser::new();
1623 let events = parser.parse(b"\x1b[<4;5;5M");
1625 assert!(matches!(
1626 events.first(),
1627 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1628 && m.modifiers.contains(Modifiers::SHIFT)
1629 ));
1630
1631 let events = parser.parse(b"\x1b[<16;5;5M");
1633 assert!(matches!(
1634 events.first(),
1635 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1636 && m.modifiers.contains(Modifiers::CTRL)
1637 ));
1638
1639 let events = parser.parse(b"\x1b[<8;5;5M");
1641 assert!(matches!(
1642 events.first(),
1643 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1644 && m.modifiers.contains(Modifiers::ALT)
1645 ));
1646 }
1647
1648 #[test]
1651 fn kitty_keyboard_release_event() {
1652 let mut parser = InputParser::new();
1653 let events = parser.parse(b"\x1b[97;1:3u");
1655 assert!(matches!(
1656 events.first(),
1657 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.kind == KeyEventKind::Release
1658 ));
1659 }
1660
1661 #[test]
1662 fn kitty_keyboard_special_keys() {
1663 let mut parser = InputParser::new();
1664
1665 assert!(matches!(
1667 parser.parse(b"\x1b[57344u").first(),
1668 Some(Event::Key(k)) if k.code == KeyCode::Escape
1669 ));
1670
1671 assert!(matches!(
1673 parser.parse(b"\x1b[57345u").first(),
1674 Some(Event::Key(k)) if k.code == KeyCode::Enter
1675 ));
1676
1677 assert!(matches!(
1679 parser.parse(b"\x1b[57346u").first(),
1680 Some(Event::Key(k)) if k.code == KeyCode::Tab
1681 ));
1682
1683 assert!(matches!(
1685 parser.parse(b"\x1b[57347u").first(),
1686 Some(Event::Key(k)) if k.code == KeyCode::Backspace
1687 ));
1688
1689 assert!(matches!(
1691 parser.parse(b"\x1b[57348u").first(),
1692 Some(Event::Key(k)) if k.code == KeyCode::Insert
1693 ));
1694
1695 assert!(matches!(
1697 parser.parse(b"\x1b[57349u").first(),
1698 Some(Event::Key(k)) if k.code == KeyCode::Delete
1699 ));
1700 }
1701
1702 #[test]
1703 fn kitty_keyboard_navigation_keys() {
1704 let mut parser = InputParser::new();
1705
1706 assert!(matches!(
1708 parser.parse(b"\x1b[57350u").first(),
1709 Some(Event::Key(k)) if k.code == KeyCode::Left
1710 ));
1711 assert!(matches!(
1713 parser.parse(b"\x1b[57351u").first(),
1714 Some(Event::Key(k)) if k.code == KeyCode::Right
1715 ));
1716 assert!(matches!(
1718 parser.parse(b"\x1b[57352u").first(),
1719 Some(Event::Key(k)) if k.code == KeyCode::Up
1720 ));
1721 assert!(matches!(
1723 parser.parse(b"\x1b[57353u").first(),
1724 Some(Event::Key(k)) if k.code == KeyCode::Down
1725 ));
1726 assert!(matches!(
1728 parser.parse(b"\x1b[57354u").first(),
1729 Some(Event::Key(k)) if k.code == KeyCode::PageUp
1730 ));
1731 assert!(matches!(
1733 parser.parse(b"\x1b[57355u").first(),
1734 Some(Event::Key(k)) if k.code == KeyCode::PageDown
1735 ));
1736 assert!(matches!(
1738 parser.parse(b"\x1b[57356u").first(),
1739 Some(Event::Key(k)) if k.code == KeyCode::Home
1740 ));
1741 assert!(matches!(
1743 parser.parse(b"\x1b[57357u").first(),
1744 Some(Event::Key(k)) if k.code == KeyCode::End
1745 ));
1746 }
1747
1748 #[test]
1749 fn kitty_keyboard_f_keys() {
1750 let mut parser = InputParser::new();
1751 assert!(matches!(
1753 parser.parse(b"\x1b[57364u").first(),
1754 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1755 ));
1756 assert!(matches!(
1758 parser.parse(b"\x1b[57375u").first(),
1759 Some(Event::Key(k)) if k.code == KeyCode::F(12)
1760 ));
1761 assert!(matches!(
1763 parser.parse(b"\x1b[57387u").first(),
1764 Some(Event::Key(k)) if k.code == KeyCode::F(24)
1765 ));
1766 }
1767
1768 #[test]
1769 fn kitty_keyboard_ascii_as_standard() {
1770 let mut parser = InputParser::new();
1771 assert!(matches!(
1773 parser.parse(b"\x1b[9u").first(),
1774 Some(Event::Key(k)) if k.code == KeyCode::Tab
1775 ));
1776 assert!(matches!(
1777 parser.parse(b"\x1b[13u").first(),
1778 Some(Event::Key(k)) if k.code == KeyCode::Enter
1779 ));
1780 assert!(matches!(
1781 parser.parse(b"\x1b[27u").first(),
1782 Some(Event::Key(k)) if k.code == KeyCode::Escape
1783 ));
1784 assert!(matches!(
1785 parser.parse(b"\x1b[127u").first(),
1786 Some(Event::Key(k)) if k.code == KeyCode::Backspace
1787 ));
1788 assert!(matches!(
1790 parser.parse(b"\x1b[8u").first(),
1791 Some(Event::Key(k)) if k.code == KeyCode::Backspace
1792 ));
1793 }
1794
1795 #[test]
1798 fn osc52_clipboard_bel_terminated() {
1799 let mut parser = InputParser::new();
1800 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x07");
1803 assert!(matches!(
1804 events.first(),
1805 Some(Event::Clipboard(c)) if c.content == "hello" && c.source == ClipboardSource::Osc52
1806 ));
1807 }
1808
1809 #[test]
1810 fn osc52_clipboard_st_terminated() {
1811 let mut parser = InputParser::new();
1812 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x1b\\");
1814 assert!(matches!(
1815 events.first(),
1816 Some(Event::Clipboard(c)) if c.content == "hello"
1817 ));
1818 }
1819
1820 #[test]
1821 fn osc52_clipboard_primary_selection() {
1822 let mut parser = InputParser::new();
1823 let events = parser.parse(b"\x1b]52;p;YWJj\x07");
1826 assert!(matches!(
1827 events.first(),
1828 Some(Event::Clipboard(c)) if c.content == "abc"
1829 ));
1830 }
1831
1832 #[test]
1835 fn ctrl_space_is_null() {
1836 let mut parser = InputParser::new();
1837 let events = parser.parse(&[0x00]);
1838 assert!(matches!(
1839 events.first(),
1840 Some(Event::Key(k)) if k.code == KeyCode::Null
1841 ));
1842 }
1843
1844 #[test]
1845 fn all_ctrl_letter_keys() {
1846 let mut parser = InputParser::new();
1847 for byte in 0x01..=0x1Au8 {
1849 let events = parser.parse(&[byte]);
1850 assert_eq!(
1851 events.len(),
1852 1,
1853 "Ctrl+{} should produce one event",
1854 (byte + b'a' - 1) as char
1855 );
1856 match byte {
1857 0x08 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace)),
1858 0x09 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Tab)),
1859 0x0D => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Enter)),
1860 _ => {
1861 let expected_char = (byte + b'a' - 1) as char;
1862 match &events[0] {
1863 Event::Key(k) => {
1864 assert_eq!(
1865 k.code,
1866 KeyCode::Char(expected_char),
1867 "Byte 0x{byte:02X} should produce Ctrl+{expected_char}"
1868 );
1869 assert!(
1870 k.modifiers.contains(Modifiers::CTRL),
1871 "Byte 0x{byte:02X} should have Ctrl modifier"
1872 );
1873 }
1874 other => {
1875 panic!("Byte 0x{byte:02X}: expected Key event, got {other:?}");
1876 }
1877 }
1878 }
1879 }
1880 }
1881 }
1882
1883 #[test]
1886 fn utf8_3byte_cjk() {
1887 let mut parser = InputParser::new();
1888 let events = parser.parse(&[0xE4, 0xB8, 0xAD]);
1890 assert!(matches!(
1891 events.first(),
1892 Some(Event::Key(k)) if k.code == KeyCode::Char('中')
1893 ));
1894 }
1895
1896 #[test]
1897 fn utf8_4byte_emoji() {
1898 let mut parser = InputParser::new();
1899 let events = parser.parse(&[0xF0, 0x9F, 0xA6, 0x80]);
1901 assert!(matches!(
1902 events.first(),
1903 Some(Event::Key(k)) if k.code == KeyCode::Char('🦀')
1904 ));
1905 }
1906
1907 #[test]
1910 fn empty_input_returns_no_events() {
1911 let mut parser = InputParser::new();
1912 let events = parser.parse(b"");
1913 assert!(events.is_empty());
1914 }
1915
1916 #[test]
1919 fn unknown_csi_tilde_ignored() {
1920 let mut parser = InputParser::new();
1921 let events = parser.parse(b"\x1b[99~");
1923 assert!(events.is_empty());
1924
1925 let events = parser.parse(b"a");
1927 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('a')));
1928 }
1929
1930 #[test]
1933 fn alt_special_chars() {
1934 let mut parser = InputParser::new();
1935
1936 let events = parser.parse(b"\x1b ");
1938 assert!(matches!(
1939 events.first(),
1940 Some(Event::Key(k)) if k.code == KeyCode::Char(' ') && k.modifiers.contains(Modifiers::ALT)
1941 ));
1942
1943 let events = parser.parse(b"\x1b5");
1945 assert!(matches!(
1946 events.first(),
1947 Some(Event::Key(k)) if k.code == KeyCode::Char('5') && k.modifiers.contains(Modifiers::ALT)
1948 ));
1949
1950 let events = parser.parse(b"\x1b}");
1952 assert!(matches!(
1953 events.first(),
1954 Some(Event::Key(k)) if k.code == KeyCode::Char('}') && k.modifiers.contains(Modifiers::ALT)
1955 ));
1956 }
1957
1958 #[test]
1961 fn ss3_arrow_keys() {
1962 let mut parser = InputParser::new();
1963 assert!(matches!(
1964 parser.parse(b"\x1bOA").first(),
1965 Some(Event::Key(k)) if k.code == KeyCode::Up
1966 ));
1967 assert!(matches!(
1968 parser.parse(b"\x1bOB").first(),
1969 Some(Event::Key(k)) if k.code == KeyCode::Down
1970 ));
1971 assert!(matches!(
1972 parser.parse(b"\x1bOC").first(),
1973 Some(Event::Key(k)) if k.code == KeyCode::Right
1974 ));
1975 assert!(matches!(
1976 parser.parse(b"\x1bOD").first(),
1977 Some(Event::Key(k)) if k.code == KeyCode::Left
1978 ));
1979 }
1980
1981 #[test]
1984 fn xterm_modifier_encoding() {
1985 assert_eq!(InputParser::modifiers_from_xterm(1), Modifiers::NONE);
1987 assert_eq!(InputParser::modifiers_from_xterm(2), Modifiers::SHIFT);
1988 assert_eq!(InputParser::modifiers_from_xterm(3), Modifiers::ALT);
1989 assert_eq!(
1990 InputParser::modifiers_from_xterm(4),
1991 Modifiers::SHIFT | Modifiers::ALT
1992 );
1993 assert_eq!(InputParser::modifiers_from_xterm(5), Modifiers::CTRL);
1994 assert_eq!(
1995 InputParser::modifiers_from_xterm(6),
1996 Modifiers::SHIFT | Modifiers::CTRL
1997 );
1998 assert_eq!(InputParser::modifiers_from_xterm(9), Modifiers::SUPER);
1999 }
2000
2001 #[test]
2004 fn ss3_interrupted_by_esc() {
2005 let mut parser = InputParser::new();
2006 let events = parser.parse(b"\x1bO\x1b[A");
2008 assert!(matches!(
2010 events.first(),
2011 Some(Event::Key(k)) if k.code == KeyCode::Up
2012 ));
2013 }
2014
2015 #[test]
2018 fn kitty_keyboard_reserved_keycode_ignored() {
2019 let mut parser = InputParser::new();
2020 let events = parser.parse(b"\x1b[57360u");
2022 assert!(events.is_empty());
2023
2024 let events = parser.parse(b"x");
2026 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('x')));
2027 }
2028 #[test]
2029 fn utf8_invalid_sequence_emits_replacement() {
2030 let mut parser = InputParser::new();
2031
2032 let events = parser.parse(&[0xE0, 0x41]);
2036 assert_eq!(events.len(), 2);
2037
2038 match &events[0] {
2039 Event::Key(k) => assert_eq!(k.code, KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2040 _ => panic!("Expected replacement character"),
2041 }
2042
2043 match &events[1] {
2044 Event::Key(k) => assert_eq!(k.code, KeyCode::Char('A')),
2045 _ => panic!("Expected character 'A'"),
2046 }
2047 }
2048
2049 #[test]
2050 fn utf8_invalid_lead_emits_replacement() {
2051 let mut parser = InputParser::new();
2052
2053 let events = parser.parse(&[0xC0, b'a']);
2055 assert!(
2056 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2057 "Expected replacement for invalid lead"
2058 );
2059 assert!(
2060 events
2061 .iter()
2062 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('a'))),
2063 "Expected subsequent ASCII to be preserved"
2064 );
2065
2066 let events = parser.parse(&[0xF5, b'b']);
2068 assert!(
2069 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2070 "Expected replacement for out-of-range lead"
2071 );
2072 assert!(
2073 events
2074 .iter()
2075 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('b'))),
2076 "Expected subsequent ASCII to be preserved"
2077 );
2078 }
2079}
2080
2081#[cfg(test)]
2082mod proptest_fuzz {
2083 use super::*;
2084 use proptest::prelude::*;
2085
2086 fn arb_byte() -> impl Strategy<Value = u8> {
2090 any::<u8>()
2091 }
2092
2093 fn arb_byte_vec(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
2094 prop::collection::vec(arb_byte(), 0..=max_len)
2095 }
2096
2097 fn csi_sequence() -> impl Strategy<Value = Vec<u8>> {
2099 let params = prop::collection::vec(0x30u8..=0x3F, 0..=20);
2100 let final_byte = 0x40u8..=0x7E;
2101 (params, final_byte).prop_map(|(p, f)| {
2102 let mut buf = vec![0x1B, b'['];
2103 buf.extend_from_slice(&p);
2104 buf.push(f);
2105 buf
2106 })
2107 }
2108
2109 fn osc_sequence() -> impl Strategy<Value = Vec<u8>> {
2111 let content = prop::collection::vec(0x20u8..=0x7E, 0..=64);
2112 let terminator = prop_oneof![
2113 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
2116 (content, terminator).prop_map(|(c, t)| {
2117 let mut buf = vec![0x1B, b']'];
2118 buf.extend_from_slice(&c);
2119 buf.extend_from_slice(&t);
2120 buf
2121 })
2122 }
2123
2124 fn ss3_sequence() -> impl Strategy<Value = Vec<u8>> {
2126 (0x40u8..=0x7E).prop_map(|f| vec![0x1B, b'O', f])
2127 }
2128
2129 fn paste_sequence() -> impl Strategy<Value = Vec<u8>> {
2131 prop::collection::vec(0x20u8..=0x7E, 0..=128).prop_map(|content| {
2132 let mut buf = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
2133 buf.extend_from_slice(&content);
2134 buf.extend_from_slice(b"\x1b[201~");
2135 buf
2136 })
2137 }
2138
2139 fn mixed_adversarial() -> impl Strategy<Value = Vec<u8>> {
2141 let fragment = prop_oneof![
2142 csi_sequence(),
2143 osc_sequence(),
2144 ss3_sequence(),
2145 paste_sequence(),
2146 arb_byte_vec(16), Just(vec![0x1B]), Just(vec![0x1B, b'[']), Just(vec![0x1B, b']']), prop::collection::vec(0x80u8..=0xFF, 1..=4), ];
2152 prop::collection::vec(fragment, 1..=8)
2153 .prop_map(|frags| frags.into_iter().flatten().collect())
2154 }
2155
2156 proptest! {
2159 #[test]
2161 fn random_bytes_never_panic(input in arb_byte_vec(512)) {
2162 let mut parser = InputParser::new();
2163 let _ = parser.parse(&input);
2164 }
2165
2166 #[test]
2168 fn parser_recovers_after_garbage(input in arb_byte_vec(256)) {
2169 let mut parser = InputParser::new();
2170 let _ = parser.parse(&input);
2171
2172 let events = parser.parse(b"z");
2174 let _ = events;
2177 }
2178
2179 #[test]
2181 fn mixed_sequences_never_panic(input in mixed_adversarial()) {
2182 let mut parser = InputParser::new();
2183 let _ = parser.parse(&input);
2184 }
2185
2186 #[test]
2188 fn events_are_well_formed(input in arb_byte_vec(256)) {
2189 let mut parser = InputParser::new();
2190 let events = parser.parse(&input);
2191 for event in &events {
2192 let _ = format!("{event:?}");
2194 }
2195 }
2196
2197 #[test]
2199 fn csi_event_count_bounded(seq in csi_sequence()) {
2200 let mut parser = InputParser::new();
2201 let events = parser.parse(&seq);
2202 prop_assert!(events.len() <= seq.len(),
2203 "Got {} events from {} bytes", events.len(), seq.len());
2204 }
2205
2206 #[test]
2208 fn osc_event_count_bounded(seq in osc_sequence()) {
2209 let mut parser = InputParser::new();
2210 let events = parser.parse(&seq);
2211 prop_assert!(events.len() <= seq.len(),
2212 "Got {} events from {} bytes", events.len(), seq.len());
2213 }
2214
2215 #[test]
2217 fn paste_content_bounded(content in prop::collection::vec(arb_byte(), 0..=2048)) {
2218 let mut parser = InputParser::new();
2219 let mut input = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
2220 input.extend_from_slice(&content);
2221 input.extend_from_slice(b"\x1b[201~");
2222
2223 let events = parser.parse(&input);
2224 for event in &events {
2225 if let Event::Paste(p) = event {
2226 prop_assert!(p.text.len() <= MAX_PASTE_LEN,
2227 "Paste text {} exceeds limit {}", p.text.len(), MAX_PASTE_LEN);
2228 }
2229 }
2230 }
2231
2232 #[test]
2234 fn incremental_matches_bulk(input in arb_byte_vec(128)) {
2235 let mut bulk_parser = InputParser::new();
2236 let bulk_events = bulk_parser.parse(&input);
2237
2238 let mut incr_parser = InputParser::new();
2239 let mut incr_events = Vec::new();
2240 for byte in &input {
2241 incr_events.extend(incr_parser.parse(std::slice::from_ref(byte)));
2242 }
2243
2244 let bulk_dbg: Vec<String> = bulk_events.iter().map(|e| format!("{e:?}")).collect();
2245 let incr_dbg: Vec<String> = incr_events.iter().map(|e| format!("{e:?}")).collect();
2246 prop_assert_eq!(bulk_dbg, incr_dbg,
2247 "Bulk vs incremental mismatch for input {:?}", input);
2248 }
2249
2250 #[test]
2253 fn deterministic_output(input in arb_byte_vec(128)) {
2254 let mut parser1 = InputParser::new();
2255 let events1 = parser1.parse(&input);
2256
2257 let mut parser2 = InputParser::new();
2258 let events2 = parser2.parse(&input);
2259
2260 let dbg1: Vec<String> = events1.iter().map(|e| format!("{e:?}")).collect();
2261 let dbg2: Vec<String> = events2.iter().map(|e| format!("{e:?}")).collect();
2262 prop_assert_eq!(dbg1, dbg2);
2263 }
2264 }
2265
2266 #[test]
2270 fn no_quadratic_blowup() {
2271 let mut parser = InputParser::new();
2272
2273 let garbage: Vec<u8> = (0..65536).map(|i| (i % 256) as u8).collect();
2275 let _ = parser.parse(&garbage);
2276
2277 let events = parser.parse(b"a");
2279 let _ = events; }
2281
2282 #[test]
2284 fn oversized_csi_transitions_to_ignore() {
2285 let mut parser = InputParser::new();
2286
2287 let mut input = vec![0x1B, b'['];
2289 input.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
2290 input.push(b'm');
2291
2292 let _ = parser.parse(&input);
2293
2294 let events = parser.parse(b"x");
2296 assert_eq!(events.len(), 1);
2297 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('x')));
2298 }
2299
2300 #[test]
2302 fn oversized_osc_transitions_to_ignore() {
2303 let mut parser = InputParser::new();
2304
2305 let mut input = vec![0x1B, b']'];
2307 input.extend(std::iter::repeat_n(b'a', MAX_OSC_LEN + 100));
2308 input.push(0x07); let _ = parser.parse(&input);
2311
2312 let events = parser.parse(b"y");
2314 assert_eq!(events.len(), 1);
2315 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('y')));
2316 }
2317
2318 #[test]
2320 fn rapid_esc_toggle() {
2321 let mut parser = InputParser::new();
2322
2323 let input: Vec<u8> = vec![0x1B; 1000];
2325 let _ = parser.parse(&input);
2326
2327 let events = parser.parse(b"k");
2329 assert!(!events.is_empty());
2330 }
2331
2332 #[test]
2334 fn unterminated_paste_recovery() {
2335 let mut parser = InputParser::new();
2336
2337 let mut input = b"\x1b[200~".to_vec();
2339 input.extend(std::iter::repeat_n(b'x', 2048));
2340
2341 let _ = parser.parse(&input);
2342
2343 let events = parser.parse(b"\x1b[201~");
2345 assert!(
2346 !events.is_empty(),
2347 "Parser should emit paste event on terminator"
2348 );
2349 }
2350
2351 #[test]
2353 fn truncated_utf8_lead_bytes() {
2354 let mut parser = InputParser::new();
2355
2356 for lead in [0xC2, 0xE0, 0xF0] {
2358 let _ = parser.parse(&[lead]);
2359 let events = parser.parse(b"a");
2361 let _ = events;
2363 }
2364 }
2365
2366 #[test]
2368 fn null_bytes_interleaved() {
2369 let mut parser = InputParser::new();
2370
2371 let input = b"\x00A\x00\x1b[A\x00B\x00";
2372 let events = parser.parse(input);
2373 assert!(
2375 events.len() >= 2,
2376 "Expected at least 2 events, got {}",
2377 events.len()
2378 );
2379 }
2380
2381 fn osc52_sequence() -> impl Strategy<Value = Vec<u8>> {
2385 let selector = prop_oneof![Just(b'c'), Just(b'p'), Just(b's')];
2386 let payload = prop::collection::vec(
2388 prop_oneof![
2389 0x41u8..=0x5A, 0x61u8..=0x7A, 0x30u8..=0x39, Just(b'+'),
2393 Just(b'/'),
2394 Just(b'='),
2395 ],
2396 0..=128,
2397 );
2398 let terminator = prop_oneof![
2399 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
2402 (selector, payload, terminator).prop_map(|(sel, pay, term)| {
2403 let mut buf = vec![0x1B, b']', b'5', b'2', b';', sel, b';'];
2404 buf.extend_from_slice(&pay);
2405 buf.extend_from_slice(&term);
2406 buf
2407 })
2408 }
2409
2410 fn sgr_mouse_sequence() -> impl Strategy<Value = Vec<u8>> {
2412 let button_code = 0u16..128;
2413 let x = 1u16..300;
2414 let y = 1u16..100;
2415 let final_byte = prop_oneof![Just(b'M'), Just(b'm')];
2416 (button_code, x, y, final_byte)
2417 .prop_map(|(btn, x, y, fb)| format!("\x1b[<{btn};{x};{y}{}", fb as char).into_bytes())
2418 }
2419
2420 fn kitty_keyboard_sequence() -> impl Strategy<Value = Vec<u8>> {
2422 let keycode = prop_oneof![
2423 0x20u32..0x7F, 0x57344u32..0x57400, 0x100u32..0x200, ];
2427 let modifier = 1u32..16;
2428 let kind = prop_oneof![Just(1u32), Just(2u32), Just(3u32)]; (keycode, prop::option::of(modifier), prop::option::of(kind)).prop_map(
2430 |(kc, mods, kind)| match (mods, kind) {
2431 (Some(m), Some(k)) => format!("\x1b[{kc};{m}:{k}u").into_bytes(),
2432 (Some(m), None) => format!("\x1b[{kc};{m}u").into_bytes(),
2433 _ => format!("\x1b[{kc}u").into_bytes(),
2434 },
2435 )
2436 }
2437
2438 proptest! {
2439 #[test]
2443 fn osc52_never_panics(seq in osc52_sequence()) {
2444 let mut parser = InputParser::new();
2445 let events = parser.parse(&seq);
2446 for event in &events {
2448 if let Event::Clipboard(c) = event {
2449 prop_assert!(!c.content.is_empty() || c.content.is_empty(),
2450 "Clipboard event must have a content field");
2451 }
2452 }
2453 }
2454
2455 #[test]
2457 fn osc52_corrupt_base64_safe(payload in arb_byte_vec(128)) {
2458 let mut parser = InputParser::new();
2459 let mut input = b"\x1b]52;c;".to_vec();
2460 input.extend_from_slice(&payload);
2461 input.push(0x07); let _ = parser.parse(&input);
2463 }
2464
2465 #[test]
2469 fn sgr_mouse_never_panics(seq in sgr_mouse_sequence()) {
2470 let mut parser = InputParser::new();
2471 let events = parser.parse(&seq);
2472 for event in &events {
2473 let _ = format!("{event:?}");
2475 }
2476 }
2477
2478 #[test]
2480 fn sgr_mouse_extreme_coords(
2481 btn in 0u16..128,
2482 x in 0u16..=65535,
2483 y in 0u16..=65535,
2484 ) {
2485 let mut parser = InputParser::new();
2486 let input = format!("\x1b[<{btn};{x};{y}M").into_bytes();
2487 let events = parser.parse(&input);
2488 for event in &events {
2489 if let Event::Mouse(m) = event {
2490 prop_assert!(m.x <= x, "Mouse x {} > input x {}", m.x, x);
2491 prop_assert!(m.y <= y, "Mouse y {} > input y {}", m.y, y);
2492 }
2493 }
2494 }
2495
2496 #[test]
2500 fn kitty_keyboard_never_panics(seq in kitty_keyboard_sequence()) {
2501 let mut parser = InputParser::new();
2502 let _ = parser.parse(&seq);
2503 }
2504
2505 #[test]
2509 fn truncated_csi_then_valid(
2510 params in prop::collection::vec(0x30u8..=0x3F, 1..=10),
2511 valid_char in 0x20u8..0x7F,
2512 ) {
2513 let mut parser = InputParser::new();
2514
2515 let mut partial = vec![0x1B, b'['];
2517 partial.extend_from_slice(¶ms);
2518 let _ = parser.parse(&partial);
2519
2520 let events = parser.parse(&[0x1B, b'[', b'A']); let _ = events;
2525
2526 let events = parser.parse(&[valid_char]);
2528 let _ = events;
2529 }
2530
2531 #[test]
2533 fn truncated_osc_then_valid(
2534 content in prop::collection::vec(0x20u8..=0x7E, 1..=32),
2535 ) {
2536 let mut parser = InputParser::new();
2537
2538 let mut partial = vec![0x1B, b']'];
2540 partial.extend_from_slice(&content);
2541 let _ = parser.parse(&partial);
2542
2543 let events = parser.parse(b"\x1bz");
2545 let _ = events;
2546 }
2547
2548 #[test]
2552 fn csi_near_limit_produces_event(
2553 fill_byte in 0x30u8..=0x39, ) {
2555 let mut parser = InputParser::new();
2556
2557 let mut input = vec![0x1B, b'['];
2558 input.extend(std::iter::repeat_n(fill_byte, MAX_CSI_LEN - 1));
2560 input.push(b'm'); let events = parser.parse(&input);
2563 let _ = events;
2566
2567 let events = parser.parse(b"a");
2569 prop_assert!(!events.is_empty(), "Parser stuck after near-limit CSI");
2570 }
2571
2572 #[test]
2574 fn osc_near_limit_processes(
2575 fill_byte in 0x20u8..=0x7E,
2576 ) {
2577 let mut parser = InputParser::new();
2578
2579 let mut input = vec![0x1B, b']'];
2580 input.extend(std::iter::repeat_n(fill_byte, MAX_OSC_LEN - 1));
2581 input.push(0x07); let _ = parser.parse(&input);
2584
2585 let events = parser.parse(b"b");
2587 prop_assert!(!events.is_empty(), "Parser stuck after near-limit OSC");
2588 }
2589
2590 #[test]
2594 fn consecutive_pastes_emit_events(count in 2usize..=5) {
2595 let mut parser = InputParser::new();
2596 let mut input = Vec::new();
2597
2598 for i in 0..count {
2599 input.extend_from_slice(b"\x1b[200~");
2600 input.extend_from_slice(format!("paste_{i}").as_bytes());
2601 input.extend_from_slice(b"\x1b[201~");
2602 }
2603
2604 let events = parser.parse(&input);
2605 let paste_events: Vec<_> = events.iter()
2606 .filter(|e| matches!(e, Event::Paste(_)))
2607 .collect();
2608
2609 prop_assert_eq!(paste_events.len(), count,
2610 "Expected {} paste events, got {}", count, paste_events.len());
2611 }
2612
2613 #[test]
2615 fn paste_with_invalid_utf8(content in arb_byte_vec(256)) {
2616 let mut parser = InputParser::new();
2617 let mut input = b"\x1b[200~".to_vec();
2618 input.extend_from_slice(&content);
2619 input.extend_from_slice(b"\x1b[201~");
2620
2621 let events = parser.parse(&input);
2622 for event in &events {
2623 if let Event::Paste(p) = event {
2624 prop_assert!(p.text.is_char_boundary(0), "Paste text is not valid UTF-8");
2626 }
2627 }
2628 }
2629
2630 #[test]
2634 fn recovery_via_esc_reset(garbage in arb_byte_vec(256)) {
2635 let mut parser = InputParser::new();
2636 let _ = parser.parse(&garbage);
2637
2638 let _ = parser.parse(b"\x07\x1b\\\x1b");
2641 let _ = parser.parse(b"\x1b");
2642
2643 let _ = parser.parse(b"z");
2645
2646 let events = parser.parse(b"q");
2648 prop_assert!(!events.is_empty(),
2651 "Parser did not recover after garbage + reset");
2652 }
2653 }
2654}