1#![forbid(unsafe_code)]
2
3use crate::event::{
27 ClipboardEvent, ClipboardSource, Event, KeyCode, KeyEvent, KeyEventKind, Modifiers,
28 MouseButton, MouseEvent, MouseEventKind, PasteEvent,
29};
30
31#[cfg(feature = "tracing")]
33use crate::logging::{debug, debug_span, trace};
34#[cfg(not(feature = "tracing"))]
35use crate::{debug, debug_span, trace};
36
37const MAX_CSI_LEN: usize = 256;
39
40const MAX_OSC_LEN: usize = 4096;
42
43const MAX_PASTE_LEN: usize = 1024 * 1024; const MAX_EVENT_RESERVE_HINT: usize = 8 * 1024 + 1;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
53enum ParserState {
54 #[default]
56 Ground,
57 Escape,
59 Csi,
61 CsiParam,
63 CsiIgnore,
65 Ss3,
67 Osc,
69 OscContent,
71 OscEscape,
73 OscIgnore,
75 Utf8 {
77 collected: u8,
79 expected: u8,
81 },
82}
83
84#[derive(Debug)]
94pub struct InputParser {
95 state: ParserState,
97 buffer: Vec<u8>,
99 paste_buffer: Vec<u8>,
101 utf8_buffer: [u8; 4],
103 in_paste: bool,
105 pending_event: Option<Event>,
107}
108
109impl Default for InputParser {
110 fn default() -> Self {
111 Self::new()
112 }
113}
114
115impl InputParser {
116 #[inline]
117 fn event_reserve_hint(input_len: usize) -> usize {
118 input_len.saturating_add(1).min(MAX_EVENT_RESERVE_HINT)
119 }
120
121 #[must_use]
123 pub fn new() -> Self {
124 Self {
125 state: ParserState::Ground,
126 buffer: Vec::with_capacity(64),
127 paste_buffer: Vec::new(),
128 utf8_buffer: [0; 4],
129 in_paste: false,
130 pending_event: None,
131 }
132 }
133
134 pub fn parse(&mut self, input: &[u8]) -> Vec<Event> {
136 let mut events = Vec::with_capacity(Self::event_reserve_hint(input.len()));
137 self.parse_with(input, |event| events.push(event));
138 events
139 }
140
141 pub fn parse_with<F>(&mut self, input: &[u8], mut emit: F)
143 where
144 F: FnMut(Event),
145 {
146 let span = debug_span!("event.normalize", raw_byte_count = input.len());
147 let _guard = span.enter();
148 trace!("raw input bytes: {} bytes", input.len());
149
150 for &byte in input {
151 if let Some(event) = self.process_byte(byte) {
152 debug!(event_type = event.event_type_label(), "normalized event");
153 emit(event);
154 }
155 if let Some(pending) = self.pending_event.take() {
156 debug!(event_type = pending.event_type_label(), "normalized event");
157 emit(pending);
158 }
159 }
160 }
161
162 pub fn parse_into(&mut self, input: &[u8], events: &mut Vec<Event>) {
167 let needed = Self::event_reserve_hint(input.len());
168 let available = events.capacity().saturating_sub(events.len());
169 if available < needed {
170 events.reserve(needed - available);
171 }
172 self.parse_with(input, |event| events.push(event));
173 }
174
175 fn process_byte(&mut self, byte: u8) -> Option<Event> {
177 if self.in_paste {
179 return self.process_paste_byte(byte);
180 }
181
182 match self.state {
183 ParserState::Ground => self.process_ground(byte),
184 ParserState::Escape => self.process_escape(byte),
185 ParserState::Csi => self.process_csi(byte),
186 ParserState::CsiParam => self.process_csi_param(byte),
187 ParserState::CsiIgnore => self.process_csi_ignore(byte),
188 ParserState::Ss3 => self.process_ss3(byte),
189 ParserState::Osc => self.process_osc(byte),
190 ParserState::OscContent => self.process_osc_content(byte),
191 ParserState::OscEscape => self.process_osc_escape(byte),
192 ParserState::OscIgnore => self.process_osc_ignore(byte),
193 ParserState::Utf8 {
194 collected,
195 expected,
196 } => self.process_utf8(byte, collected, expected),
197 }
198 }
199
200 fn process_ground(&mut self, byte: u8) -> Option<Event> {
202 match byte {
203 0x1B => {
205 self.state = ParserState::Escape;
206 None
207 }
208 0x00 => Some(Event::Key(KeyEvent::new(KeyCode::Null))),
210 0x08 => Some(Event::Key(KeyEvent::new(KeyCode::Backspace))),
212 0x09 => Some(Event::Key(KeyEvent::new(KeyCode::Tab))),
214 0x0D => Some(Event::Key(KeyEvent::new(KeyCode::Enter))),
216 0x01..=0x07 | 0x0A..=0x0C | 0x0E..=0x1A => {
218 let c = (byte + b'a' - 1) as char;
219 Some(Event::Key(
220 KeyEvent::new(KeyCode::Char(c)).with_modifiers(Modifiers::CTRL),
221 ))
222 }
223 0x7F => Some(Event::Key(KeyEvent::new(KeyCode::Backspace))),
225 0x20..=0x7E => Some(Event::Key(KeyEvent::new(KeyCode::Char(byte as char)))),
227 0xC2..=0xDF => {
229 self.utf8_buffer[0] = byte;
230 self.state = ParserState::Utf8 {
231 collected: 1,
232 expected: 2,
233 };
234 None
235 }
236 0xE0..=0xEF => {
237 self.utf8_buffer[0] = byte;
238 self.state = ParserState::Utf8 {
239 collected: 1,
240 expected: 3,
241 };
242 None
243 }
244 0xF0..=0xF4 => {
245 self.utf8_buffer[0] = byte;
246 self.state = ParserState::Utf8 {
247 collected: 1,
248 expected: 4,
249 };
250 None
251 }
252 0xC0..=0xC1 | 0xF5..=0xFF => Some(Event::Key(KeyEvent::new(KeyCode::Char(
254 std::char::REPLACEMENT_CHARACTER,
255 )))),
256 _ => None,
258 }
259 }
260
261 fn process_escape(&mut self, byte: u8) -> Option<Event> {
263 match byte {
264 b'[' => {
266 self.state = ParserState::Csi;
267 self.buffer.clear();
268 None
269 }
270 b'O' => {
272 self.state = ParserState::Ss3;
273 None
274 }
275 b']' => {
277 self.state = ParserState::Osc;
278 self.buffer.clear();
279 None
280 }
281 0x1B => {
284 self.state = ParserState::Ground;
285 Some(Event::Key(
286 KeyEvent::new(KeyCode::Escape).with_modifiers(Modifiers::ALT),
287 ))
288 }
289 0x20..=0x7E => {
291 self.state = ParserState::Ground;
292 Some(Event::Key(
293 KeyEvent::new(KeyCode::Char(byte as char)).with_modifiers(Modifiers::ALT),
294 ))
295 }
296 0x7F => {
298 self.state = ParserState::Ground;
299 Some(Event::Key(
300 KeyEvent::new(KeyCode::Backspace).with_modifiers(Modifiers::ALT),
301 ))
302 }
303 _ => {
305 self.state = ParserState::Ground;
306 None
307 }
308 }
309 }
310
311 fn process_csi(&mut self, byte: u8) -> Option<Event> {
313 if byte == 0x1B {
315 self.state = ParserState::Escape;
316 self.buffer.clear();
317 return None;
318 }
319
320 self.buffer.push(byte);
321
322 match byte {
323 0x20..=0x3F => {
325 self.state = ParserState::CsiParam;
326 None
327 }
328 0x40..=0x7E => {
330 self.state = ParserState::Ground;
331 self.parse_csi_sequence()
332 }
333 _ => {
335 self.state = ParserState::Ground;
336 self.buffer.clear();
337 None
338 }
339 }
340 }
341
342 fn process_csi_param(&mut self, byte: u8) -> Option<Event> {
344 if byte == 0x1B {
346 self.state = ParserState::Escape;
347 self.buffer.clear();
348 return None;
349 }
350
351 if self.buffer.len() >= MAX_CSI_LEN {
353 self.state = ParserState::CsiIgnore;
354 self.buffer.clear();
355 return None;
356 }
357
358 self.buffer.push(byte);
359
360 match byte {
361 0x20..=0x3F => None,
363 0x40..=0x7E => {
365 self.state = ParserState::Ground;
366 self.parse_csi_sequence()
367 }
368 _ => {
370 self.state = ParserState::Ground;
371 self.buffer.clear();
372 None
373 }
374 }
375 }
376
377 fn process_csi_ignore(&mut self, byte: u8) -> Option<Event> {
379 if byte == 0x1B {
381 self.state = ParserState::Escape;
382 return None;
383 }
384
385 if (0x40..=0x7E).contains(&byte) {
388 self.state = ParserState::Ground;
389 } else if !(0x20..=0x7E).contains(&byte) {
390 self.state = ParserState::Ground;
392 }
393 None
394 }
395
396 fn parse_csi_sequence(&mut self) -> Option<Event> {
398 let seq = std::mem::take(&mut self.buffer);
399 if seq.is_empty() {
400 return None;
401 }
402
403 let final_byte = *seq.last()?;
404 let params = &seq[..seq.len() - 1];
405
406 match (params, final_byte) {
408 ([], b'I') => return Some(Event::Focus(true)),
410 ([], b'O') => return Some(Event::Focus(false)),
411
412 (b"200", b'~') => {
414 self.in_paste = true;
415 self.paste_buffer.clear();
416 self.buffer.clear(); return None;
418 }
419 (b"201", b'~') => {
420 self.in_paste = false;
421 let content = String::from_utf8_lossy(&self.paste_buffer).into_owned();
422 self.paste_buffer.clear();
423 return Some(Event::Paste(PasteEvent::bracketed(content)));
424 }
425
426 _ if params.starts_with(b"<") && (final_byte == b'M' || final_byte == b'm') => {
428 return self.parse_sgr_mouse(params, final_byte);
429 }
430
431 _ => {}
432 }
433
434 match final_byte {
436 b'A' => Some(Event::Key(self.key_with_modifiers(KeyCode::Up, params))),
437 b'B' => Some(Event::Key(self.key_with_modifiers(KeyCode::Down, params))),
438 b'C' => Some(Event::Key(self.key_with_modifiers(KeyCode::Right, params))),
439 b'D' => Some(Event::Key(self.key_with_modifiers(KeyCode::Left, params))),
440 b'H' => Some(Event::Key(self.key_with_modifiers(KeyCode::Home, params))),
441 b'F' => Some(Event::Key(self.key_with_modifiers(KeyCode::End, params))),
442 b'Z' => Some(Event::Key(
443 self.key_with_modifiers(KeyCode::BackTab, params),
444 )),
445 b'~' => self.parse_csi_tilde(params),
446 b'u' => self.parse_kitty_keyboard(params),
447 _ => None,
448 }
449 }
450
451 fn parse_csi_tilde(&self, params: &[u8]) -> Option<Event> {
453 let num = self.parse_first_param(params)?;
454 let mods = self.parse_modifier_param(params);
455
456 let code = match num {
457 1 => KeyCode::Home,
458 2 => KeyCode::Insert,
459 3 => KeyCode::Delete,
460 4 => KeyCode::End,
461 5 => KeyCode::PageUp,
462 6 => KeyCode::PageDown,
463 15 => KeyCode::F(5),
464 17 => KeyCode::F(6),
465 18 => KeyCode::F(7),
466 19 => KeyCode::F(8),
467 20 => KeyCode::F(9),
468 21 => KeyCode::F(10),
469 23 => KeyCode::F(11),
470 24 => KeyCode::F(12),
471 _ => return None,
472 };
473
474 Some(Event::Key(KeyEvent::new(code).with_modifiers(mods)))
475 }
476
477 fn parse_first_param(&self, params: &[u8]) -> Option<u32> {
479 let s = std::str::from_utf8(params).ok()?;
480 let first = s.split(';').next()?;
481 first.parse().ok()
482 }
483
484 fn parse_modifier_param(&self, params: &[u8]) -> Modifiers {
486 let s = match std::str::from_utf8(params) {
487 Ok(s) => s,
488 Err(_) => return Modifiers::NONE,
489 };
490
491 let modifier_value: u32 = s
492 .split(';')
493 .nth(1)
494 .and_then(|s| s.parse().ok())
495 .unwrap_or(1);
496
497 Self::modifiers_from_xterm(modifier_value)
498 }
499
500 fn parse_kitty_keyboard(&self, params: &[u8]) -> Option<Event> {
504 let s = std::str::from_utf8(params).ok()?;
505 if s.is_empty() {
506 return None;
507 }
508
509 let mut parts = s.split(';');
510 let key_part = parts.next().unwrap_or("");
511 let key_code_str = key_part.split(':').next().unwrap_or("");
512 let key_code: u32 = key_code_str.parse().ok()?;
513
514 let mod_part = parts.next().unwrap_or("");
515 let (modifiers, kind) = Self::kitty_modifiers_and_kind(mod_part);
516
517 let code = Self::kitty_keycode_to_keycode(key_code)?;
518 Some(Event::Key(
519 KeyEvent::new(code)
520 .with_modifiers(modifiers)
521 .with_kind(kind),
522 ))
523 }
524
525 fn kitty_modifiers_and_kind(mod_part: &str) -> (Modifiers, KeyEventKind) {
526 if mod_part.is_empty() {
527 return (Modifiers::NONE, KeyEventKind::Press);
528 }
529
530 let mut parts = mod_part.split(':');
531 let mod_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
532 let kind_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
533
534 let modifiers = Self::modifiers_from_xterm(mod_value);
535 let kind = match kind_value {
536 2 => KeyEventKind::Repeat,
537 3 => KeyEventKind::Release,
538 _ => KeyEventKind::Press,
539 };
540
541 (modifiers, kind)
542 }
543
544 fn kitty_keycode_to_keycode(key_code: u32) -> Option<KeyCode> {
545 match key_code {
546 9 => Some(KeyCode::Tab),
548 13 => Some(KeyCode::Enter),
549 27 => Some(KeyCode::Escape),
550 8 | 127 => Some(KeyCode::Backspace),
551 57_344 => Some(KeyCode::Escape),
553 57_345 => Some(KeyCode::Enter),
554 57_346 => Some(KeyCode::Tab),
555 57_347 => Some(KeyCode::Backspace),
556 57_348 => Some(KeyCode::Insert),
557 57_349 => Some(KeyCode::Delete),
558 57_350 => Some(KeyCode::Left),
559 57_351 => Some(KeyCode::Right),
560 57_352 => Some(KeyCode::Up),
561 57_353 => Some(KeyCode::Down),
562 57_354 => Some(KeyCode::PageUp),
563 57_355 => Some(KeyCode::PageDown),
564 57_356 => Some(KeyCode::Home),
565 57_357 => Some(KeyCode::End),
566 57_364..=57_387 => {
568 let f_num = key_code - 57_364 + 1;
571 debug_assert!(f_num <= 24, "F-key number {f_num} exceeds F24");
572 Some(KeyCode::F(f_num as u8))
573 }
574 57_358..=57_363 | 57_388..=63_743 => None,
576 _ => char::from_u32(key_code).map(KeyCode::Char),
578 }
579 }
580
581 fn modifiers_from_xterm(value: u32) -> Modifiers {
582 let bits = value.saturating_sub(1);
585 let mut mods = Modifiers::NONE;
586 if bits & 1 != 0 {
587 mods |= Modifiers::SHIFT;
588 }
589 if bits & 2 != 0 {
590 mods |= Modifiers::ALT;
591 }
592 if bits & 4 != 0 {
593 mods |= Modifiers::CTRL;
594 }
595 if bits & 8 != 0 {
596 mods |= Modifiers::SUPER;
597 }
598 mods
599 }
600
601 fn key_with_modifiers(&self, code: KeyCode, params: &[u8]) -> KeyEvent {
603 KeyEvent::new(code).with_modifiers(self.parse_modifier_param(params))
604 }
605
606 fn parse_sgr_mouse(&self, params: &[u8], final_byte: u8) -> Option<Event> {
608 let params = ¶ms[1..];
611 let s = std::str::from_utf8(params).ok()?;
612 let mut parts = s.split(';');
613
614 let button_code: u16 = parts.next()?.parse().ok()?;
615 let x: u16 = parts.next()?.parse().ok()?;
616 let y: u16 = parts.next()?.parse().ok()?;
617
618 let (button, mods) = self.decode_mouse_button(button_code);
620
621 let kind = if final_byte == b'M' {
622 if button_code & 64 != 0 {
623 match button_code & 3 {
626 0 => MouseEventKind::ScrollUp,
627 1 => MouseEventKind::ScrollDown,
628 2 => MouseEventKind::ScrollLeft,
629 _ => MouseEventKind::ScrollRight,
630 }
631 } else if button_code & 32 != 0 {
632 if button_code & 3 == 3 {
635 MouseEventKind::Moved
636 } else {
637 MouseEventKind::Drag(button)
638 }
639 } else {
640 MouseEventKind::Down(button)
641 }
642 } else {
643 MouseEventKind::Up(button)
644 };
645
646 Some(Event::Mouse(MouseEvent {
647 kind,
648 x: x.saturating_sub(1), y: y.saturating_sub(1),
650 modifiers: mods,
651 }))
652 }
653
654 fn decode_mouse_button(&self, code: u16) -> (MouseButton, Modifiers) {
656 let button = match code & 0b11 {
657 0 => MouseButton::Left,
658 1 => MouseButton::Middle,
659 2 => MouseButton::Right,
660 _ => MouseButton::Left,
661 };
662
663 let mut mods = Modifiers::NONE;
664 if code & 4 != 0 {
665 mods |= Modifiers::SHIFT;
666 }
667 if code & 8 != 0 {
668 mods |= Modifiers::ALT;
669 }
670 if code & 16 != 0 {
671 mods |= Modifiers::CTRL;
672 }
673
674 (button, mods)
675 }
676
677 fn process_ss3(&mut self, byte: u8) -> Option<Event> {
679 if byte == 0x1B {
681 self.state = ParserState::Escape;
682 return None;
683 }
684
685 self.state = ParserState::Ground;
686
687 let code = match byte {
688 b'P' => KeyCode::F(1),
689 b'Q' => KeyCode::F(2),
690 b'R' => KeyCode::F(3),
691 b'S' => KeyCode::F(4),
692 b'A' => KeyCode::Up,
693 b'B' => KeyCode::Down,
694 b'C' => KeyCode::Right,
695 b'D' => KeyCode::Left,
696 b'H' => KeyCode::Home,
697 b'F' => KeyCode::End,
698 _ => return None,
699 };
700
701 Some(Event::Key(KeyEvent::new(code)))
702 }
703
704 fn process_osc(&mut self, byte: u8) -> Option<Event> {
706 if byte == 0x1B {
708 self.state = ParserState::OscEscape;
709 return None;
710 }
711
712 self.buffer.push(byte);
713
714 match byte {
715 0x07 => {
717 self.state = ParserState::Ground;
718 self.parse_osc_sequence()
719 }
720 _ => {
722 self.state = ParserState::OscContent;
723 None
724 }
725 }
726 }
727
728 fn process_osc_content(&mut self, byte: u8) -> Option<Event> {
730 if byte == 0x1B {
732 self.state = ParserState::OscEscape;
733 return None;
734 }
735
736 if byte < 0x20 && byte != 0x07 {
738 self.state = ParserState::Ground;
739 self.buffer.clear();
740 return None;
741 }
742
743 if self.buffer.len() >= MAX_OSC_LEN {
745 self.state = ParserState::OscIgnore;
746 self.buffer.clear();
747 return None;
748 }
749
750 match byte {
751 0x07 => {
753 self.state = ParserState::Ground;
754 self.parse_osc_sequence()
755 }
756 _ => {
758 self.buffer.push(byte);
759 None
760 }
761 }
762 }
763
764 fn process_osc_escape(&mut self, byte: u8) -> Option<Event> {
766 if byte == b'\\' {
767 self.state = ParserState::Ground;
769 self.parse_osc_sequence()
770 } else if byte == 0x1B {
771 self.state = ParserState::Escape;
773 self.buffer.clear();
774 None
775 } else {
776 self.buffer.clear();
782 self.state = ParserState::Escape;
783 self.process_escape(byte)
784 }
785 }
786
787 fn process_osc_ignore(&mut self, byte: u8) -> Option<Event> {
789 match byte {
790 0x07 => {
792 self.state = ParserState::Ground;
793 None
794 }
795 0x1B => {
797 self.state = ParserState::OscEscape;
798 None
799 }
800 _ if byte < 0x20 => {
802 self.state = ParserState::Ground;
803 None
804 }
805 _ => None,
807 }
808 }
809
810 fn parse_osc_sequence(&mut self) -> Option<Event> {
812 let seq = std::mem::take(&mut self.buffer);
813
814 if seq.starts_with(b"52;") {
816 return self.parse_osc52_clipboard(&seq);
817 }
818
819 None
821 }
822
823 fn parse_osc52_clipboard(&self, seq: &[u8]) -> Option<Event> {
825 let content = &seq[3..]; if content.is_empty() {
828 return None;
829 }
830
831 let source = ClipboardSource::Osc52;
834
835 let base64_start = content.iter().position(|&b| b == b';').map(|i| i + 1)?;
837 let base64_data = &content[base64_start..];
838
839 let decoded = self.decode_base64(base64_data)?;
841
842 Some(Event::Clipboard(ClipboardEvent::new(
843 String::from_utf8_lossy(&decoded).into_owned(),
844 source,
845 )))
846 }
847
848 fn decode_base64(&self, input: &[u8]) -> Option<Vec<u8>> {
850 const DECODE_TABLE: [i8; 256] = {
851 let mut table = [-1i8; 256];
852 let mut i = 0u8;
853 while i < 26 {
854 table[(b'A' + i) as usize] = i as i8;
855 table[(b'a' + i) as usize] = (i + 26) as i8;
856 i += 1;
857 }
858 let mut i = 0u8;
859 while i < 10 {
860 table[(b'0' + i) as usize] = (i + 52) as i8;
861 i += 1;
862 }
863 table[b'+' as usize] = 62;
864 table[b'/' as usize] = 63;
865 table
866 };
867
868 let mut output = Vec::with_capacity(input.len() * 3 / 4);
869 let mut buffer = 0u32;
870 let mut bits = 0u8;
871
872 for &byte in input {
873 if byte == b'=' {
874 break;
875 }
876 let value = DECODE_TABLE[byte as usize];
877 if value < 0 {
878 continue; }
880 buffer = (buffer << 6) | (value as u32);
881 bits += 6;
882 if bits >= 8 {
883 bits -= 8;
884 output.push((buffer >> bits) as u8);
885 buffer &= (1 << bits) - 1;
886 }
887 }
888
889 Some(output)
890 }
891
892 fn process_utf8(&mut self, byte: u8, collected: u8, expected: u8) -> Option<Event> {
894 if (byte & 0xC0) != 0x80 {
896 self.state = ParserState::Ground;
899
900 self.pending_event = self.process_ground(byte);
902
903 return Some(Event::Key(KeyEvent::new(KeyCode::Char(
904 std::char::REPLACEMENT_CHARACTER,
905 ))));
906 }
907
908 self.utf8_buffer[collected as usize] = byte;
909 let new_collected = collected + 1;
910
911 if new_collected == expected {
912 self.state = ParserState::Ground;
914 let s = std::str::from_utf8(&self.utf8_buffer[..expected as usize]).ok()?;
915 let c = s.chars().next()?;
916 Some(Event::Key(KeyEvent::new(KeyCode::Char(c))))
917 } else {
918 self.state = ParserState::Utf8 {
920 collected: new_collected,
921 expected,
922 };
923 None
924 }
925 }
926
927 fn process_paste_byte(&mut self, byte: u8) -> Option<Event> {
929 const END_SEQ: &[u8] = b"\x1b[201~";
930
931 if self.paste_buffer.len() < MAX_PASTE_LEN {
937 self.paste_buffer.push(byte);
938
939 if self.paste_buffer.ends_with(END_SEQ) {
941 self.in_paste = false;
942 let content_len = self.paste_buffer.len() - END_SEQ.len();
944 let content =
945 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
946 self.paste_buffer.clear();
947 return Some(Event::Paste(PasteEvent::bracketed(content)));
948 }
949 } else {
950 self.buffer.push(byte);
955 if self.buffer.len() > END_SEQ.len() {
956 self.buffer.remove(0);
957 }
958
959 let mut last_bytes = [0u8; 6];
966 let tail_len = self.buffer.len();
967 let paste_len = self.paste_buffer.len();
968
969 if tail_len + paste_len >= 6 {
970 for i in 0..tail_len {
972 last_bytes[6 - tail_len + i] = self.buffer[i];
973 }
974 let remaining = 6 - tail_len;
976 if remaining > 0 {
977 let start = paste_len - remaining;
978 last_bytes[..remaining]
979 .copy_from_slice(&self.paste_buffer[start..(remaining + start)]);
980 }
981
982 if last_bytes == END_SEQ {
983 self.in_paste = false;
984
985 let content_len = paste_len - remaining;
990 let content =
991 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
992
993 self.paste_buffer.clear();
994 self.buffer.clear();
995
996 return Some(Event::Paste(PasteEvent::bracketed(content)));
997 }
998 }
999 }
1000
1001 None
1002 }
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007 use super::*;
1008
1009 #[test]
1010 fn csi_ignore_handles_final_bytes() {
1011 let mut parser = InputParser::new();
1012
1013 let mut seq = vec![0x1B, b'['];
1016 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100)); seq.push(b'@'); let events = parser.parse(&seq);
1020 assert_eq!(events.len(), 0);
1021
1022 let events = parser.parse(b"a");
1025 assert_eq!(events.len(), 1, "Subsequent char 'a' was swallowed");
1026 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1027 }
1028
1029 #[test]
1030 fn ascii_characters_parsed() {
1031 let mut parser = InputParser::new();
1032
1033 let events = parser.parse(b"abc");
1034 assert_eq!(events.len(), 3);
1035 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1036 assert!(matches!(events[1], Event::Key(k) if k.code == KeyCode::Char('b')));
1037 assert!(matches!(events[2], Event::Key(k) if k.code == KeyCode::Char('c')));
1038 }
1039
1040 #[test]
1041 fn control_characters() {
1042 let mut parser = InputParser::new();
1043
1044 let events = parser.parse(&[0x01]);
1046 assert_eq!(events.len(), 1);
1047 assert!(matches!(
1048 events[0],
1049 Event::Key(k) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::CTRL)
1050 ));
1051
1052 let events = parser.parse(&[0x7F]);
1054 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace));
1055 }
1056
1057 #[test]
1058 fn arrow_keys() {
1059 let mut parser = InputParser::new();
1060
1061 assert!(matches!(
1062 parser.parse(b"\x1b[A").first(),
1063 Some(Event::Key(k)) if k.code == KeyCode::Up
1064 ));
1065 assert!(matches!(
1066 parser.parse(b"\x1b[B").first(),
1067 Some(Event::Key(k)) if k.code == KeyCode::Down
1068 ));
1069 assert!(matches!(
1070 parser.parse(b"\x1b[C").first(),
1071 Some(Event::Key(k)) if k.code == KeyCode::Right
1072 ));
1073 assert!(matches!(
1074 parser.parse(b"\x1b[D").first(),
1075 Some(Event::Key(k)) if k.code == KeyCode::Left
1076 ));
1077 }
1078
1079 #[test]
1080 fn function_keys_ss3() {
1081 let mut parser = InputParser::new();
1082
1083 assert!(matches!(
1084 parser.parse(b"\x1bOP").first(),
1085 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1086 ));
1087 assert!(matches!(
1088 parser.parse(b"\x1bOQ").first(),
1089 Some(Event::Key(k)) if k.code == KeyCode::F(2)
1090 ));
1091 assert!(matches!(
1092 parser.parse(b"\x1bOR").first(),
1093 Some(Event::Key(k)) if k.code == KeyCode::F(3)
1094 ));
1095 assert!(matches!(
1096 parser.parse(b"\x1bOS").first(),
1097 Some(Event::Key(k)) if k.code == KeyCode::F(4)
1098 ));
1099 }
1100
1101 #[test]
1102 fn function_keys_csi() {
1103 let mut parser = InputParser::new();
1104
1105 assert!(matches!(
1106 parser.parse(b"\x1b[15~").first(),
1107 Some(Event::Key(k)) if k.code == KeyCode::F(5)
1108 ));
1109 assert!(matches!(
1110 parser.parse(b"\x1b[17~").first(),
1111 Some(Event::Key(k)) if k.code == KeyCode::F(6)
1112 ));
1113 }
1114
1115 #[test]
1116 fn modifiers_in_csi() {
1117 let mut parser = InputParser::new();
1118
1119 let events = parser.parse(b"\x1b[1;2A");
1121 assert!(matches!(
1122 events.first(),
1123 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::SHIFT)
1124 ));
1125
1126 let events = parser.parse(b"\x1b[1;5A");
1128 assert!(matches!(
1129 events.first(),
1130 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::CTRL)
1131 ));
1132 }
1133
1134 #[test]
1135 fn modifiers_in_csi_alt_ctrl() {
1136 let mut parser = InputParser::new();
1137
1138 let events = parser.parse(b"\x1b[1;7A");
1140 assert!(matches!(
1141 events.first(),
1142 Some(Event::Key(k))
1143 if k.code == KeyCode::Up
1144 && k.modifiers.contains(Modifiers::ALT)
1145 && k.modifiers.contains(Modifiers::CTRL)
1146 ));
1147 }
1148
1149 #[test]
1150 fn kitty_keyboard_basic_char() {
1151 let mut parser = InputParser::new();
1152
1153 let events = parser.parse(b"\x1b[97u");
1154 assert!(matches!(
1155 events.first(),
1156 Some(Event::Key(k))
1157 if k.code == KeyCode::Char('a')
1158 && k.modifiers == Modifiers::NONE
1159 && k.kind == KeyEventKind::Press
1160 ));
1161 }
1162
1163 #[test]
1164 fn kitty_keyboard_with_modifiers_and_kind() {
1165 let mut parser = InputParser::new();
1166
1167 let events = parser.parse(b"\x1b[97;5:2u");
1169 assert!(matches!(
1170 events.first(),
1171 Some(Event::Key(k))
1172 if k.code == KeyCode::Char('a')
1173 && k.modifiers.contains(Modifiers::CTRL)
1174 && k.kind == KeyEventKind::Repeat
1175 ));
1176 }
1177
1178 #[test]
1179 fn kitty_keyboard_function_key() {
1180 let mut parser = InputParser::new();
1181
1182 let events = parser.parse(b"\x1b[57364;1u");
1183 assert!(matches!(
1184 events.first(),
1185 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1186 ));
1187 }
1188
1189 #[test]
1190 fn alt_key_escapes() {
1191 let mut parser = InputParser::new();
1192
1193 let events = parser.parse(b"\x1ba");
1194 assert!(matches!(
1195 events.first(),
1196 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::ALT)
1197 ));
1198 }
1199
1200 #[test]
1201 fn alt_backspace() {
1202 let mut parser = InputParser::new();
1203
1204 let events = parser.parse(b"\x1b\x7f");
1205 assert!(matches!(
1206 events.first(),
1207 Some(Event::Key(k))
1208 if k.code == KeyCode::Backspace && k.modifiers.contains(Modifiers::ALT)
1209 ));
1210 }
1211
1212 #[test]
1213 fn escape_escape_resets_state() {
1214 let mut parser = InputParser::new();
1215
1216 let events = parser.parse(b"\x1b\x1b");
1217 assert!(matches!(
1218 events.first(),
1219 Some(Event::Key(k)) if k.code == KeyCode::Escape && k.modifiers.contains(Modifiers::ALT)
1220 ));
1221
1222 let events = parser.parse(b"a");
1223 assert!(matches!(
1224 events.first(),
1225 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers == Modifiers::NONE
1226 ));
1227 }
1228
1229 #[test]
1230 fn focus_events() {
1231 let mut parser = InputParser::new();
1232
1233 assert!(matches!(
1234 parser.parse(b"\x1b[I").first(),
1235 Some(Event::Focus(true))
1236 ));
1237 assert!(matches!(
1238 parser.parse(b"\x1b[O").first(),
1239 Some(Event::Focus(false))
1240 ));
1241 }
1242
1243 #[test]
1244 fn bracketed_paste() {
1245 let mut parser = InputParser::new();
1246
1247 let events = parser.parse(b"\x1b[200~hello world\x1b[201~");
1249 assert_eq!(events.len(), 1);
1250 assert!(matches!(
1251 &events[0],
1252 Event::Paste(p) if p.text == "hello world"
1253 ));
1254 }
1255
1256 #[test]
1257 fn mouse_sgr_protocol() {
1258 let mut parser = InputParser::new();
1259
1260 let events = parser.parse(b"\x1b[<0;10;20M");
1262 assert!(matches!(
1263 events.first(),
1264 Some(Event::Mouse(m)) if m.x == 9 && m.y == 19 ));
1266 }
1267
1268 #[test]
1269 fn mouse_sgr_modifiers() {
1270 let mut parser = InputParser::new();
1271
1272 let events = parser.parse(b"\x1b[<28;3;4M");
1274 assert!(matches!(
1275 events.first(),
1276 Some(Event::Mouse(m))
1277 if m.modifiers.contains(Modifiers::SHIFT)
1278 && m.modifiers.contains(Modifiers::ALT)
1279 && m.modifiers.contains(Modifiers::CTRL)
1280 ));
1281 }
1282
1283 #[test]
1284 fn mouse_sgr_scroll_up() {
1285 let mut parser = InputParser::new();
1286
1287 let events = parser.parse(b"\x1b[<64;5;5M");
1289 assert!(matches!(
1290 events.first(),
1291 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollUp)
1292 ));
1293 }
1294
1295 #[test]
1296 fn mouse_sgr_scroll_down() {
1297 let mut parser = InputParser::new();
1298
1299 let events = parser.parse(b"\x1b[<65;5;5M");
1301 assert!(matches!(
1302 events.first(),
1303 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollDown)
1304 ));
1305 }
1306
1307 #[test]
1308 fn mouse_sgr_scroll_left() {
1309 let mut parser = InputParser::new();
1310
1311 let events = parser.parse(b"\x1b[<66;5;5M");
1313 assert!(matches!(
1314 events.first(),
1315 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollLeft)
1316 ));
1317 }
1318
1319 #[test]
1320 fn mouse_sgr_scroll_right() {
1321 let mut parser = InputParser::new();
1322
1323 let events = parser.parse(b"\x1b[<67;5;5M");
1325 assert!(matches!(
1326 events.first(),
1327 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollRight)
1328 ));
1329 }
1330
1331 #[test]
1332 fn mouse_sgr_drag_left() {
1333 let mut parser = InputParser::new();
1334
1335 let events = parser.parse(b"\x1b[<32;10;20M");
1337 assert!(matches!(
1338 events.first(),
1339 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Drag(MouseButton::Left))
1340 ));
1341 }
1342
1343 #[test]
1344 fn utf8_characters() {
1345 let mut parser = InputParser::new();
1346
1347 let events = parser.parse(&[0xC3, 0xA9]);
1349 assert!(matches!(
1350 events.first(),
1351 Some(Event::Key(k)) if k.code == KeyCode::Char('é')
1352 ));
1353 }
1354
1355 #[test]
1356 fn invalid_utf8_emits_replacement_then_reprocesses_byte() {
1357 let mut parser = InputParser::new();
1358
1359 let events = parser.parse(&[0xE2, 0x28]);
1361 assert_eq!(events.len(), 2);
1362 assert!(matches!(
1363 events[0],
1364 Event::Key(k) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)
1365 ));
1366 assert!(matches!(
1367 events[1],
1368 Event::Key(k) if k.code == KeyCode::Char('(')
1369 ));
1370 }
1371
1372 #[test]
1373 fn dos_protection_csi() {
1374 let mut parser = InputParser::new();
1375
1376 let mut seq = vec![0x1B, b'['];
1378 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
1379 seq.push(b'A');
1380
1381 let events = parser.parse(&seq);
1384 assert_eq!(
1385 events.len(),
1386 0,
1387 "Oversized CSI sequence should produce no events"
1388 );
1389
1390 let events = parser.parse(b"\x1b[A");
1393 assert!(matches!(
1394 events.first(),
1395 Some(Event::Key(k)) if k.code == KeyCode::Up
1396 ));
1397 }
1398
1399 #[test]
1400 fn incomplete_csi_sequence_emits_no_event() {
1401 let mut parser = InputParser::new();
1402 let events = parser.parse(b"\x1b[");
1403 assert!(events.is_empty());
1404 }
1405
1406 #[test]
1407 fn dos_protection_paste() {
1408 let mut parser = InputParser::new();
1409
1410 parser.parse(b"\x1b[200~");
1412
1413 let content = vec![b'x'; MAX_PASTE_LEN - 100]; parser.parse(&content);
1416
1417 let events = parser.parse(b"\x1b[201~");
1419
1420 assert!(matches!(
1422 events.first(),
1423 Some(Event::Paste(p)) if p.text.len() <= MAX_PASTE_LEN
1424 ));
1425 }
1426
1427 #[test]
1428 fn dos_protection_paste_overflow_terminator() {
1429 let mut parser = InputParser::new();
1430
1431 parser.parse(b"\x1b[200~");
1433
1434 let overflow = 100;
1438 let content = vec![b'a'; MAX_PASTE_LEN + overflow];
1439 parser.parse(&content);
1440
1441 let events = parser.parse(b"\x1b[201~");
1444
1445 assert_eq!(events.len(), 1, "Should emit paste event");
1446 match &events[0] {
1447 Event::Paste(p) => {
1448 assert_eq!(
1451 p.text.len(),
1452 MAX_PASTE_LEN,
1453 "Paste should be capped at MAX_PASTE_LEN bytes"
1454 );
1455 assert!(p.text.chars().all(|c| c == 'a'));
1457 }
1458 _ => unreachable!("Expected Paste event"),
1459 }
1460
1461 let events = parser.parse(b"b");
1463 assert_eq!(events.len(), 1);
1464 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('b')));
1465 }
1466
1467 #[test]
1468 fn no_panic_on_invalid_input() {
1469 let mut parser = InputParser::new();
1470
1471 let garbage = [0xFF, 0xFE, 0x00, 0x1B, 0x1B, 0x1B, b'[', 0xFF, b']', 0x00];
1473
1474 let _ = parser.parse(&garbage);
1476 }
1477
1478 #[test]
1479 fn dos_protection_paste_boundary() {
1480 let mut parser = InputParser::new();
1481 parser.parse(b"\x1b[200~");
1483
1484 let content = vec![b'x'; MAX_PASTE_LEN];
1486 parser.parse(&content);
1487
1488 let events = parser.parse(b"\x1b[201~");
1491
1492 assert!(
1493 !events.is_empty(),
1494 "Parser trapped in paste mode after hitting limit"
1495 );
1496 assert!(matches!(events[0], Event::Paste(_)));
1497 }
1498
1499 #[test]
1502 fn csi_tilde_home() {
1503 let mut parser = InputParser::new();
1504 let events = parser.parse(b"\x1b[1~");
1505 assert!(matches!(
1506 events.first(),
1507 Some(Event::Key(k)) if k.code == KeyCode::Home
1508 ));
1509 }
1510
1511 #[test]
1512 fn csi_tilde_insert() {
1513 let mut parser = InputParser::new();
1514 let events = parser.parse(b"\x1b[2~");
1515 assert!(matches!(
1516 events.first(),
1517 Some(Event::Key(k)) if k.code == KeyCode::Insert
1518 ));
1519 }
1520
1521 #[test]
1522 fn csi_tilde_delete() {
1523 let mut parser = InputParser::new();
1524 let events = parser.parse(b"\x1b[3~");
1525 assert!(matches!(
1526 events.first(),
1527 Some(Event::Key(k)) if k.code == KeyCode::Delete
1528 ));
1529 }
1530
1531 #[test]
1532 fn csi_tilde_end() {
1533 let mut parser = InputParser::new();
1534 let events = parser.parse(b"\x1b[4~");
1535 assert!(matches!(
1536 events.first(),
1537 Some(Event::Key(k)) if k.code == KeyCode::End
1538 ));
1539 }
1540
1541 #[test]
1542 fn csi_tilde_page_up() {
1543 let mut parser = InputParser::new();
1544 let events = parser.parse(b"\x1b[5~");
1545 assert!(matches!(
1546 events.first(),
1547 Some(Event::Key(k)) if k.code == KeyCode::PageUp
1548 ));
1549 }
1550
1551 #[test]
1552 fn csi_tilde_page_down() {
1553 let mut parser = InputParser::new();
1554 let events = parser.parse(b"\x1b[6~");
1555 assert!(matches!(
1556 events.first(),
1557 Some(Event::Key(k)) if k.code == KeyCode::PageDown
1558 ));
1559 }
1560
1561 #[test]
1564 fn csi_home_and_end() {
1565 let mut parser = InputParser::new();
1566 assert!(matches!(
1567 parser.parse(b"\x1b[H").first(),
1568 Some(Event::Key(k)) if k.code == KeyCode::Home
1569 ));
1570 assert!(matches!(
1571 parser.parse(b"\x1b[F").first(),
1572 Some(Event::Key(k)) if k.code == KeyCode::End
1573 ));
1574 }
1575
1576 #[test]
1579 fn ss3_home_and_end() {
1580 let mut parser = InputParser::new();
1581 assert!(matches!(
1582 parser.parse(b"\x1bOH").first(),
1583 Some(Event::Key(k)) if k.code == KeyCode::Home
1584 ));
1585 assert!(matches!(
1586 parser.parse(b"\x1bOF").first(),
1587 Some(Event::Key(k)) if k.code == KeyCode::End
1588 ));
1589 }
1590
1591 #[test]
1594 fn backtab_csi_z() {
1595 let mut parser = InputParser::new();
1596 let events = parser.parse(b"\x1b[Z");
1597 assert!(matches!(
1598 events.first(),
1599 Some(Event::Key(k)) if k.code == KeyCode::BackTab
1600 ));
1601 }
1602
1603 #[test]
1606 fn function_keys_f7_to_f12() {
1607 let mut parser = InputParser::new();
1608 assert!(matches!(
1609 parser.parse(b"\x1b[18~").first(),
1610 Some(Event::Key(k)) if k.code == KeyCode::F(7)
1611 ));
1612 assert!(matches!(
1613 parser.parse(b"\x1b[19~").first(),
1614 Some(Event::Key(k)) if k.code == KeyCode::F(8)
1615 ));
1616 assert!(matches!(
1617 parser.parse(b"\x1b[20~").first(),
1618 Some(Event::Key(k)) if k.code == KeyCode::F(9)
1619 ));
1620 assert!(matches!(
1621 parser.parse(b"\x1b[21~").first(),
1622 Some(Event::Key(k)) if k.code == KeyCode::F(10)
1623 ));
1624 assert!(matches!(
1625 parser.parse(b"\x1b[23~").first(),
1626 Some(Event::Key(k)) if k.code == KeyCode::F(11)
1627 ));
1628 assert!(matches!(
1629 parser.parse(b"\x1b[24~").first(),
1630 Some(Event::Key(k)) if k.code == KeyCode::F(12)
1631 ));
1632 }
1633
1634 #[test]
1637 fn ctrl_home_and_alt_end() {
1638 let mut parser = InputParser::new();
1639
1640 let events = parser.parse(b"\x1b[1;5H");
1642 assert!(matches!(
1643 events.first(),
1644 Some(Event::Key(k)) if k.code == KeyCode::Home && k.modifiers.contains(Modifiers::CTRL)
1645 ));
1646
1647 let events = parser.parse(b"\x1b[1;3F");
1649 assert!(matches!(
1650 events.first(),
1651 Some(Event::Key(k)) if k.code == KeyCode::End && k.modifiers.contains(Modifiers::ALT)
1652 ));
1653 }
1654
1655 #[test]
1656 fn shift_ctrl_arrow() {
1657 let mut parser = InputParser::new();
1658
1659 let events = parser.parse(b"\x1b[1;6C");
1661 assert!(matches!(
1662 events.first(),
1663 Some(Event::Key(k)) if k.code == KeyCode::Right
1664 && k.modifiers.contains(Modifiers::SHIFT)
1665 && k.modifiers.contains(Modifiers::CTRL)
1666 ));
1667 }
1668
1669 #[test]
1670 fn modifiers_on_tilde_keys() {
1671 let mut parser = InputParser::new();
1672
1673 let events = parser.parse(b"\x1b[3;5~");
1675 assert!(matches!(
1676 events.first(),
1677 Some(Event::Key(k)) if k.code == KeyCode::Delete && k.modifiers.contains(Modifiers::CTRL)
1678 ));
1679
1680 let events = parser.parse(b"\x1b[5;2~");
1682 assert!(matches!(
1683 events.first(),
1684 Some(Event::Key(k)) if k.code == KeyCode::PageUp && k.modifiers.contains(Modifiers::SHIFT)
1685 ));
1686 }
1687
1688 #[test]
1691 fn mouse_sgr_right_click() {
1692 let mut parser = InputParser::new();
1693 let events = parser.parse(b"\x1b[<2;15;10M");
1695 assert!(matches!(
1696 events.first(),
1697 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Right))
1698 && m.x == 14 && m.y == 9
1699 ));
1700 }
1701
1702 #[test]
1703 fn mouse_sgr_middle_click() {
1704 let mut parser = InputParser::new();
1705 let events = parser.parse(b"\x1b[<1;5;5M");
1707 assert!(matches!(
1708 events.first(),
1709 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Middle))
1710 ));
1711 }
1712
1713 #[test]
1714 fn mouse_sgr_button_release() {
1715 let mut parser = InputParser::new();
1716 let events = parser.parse(b"\x1b[<0;10;20m");
1718 assert!(matches!(
1719 events.first(),
1720 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Up(MouseButton::Left))
1721 ));
1722 }
1723
1724 #[test]
1725 fn mouse_sgr_moved() {
1726 let mut parser = InputParser::new();
1727 let events = parser.parse(b"\x1b[<35;10;20M");
1729 assert!(matches!(
1730 events.first(),
1731 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Moved)
1732 ));
1733 }
1734
1735 #[test]
1736 fn mouse_sgr_with_modifiers() {
1737 let mut parser = InputParser::new();
1738 let events = parser.parse(b"\x1b[<4;5;5M");
1740 assert!(matches!(
1741 events.first(),
1742 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1743 && m.modifiers.contains(Modifiers::SHIFT)
1744 ));
1745
1746 let events = parser.parse(b"\x1b[<16;5;5M");
1748 assert!(matches!(
1749 events.first(),
1750 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1751 && m.modifiers.contains(Modifiers::CTRL)
1752 ));
1753
1754 let events = parser.parse(b"\x1b[<8;5;5M");
1756 assert!(matches!(
1757 events.first(),
1758 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1759 && m.modifiers.contains(Modifiers::ALT)
1760 ));
1761 }
1762
1763 #[test]
1766 fn kitty_keyboard_release_event() {
1767 let mut parser = InputParser::new();
1768 let events = parser.parse(b"\x1b[97;1:3u");
1770 assert!(matches!(
1771 events.first(),
1772 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.kind == KeyEventKind::Release
1773 ));
1774
1775 let events = parser.parse(b"\x1b[65;5:3u");
1777 assert!(matches!(
1778 events.first(),
1779 Some(Event::Key(k))
1780 if k.code == KeyCode::Char('A')
1781 && k.modifiers.contains(Modifiers::CTRL)
1782 && k.kind == KeyEventKind::Release
1783 ));
1784 }
1785
1786 #[test]
1787 fn kitty_keyboard_special_keys() {
1788 let mut parser = InputParser::new();
1789
1790 assert!(matches!(
1792 parser.parse(b"\x1b[57344u").first(),
1793 Some(Event::Key(k)) if k.code == KeyCode::Escape
1794 ));
1795
1796 assert!(matches!(
1798 parser.parse(b"\x1b[57345u").first(),
1799 Some(Event::Key(k)) if k.code == KeyCode::Enter
1800 ));
1801
1802 assert!(matches!(
1804 parser.parse(b"\x1b[57346u").first(),
1805 Some(Event::Key(k)) if k.code == KeyCode::Tab
1806 ));
1807
1808 assert!(matches!(
1810 parser.parse(b"\x1b[57347u").first(),
1811 Some(Event::Key(k)) if k.code == KeyCode::Backspace
1812 ));
1813
1814 assert!(matches!(
1816 parser.parse(b"\x1b[57348u").first(),
1817 Some(Event::Key(k)) if k.code == KeyCode::Insert
1818 ));
1819
1820 assert!(matches!(
1822 parser.parse(b"\x1b[57349u").first(),
1823 Some(Event::Key(k)) if k.code == KeyCode::Delete
1824 ));
1825 }
1826
1827 #[test]
1828 fn kitty_keyboard_navigation_keys() {
1829 let mut parser = InputParser::new();
1830
1831 assert!(matches!(
1833 parser.parse(b"\x1b[57350u").first(),
1834 Some(Event::Key(k)) if k.code == KeyCode::Left
1835 ));
1836 assert!(matches!(
1838 parser.parse(b"\x1b[57351u").first(),
1839 Some(Event::Key(k)) if k.code == KeyCode::Right
1840 ));
1841 assert!(matches!(
1843 parser.parse(b"\x1b[57352u").first(),
1844 Some(Event::Key(k)) if k.code == KeyCode::Up
1845 ));
1846 assert!(matches!(
1848 parser.parse(b"\x1b[57353u").first(),
1849 Some(Event::Key(k)) if k.code == KeyCode::Down
1850 ));
1851 assert!(matches!(
1853 parser.parse(b"\x1b[57354u").first(),
1854 Some(Event::Key(k)) if k.code == KeyCode::PageUp
1855 ));
1856 assert!(matches!(
1858 parser.parse(b"\x1b[57355u").first(),
1859 Some(Event::Key(k)) if k.code == KeyCode::PageDown
1860 ));
1861 assert!(matches!(
1863 parser.parse(b"\x1b[57356u").first(),
1864 Some(Event::Key(k)) if k.code == KeyCode::Home
1865 ));
1866 assert!(matches!(
1868 parser.parse(b"\x1b[57357u").first(),
1869 Some(Event::Key(k)) if k.code == KeyCode::End
1870 ));
1871 }
1872
1873 #[test]
1874 fn kitty_keyboard_f_keys() {
1875 let mut parser = InputParser::new();
1876 assert!(matches!(
1878 parser.parse(b"\x1b[57364u").first(),
1879 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1880 ));
1881 assert!(matches!(
1883 parser.parse(b"\x1b[57375u").first(),
1884 Some(Event::Key(k)) if k.code == KeyCode::F(12)
1885 ));
1886 assert!(matches!(
1888 parser.parse(b"\x1b[57387u").first(),
1889 Some(Event::Key(k)) if k.code == KeyCode::F(24)
1890 ));
1891 }
1892
1893 #[test]
1894 fn kitty_keyboard_ascii_as_standard() {
1895 let mut parser = InputParser::new();
1896 assert!(matches!(
1898 parser.parse(b"\x1b[9u").first(),
1899 Some(Event::Key(k)) if k.code == KeyCode::Tab
1900 ));
1901 assert!(matches!(
1902 parser.parse(b"\x1b[13u").first(),
1903 Some(Event::Key(k)) if k.code == KeyCode::Enter
1904 ));
1905 assert!(matches!(
1906 parser.parse(b"\x1b[27u").first(),
1907 Some(Event::Key(k)) if k.code == KeyCode::Escape
1908 ));
1909 assert!(matches!(
1910 parser.parse(b"\x1b[127u").first(),
1911 Some(Event::Key(k)) if k.code == KeyCode::Backspace
1912 ));
1913 assert!(matches!(
1915 parser.parse(b"\x1b[8u").first(),
1916 Some(Event::Key(k)) if k.code == KeyCode::Backspace
1917 ));
1918 }
1919
1920 #[test]
1923 fn osc52_clipboard_bel_terminated() {
1924 let mut parser = InputParser::new();
1925 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x07");
1928 assert!(matches!(
1929 events.first(),
1930 Some(Event::Clipboard(c)) if c.content == "hello" && c.source == ClipboardSource::Osc52
1931 ));
1932 }
1933
1934 #[test]
1935 fn osc52_clipboard_st_terminated() {
1936 let mut parser = InputParser::new();
1937 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x1b\\");
1939 assert!(matches!(
1940 events.first(),
1941 Some(Event::Clipboard(c)) if c.content == "hello"
1942 ));
1943 }
1944
1945 #[test]
1946 fn osc52_clipboard_primary_selection() {
1947 let mut parser = InputParser::new();
1948 let events = parser.parse(b"\x1b]52;p;YWJj\x07");
1951 assert!(matches!(
1952 events.first(),
1953 Some(Event::Clipboard(c)) if c.content == "abc"
1954 ));
1955 }
1956
1957 #[test]
1960 fn ctrl_space_is_null() {
1961 let mut parser = InputParser::new();
1962 let events = parser.parse(&[0x00]);
1963 assert!(matches!(
1964 events.first(),
1965 Some(Event::Key(k)) if k.code == KeyCode::Null
1966 ));
1967 }
1968
1969 #[test]
1970 fn all_ctrl_letter_keys() {
1971 let mut parser = InputParser::new();
1972 for byte in 0x01..=0x1Au8 {
1974 let events = parser.parse(&[byte]);
1975 assert_eq!(
1976 events.len(),
1977 1,
1978 "Ctrl+{} should produce one event",
1979 (byte + b'a' - 1) as char
1980 );
1981 match byte {
1982 0x08 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace)),
1983 0x09 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Tab)),
1984 0x0D => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Enter)),
1985 _ => {
1986 let expected_char = (byte + b'a' - 1) as char;
1987 match &events[0] {
1988 Event::Key(k) => {
1989 assert_eq!(
1990 k.code,
1991 KeyCode::Char(expected_char),
1992 "Byte 0x{byte:02X} should produce Ctrl+{expected_char}"
1993 );
1994 assert!(
1995 k.modifiers.contains(Modifiers::CTRL),
1996 "Byte 0x{byte:02X} should have Ctrl modifier"
1997 );
1998 }
1999 other => {
2000 panic!("Byte 0x{byte:02X}: expected Key event, got {other:?}");
2001 }
2002 }
2003 }
2004 }
2005 }
2006 }
2007
2008 #[test]
2011 fn utf8_3byte_cjk() {
2012 let mut parser = InputParser::new();
2013 let events = parser.parse(&[0xE4, 0xB8, 0xAD]);
2015 assert!(matches!(
2016 events.first(),
2017 Some(Event::Key(k)) if k.code == KeyCode::Char('中')
2018 ));
2019 }
2020
2021 #[test]
2022 fn utf8_4byte_emoji() {
2023 let mut parser = InputParser::new();
2024 let events = parser.parse(&[0xF0, 0x9F, 0xA6, 0x80]);
2026 assert!(matches!(
2027 events.first(),
2028 Some(Event::Key(k)) if k.code == KeyCode::Char('🦀')
2029 ));
2030 }
2031
2032 #[test]
2035 fn empty_input_returns_no_events() {
2036 let mut parser = InputParser::new();
2037 let events = parser.parse(b"");
2038 assert!(events.is_empty());
2039 }
2040
2041 #[test]
2044 fn unknown_csi_tilde_ignored() {
2045 let mut parser = InputParser::new();
2046 let events = parser.parse(b"\x1b[99~");
2048 assert!(events.is_empty());
2049
2050 let events = parser.parse(b"a");
2052 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('a')));
2053 }
2054
2055 #[test]
2058 fn alt_special_chars() {
2059 let mut parser = InputParser::new();
2060
2061 let events = parser.parse(b"\x1b ");
2063 assert!(matches!(
2064 events.first(),
2065 Some(Event::Key(k)) if k.code == KeyCode::Char(' ') && k.modifiers.contains(Modifiers::ALT)
2066 ));
2067
2068 let events = parser.parse(b"\x1b5");
2070 assert!(matches!(
2071 events.first(),
2072 Some(Event::Key(k)) if k.code == KeyCode::Char('5') && k.modifiers.contains(Modifiers::ALT)
2073 ));
2074
2075 let events = parser.parse(b"\x1b}");
2077 assert!(matches!(
2078 events.first(),
2079 Some(Event::Key(k)) if k.code == KeyCode::Char('}') && k.modifiers.contains(Modifiers::ALT)
2080 ));
2081 }
2082
2083 #[test]
2086 fn ss3_arrow_keys() {
2087 let mut parser = InputParser::new();
2088 assert!(matches!(
2089 parser.parse(b"\x1bOA").first(),
2090 Some(Event::Key(k)) if k.code == KeyCode::Up
2091 ));
2092 assert!(matches!(
2093 parser.parse(b"\x1bOB").first(),
2094 Some(Event::Key(k)) if k.code == KeyCode::Down
2095 ));
2096 assert!(matches!(
2097 parser.parse(b"\x1bOC").first(),
2098 Some(Event::Key(k)) if k.code == KeyCode::Right
2099 ));
2100 assert!(matches!(
2101 parser.parse(b"\x1bOD").first(),
2102 Some(Event::Key(k)) if k.code == KeyCode::Left
2103 ));
2104 }
2105
2106 #[test]
2109 fn xterm_modifier_encoding() {
2110 assert_eq!(InputParser::modifiers_from_xterm(1), Modifiers::NONE);
2112 assert_eq!(InputParser::modifiers_from_xterm(2), Modifiers::SHIFT);
2113 assert_eq!(InputParser::modifiers_from_xterm(3), Modifiers::ALT);
2114 assert_eq!(
2115 InputParser::modifiers_from_xterm(4),
2116 Modifiers::SHIFT | Modifiers::ALT
2117 );
2118 assert_eq!(InputParser::modifiers_from_xterm(5), Modifiers::CTRL);
2119 assert_eq!(
2120 InputParser::modifiers_from_xterm(6),
2121 Modifiers::SHIFT | Modifiers::CTRL
2122 );
2123 assert_eq!(InputParser::modifiers_from_xterm(9), Modifiers::SUPER);
2124 }
2125
2126 #[test]
2129 fn ss3_interrupted_by_esc() {
2130 let mut parser = InputParser::new();
2131 let events = parser.parse(b"\x1bO\x1b[A");
2133 assert!(matches!(
2135 events.first(),
2136 Some(Event::Key(k)) if k.code == KeyCode::Up
2137 ));
2138 }
2139
2140 #[test]
2143 fn kitty_keyboard_reserved_keycode_ignored() {
2144 let mut parser = InputParser::new();
2145 let events = parser.parse(b"\x1b[57360u");
2147 assert!(events.is_empty());
2148
2149 let events = parser.parse(b"x");
2151 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('x')));
2152 }
2153 #[test]
2154 fn utf8_invalid_sequence_emits_replacement() {
2155 let mut parser = InputParser::new();
2156
2157 let events = parser.parse(&[0xE0, 0x41]);
2161 assert_eq!(events.len(), 2);
2162
2163 match &events[0] {
2164 Event::Key(k) => assert_eq!(k.code, KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2165 _ => panic!("Expected replacement character"),
2166 }
2167
2168 match &events[1] {
2169 Event::Key(k) => assert_eq!(k.code, KeyCode::Char('A')),
2170 _ => panic!("Expected character 'A'"),
2171 }
2172 }
2173
2174 #[test]
2175 fn utf8_invalid_lead_emits_replacement() {
2176 let mut parser = InputParser::new();
2177
2178 let events = parser.parse(&[0xC0, b'a']);
2180 assert!(
2181 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2182 "Expected replacement for invalid lead"
2183 );
2184 assert!(
2185 events
2186 .iter()
2187 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('a'))),
2188 "Expected subsequent ASCII to be preserved"
2189 );
2190
2191 let events = parser.parse(&[0xF5, b'b']);
2193 assert!(
2194 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2195 "Expected replacement for out-of-range lead"
2196 );
2197 assert!(
2198 events
2199 .iter()
2200 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('b'))),
2201 "Expected subsequent ASCII to be preserved"
2202 );
2203 }
2204}
2205
2206#[cfg(test)]
2207mod proptest_fuzz {
2208 use super::*;
2209 use proptest::prelude::*;
2210
2211 fn arb_byte() -> impl Strategy<Value = u8> {
2215 any::<u8>()
2216 }
2217
2218 fn arb_byte_vec(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
2219 prop::collection::vec(arb_byte(), 0..=max_len)
2220 }
2221
2222 fn csi_sequence() -> impl Strategy<Value = Vec<u8>> {
2224 let params = prop::collection::vec(0x30u8..=0x3F, 0..=20);
2225 let final_byte = 0x40u8..=0x7E;
2226 (params, final_byte).prop_map(|(p, f)| {
2227 let mut buf = vec![0x1B, b'['];
2228 buf.extend_from_slice(&p);
2229 buf.push(f);
2230 buf
2231 })
2232 }
2233
2234 fn osc_sequence() -> impl Strategy<Value = Vec<u8>> {
2236 let content = prop::collection::vec(0x20u8..=0x7E, 0..=64);
2237 let terminator = prop_oneof![
2238 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
2241 (content, terminator).prop_map(|(c, t)| {
2242 let mut buf = vec![0x1B, b']'];
2243 buf.extend_from_slice(&c);
2244 buf.extend_from_slice(&t);
2245 buf
2246 })
2247 }
2248
2249 fn ss3_sequence() -> impl Strategy<Value = Vec<u8>> {
2251 (0x40u8..=0x7E).prop_map(|f| vec![0x1B, b'O', f])
2252 }
2253
2254 fn paste_sequence() -> impl Strategy<Value = Vec<u8>> {
2256 prop::collection::vec(0x20u8..=0x7E, 0..=128).prop_map(|content| {
2257 let mut buf = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
2258 buf.extend_from_slice(&content);
2259 buf.extend_from_slice(b"\x1b[201~");
2260 buf
2261 })
2262 }
2263
2264 fn mixed_adversarial() -> impl Strategy<Value = Vec<u8>> {
2266 let fragment = prop_oneof![
2267 csi_sequence(),
2268 osc_sequence(),
2269 ss3_sequence(),
2270 paste_sequence(),
2271 arb_byte_vec(16), Just(vec![0x1B]), Just(vec![0x1B, b'[']), Just(vec![0x1B, b']']), prop::collection::vec(0x80u8..=0xFF, 1..=4), ];
2277 prop::collection::vec(fragment, 1..=8)
2278 .prop_map(|frags| frags.into_iter().flatten().collect())
2279 }
2280
2281 proptest! {
2284 #[test]
2286 fn random_bytes_never_panic(input in arb_byte_vec(512)) {
2287 let mut parser = InputParser::new();
2288 let _ = parser.parse(&input);
2289 }
2290
2291 #[test]
2293 fn parser_recovers_after_garbage(input in arb_byte_vec(256)) {
2294 let mut parser = InputParser::new();
2295 let _ = parser.parse(&input);
2296
2297 let events = parser.parse(b"z");
2299 let _ = events;
2302 }
2303
2304 #[test]
2306 fn mixed_sequences_never_panic(input in mixed_adversarial()) {
2307 let mut parser = InputParser::new();
2308 let _ = parser.parse(&input);
2309 }
2310
2311 #[test]
2313 fn events_are_well_formed(input in arb_byte_vec(256)) {
2314 let mut parser = InputParser::new();
2315 let events = parser.parse(&input);
2316 for event in &events {
2317 let _ = format!("{event:?}");
2319 }
2320 }
2321
2322 #[test]
2324 fn csi_event_count_bounded(seq in csi_sequence()) {
2325 let mut parser = InputParser::new();
2326 let events = parser.parse(&seq);
2327 prop_assert!(events.len() <= seq.len(),
2328 "Got {} events from {} bytes", events.len(), seq.len());
2329 }
2330
2331 #[test]
2333 fn osc_event_count_bounded(seq in osc_sequence()) {
2334 let mut parser = InputParser::new();
2335 let events = parser.parse(&seq);
2336 prop_assert!(events.len() <= seq.len(),
2337 "Got {} events from {} bytes", events.len(), seq.len());
2338 }
2339
2340 #[test]
2342 fn paste_content_bounded(content in prop::collection::vec(arb_byte(), 0..=2048)) {
2343 let mut parser = InputParser::new();
2344 let mut input = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
2345 input.extend_from_slice(&content);
2346 input.extend_from_slice(b"\x1b[201~");
2347
2348 let events = parser.parse(&input);
2349 for event in &events {
2350 if let Event::Paste(p) = event {
2351 prop_assert!(p.text.len() <= MAX_PASTE_LEN,
2352 "Paste text {} exceeds limit {}", p.text.len(), MAX_PASTE_LEN);
2353 }
2354 }
2355 }
2356
2357 #[test]
2359 fn incremental_matches_bulk(input in arb_byte_vec(128)) {
2360 let mut bulk_parser = InputParser::new();
2361 let bulk_events = bulk_parser.parse(&input);
2362
2363 let mut incr_parser = InputParser::new();
2364 let mut incr_events = Vec::new();
2365 for byte in &input {
2366 incr_events.extend(incr_parser.parse(std::slice::from_ref(byte)));
2367 }
2368
2369 let bulk_dbg: Vec<String> = bulk_events.iter().map(|e| format!("{e:?}")).collect();
2370 let incr_dbg: Vec<String> = incr_events.iter().map(|e| format!("{e:?}")).collect();
2371 prop_assert_eq!(bulk_dbg, incr_dbg,
2372 "Bulk vs incremental mismatch for input {:?}", input);
2373 }
2374
2375 #[test]
2378 fn deterministic_output(input in arb_byte_vec(128)) {
2379 let mut parser1 = InputParser::new();
2380 let events1 = parser1.parse(&input);
2381
2382 let mut parser2 = InputParser::new();
2383 let events2 = parser2.parse(&input);
2384
2385 let dbg1: Vec<String> = events1.iter().map(|e| format!("{e:?}")).collect();
2386 let dbg2: Vec<String> = events2.iter().map(|e| format!("{e:?}")).collect();
2387 prop_assert_eq!(dbg1, dbg2);
2388 }
2389 }
2390
2391 #[test]
2395 fn no_quadratic_blowup() {
2396 let mut parser = InputParser::new();
2397
2398 let garbage: Vec<u8> = (0..65536).map(|i| (i % 256) as u8).collect();
2400 let _ = parser.parse(&garbage);
2401
2402 let events = parser.parse(b"a");
2404 let _ = events; }
2406
2407 #[test]
2409 fn oversized_csi_transitions_to_ignore() {
2410 let mut parser = InputParser::new();
2411
2412 let mut input = vec![0x1B, b'['];
2414 input.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
2415 input.push(b'm');
2416
2417 let _ = parser.parse(&input);
2418
2419 let events = parser.parse(b"x");
2421 assert_eq!(events.len(), 1);
2422 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('x')));
2423 }
2424
2425 #[test]
2427 fn oversized_osc_transitions_to_ignore() {
2428 let mut parser = InputParser::new();
2429
2430 let mut input = vec![0x1B, b']'];
2432 input.extend(std::iter::repeat_n(b'a', MAX_OSC_LEN + 100));
2433 input.push(0x07); let _ = parser.parse(&input);
2436
2437 let events = parser.parse(b"y");
2439 assert_eq!(events.len(), 1);
2440 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('y')));
2441 }
2442
2443 #[test]
2445 fn rapid_esc_toggle() {
2446 let mut parser = InputParser::new();
2447
2448 let input: Vec<u8> = vec![0x1B; 1000];
2450 let _ = parser.parse(&input);
2451
2452 let events = parser.parse(b"k");
2454 assert!(!events.is_empty());
2455 }
2456
2457 #[test]
2459 fn unterminated_paste_recovery() {
2460 let mut parser = InputParser::new();
2461
2462 let mut input = b"\x1b[200~".to_vec();
2464 input.extend(std::iter::repeat_n(b'x', 2048));
2465
2466 let _ = parser.parse(&input);
2467
2468 let events = parser.parse(b"\x1b[201~");
2470 assert!(
2471 !events.is_empty(),
2472 "Parser should emit paste event on terminator"
2473 );
2474 }
2475
2476 #[test]
2478 fn truncated_utf8_lead_bytes() {
2479 let mut parser = InputParser::new();
2480
2481 for lead in [0xC2, 0xE0, 0xF0] {
2483 let _ = parser.parse(&[lead]);
2484 let events = parser.parse(b"a");
2486 let _ = events;
2488 }
2489 }
2490
2491 #[test]
2493 fn null_bytes_interleaved() {
2494 let mut parser = InputParser::new();
2495
2496 let input = b"\x00A\x00\x1b[A\x00B\x00";
2497 let events = parser.parse(input);
2498 assert!(
2500 events.len() >= 2,
2501 "Expected at least 2 events, got {}",
2502 events.len()
2503 );
2504 }
2505
2506 fn osc52_sequence() -> impl Strategy<Value = Vec<u8>> {
2510 let selector = prop_oneof![Just(b'c'), Just(b'p'), Just(b's')];
2511 let payload = prop::collection::vec(
2513 prop_oneof![
2514 0x41u8..=0x5A, 0x61u8..=0x7A, 0x30u8..=0x39, Just(b'+'),
2518 Just(b'/'),
2519 Just(b'='),
2520 ],
2521 0..=128,
2522 );
2523 let terminator = prop_oneof![
2524 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
2527 (selector, payload, terminator).prop_map(|(sel, pay, term)| {
2528 let mut buf = vec![0x1B, b']', b'5', b'2', b';', sel, b';'];
2529 buf.extend_from_slice(&pay);
2530 buf.extend_from_slice(&term);
2531 buf
2532 })
2533 }
2534
2535 fn sgr_mouse_sequence() -> impl Strategy<Value = Vec<u8>> {
2537 let button_code = 0u16..128;
2538 let x = 1u16..300;
2539 let y = 1u16..100;
2540 let final_byte = prop_oneof![Just(b'M'), Just(b'm')];
2541 (button_code, x, y, final_byte)
2542 .prop_map(|(btn, x, y, fb)| format!("\x1b[<{btn};{x};{y}{}", fb as char).into_bytes())
2543 }
2544
2545 fn kitty_keyboard_sequence() -> impl Strategy<Value = Vec<u8>> {
2547 let keycode = prop_oneof![
2548 0x20u32..0x7F, 0x57344u32..0x57400, 0x100u32..0x200, ];
2552 let modifier = 1u32..16;
2553 let kind = prop_oneof![Just(1u32), Just(2u32), Just(3u32)]; (keycode, prop::option::of(modifier), prop::option::of(kind)).prop_map(
2555 |(kc, mods, kind)| match (mods, kind) {
2556 (Some(m), Some(k)) => format!("\x1b[{kc};{m}:{k}u").into_bytes(),
2557 (Some(m), None) => format!("\x1b[{kc};{m}u").into_bytes(),
2558 _ => format!("\x1b[{kc}u").into_bytes(),
2559 },
2560 )
2561 }
2562
2563 proptest! {
2564 #[test]
2568 fn osc52_never_panics(seq in osc52_sequence()) {
2569 let mut parser = InputParser::new();
2570 let events = parser.parse(&seq);
2571 for event in &events {
2573 if let Event::Clipboard(c) = event {
2574 prop_assert!(!c.content.is_empty() || c.content.is_empty(),
2575 "Clipboard event must have a content field");
2576 }
2577 }
2578 }
2579
2580 #[test]
2582 fn osc52_corrupt_base64_safe(payload in arb_byte_vec(128)) {
2583 let mut parser = InputParser::new();
2584 let mut input = b"\x1b]52;c;".to_vec();
2585 input.extend_from_slice(&payload);
2586 input.push(0x07); let _ = parser.parse(&input);
2588 }
2589
2590 #[test]
2594 fn sgr_mouse_never_panics(seq in sgr_mouse_sequence()) {
2595 let mut parser = InputParser::new();
2596 let events = parser.parse(&seq);
2597 for event in &events {
2598 let _ = format!("{event:?}");
2600 }
2601 }
2602
2603 #[test]
2605 fn sgr_mouse_extreme_coords(
2606 btn in 0u16..128,
2607 x in 0u16..=65535,
2608 y in 0u16..=65535,
2609 ) {
2610 let mut parser = InputParser::new();
2611 let input = format!("\x1b[<{btn};{x};{y}M").into_bytes();
2612 let events = parser.parse(&input);
2613 for event in &events {
2614 if let Event::Mouse(m) = event {
2615 prop_assert!(m.x <= x, "Mouse x {} > input x {}", m.x, x);
2616 prop_assert!(m.y <= y, "Mouse y {} > input y {}", m.y, y);
2617 }
2618 }
2619 }
2620
2621 #[test]
2625 fn kitty_keyboard_never_panics(seq in kitty_keyboard_sequence()) {
2626 let mut parser = InputParser::new();
2627 let _ = parser.parse(&seq);
2628 }
2629
2630 #[test]
2634 fn truncated_csi_then_valid(
2635 params in prop::collection::vec(0x30u8..=0x3F, 1..=10),
2636 valid_char in 0x20u8..0x7F,
2637 ) {
2638 let mut parser = InputParser::new();
2639
2640 let mut partial = vec![0x1B, b'['];
2642 partial.extend_from_slice(¶ms);
2643 let _ = parser.parse(&partial);
2644
2645 let events = parser.parse(&[0x1B, b'[', b'A']); let _ = events;
2650
2651 let events = parser.parse(&[valid_char]);
2653 let _ = events;
2654 }
2655
2656 #[test]
2658 fn truncated_osc_then_valid(
2659 content in prop::collection::vec(0x20u8..=0x7E, 1..=32),
2660 ) {
2661 let mut parser = InputParser::new();
2662
2663 let mut partial = vec![0x1B, b']'];
2665 partial.extend_from_slice(&content);
2666 let _ = parser.parse(&partial);
2667
2668 let events = parser.parse(b"\x1bz");
2670 let _ = events;
2671 }
2672
2673 #[test]
2677 fn csi_near_limit_produces_event(
2678 fill_byte in 0x30u8..=0x39, ) {
2680 let mut parser = InputParser::new();
2681
2682 let mut input = vec![0x1B, b'['];
2683 input.extend(std::iter::repeat_n(fill_byte, MAX_CSI_LEN - 1));
2685 input.push(b'm'); let events = parser.parse(&input);
2688 let _ = events;
2691
2692 let events = parser.parse(b"a");
2694 prop_assert!(!events.is_empty(), "Parser stuck after near-limit CSI");
2695 }
2696
2697 #[test]
2699 fn osc_near_limit_processes(
2700 fill_byte in 0x20u8..=0x7E,
2701 ) {
2702 let mut parser = InputParser::new();
2703
2704 let mut input = vec![0x1B, b']'];
2705 input.extend(std::iter::repeat_n(fill_byte, MAX_OSC_LEN - 1));
2706 input.push(0x07); let _ = parser.parse(&input);
2709
2710 let events = parser.parse(b"b");
2712 prop_assert!(!events.is_empty(), "Parser stuck after near-limit OSC");
2713 }
2714
2715 #[test]
2719 fn consecutive_pastes_emit_events(count in 2usize..=5) {
2720 let mut parser = InputParser::new();
2721 let mut input = Vec::new();
2722
2723 for i in 0..count {
2724 input.extend_from_slice(b"\x1b[200~");
2725 input.extend_from_slice(format!("paste_{i}").as_bytes());
2726 input.extend_from_slice(b"\x1b[201~");
2727 }
2728
2729 let events = parser.parse(&input);
2730 let paste_events: Vec<_> = events.iter()
2731 .filter(|e| matches!(e, Event::Paste(_)))
2732 .collect();
2733
2734 prop_assert_eq!(paste_events.len(), count,
2735 "Expected {} paste events, got {}", count, paste_events.len());
2736 }
2737
2738 #[test]
2740 fn paste_with_invalid_utf8(content in arb_byte_vec(256)) {
2741 let mut parser = InputParser::new();
2742 let mut input = b"\x1b[200~".to_vec();
2743 input.extend_from_slice(&content);
2744 input.extend_from_slice(b"\x1b[201~");
2745
2746 let events = parser.parse(&input);
2747 for event in &events {
2748 if let Event::Paste(p) = event {
2749 prop_assert!(p.text.is_char_boundary(0), "Paste text is not valid UTF-8");
2751 }
2752 }
2753 }
2754
2755 #[test]
2759 fn recovery_via_esc_reset(garbage in arb_byte_vec(256)) {
2760 let mut parser = InputParser::new();
2761 let _ = parser.parse(&garbage);
2762
2763 let _ = parser.parse(b"\x07\x1b\\\x1b");
2766 let _ = parser.parse(b"\x1b");
2767
2768 let _ = parser.parse(b"z");
2770
2771 let events = parser.parse(b"q");
2773 prop_assert!(!events.is_empty(),
2776 "Parser did not recover after garbage + reset");
2777 }
2778 }
2779}