1#![forbid(unsafe_code)]
2
3use crate::event::{
27 ClipboardEvent, ClipboardSource, Event, KeyCode, KeyEvent, KeyEventKind, Modifiers,
28 MouseButton, MouseEvent, MouseEventKind, PasteEvent,
29};
30
31#[cfg(feature = "tracing")]
33use crate::logging::{debug, debug_span, trace};
34#[cfg(not(feature = "tracing"))]
35use crate::{debug, debug_span, trace};
36
37const MAX_CSI_LEN: usize = 256;
39
40const MAX_OSC_LEN: usize = 102_400;
42
43const MAX_PASTE_LEN: usize = 1024 * 1024; const MAX_EVENT_RESERVE_HINT: usize = 8 * 1024 + 1;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
53enum ParserState {
54 #[default]
56 Ground,
57 Escape,
59 Csi,
61 CsiParam,
63 CsiIgnore,
65 Ss3,
67 Osc,
69 OscContent,
71 OscEscape,
73 OscIgnore,
75 DcsIgnore,
81 DcsEscape,
84 Utf8 {
86 collected: u8,
88 expected: u8,
90 alt: bool,
93 },
94 MouseX10 { collected: u8, buffer: [u8; 3] },
96}
97
98#[derive(Debug)]
108pub struct InputParser {
109 state: ParserState,
111 buffer: Vec<u8>,
113 paste_buffer: Vec<u8>,
115 utf8_buffer: [u8; 4],
117 in_paste: bool,
119 pending_event: Option<Event>,
121 expect_x10_mouse: bool,
130 allow_legacy_mouse: bool,
137}
138
139impl Default for InputParser {
140 fn default() -> Self {
141 Self::new()
142 }
143}
144
145impl InputParser {
146 #[inline]
147 fn event_reserve_hint(input_len: usize) -> usize {
148 input_len.saturating_add(1).min(MAX_EVENT_RESERVE_HINT)
149 }
150
151 #[must_use]
153 pub fn new() -> Self {
154 Self {
155 state: ParserState::Ground,
156 buffer: Vec::with_capacity(64),
157 paste_buffer: Vec::new(),
158 utf8_buffer: [0; 4],
159 in_paste: false,
160 pending_event: None,
161 expect_x10_mouse: false,
162 allow_legacy_mouse: false,
163 }
164 }
165
166 pub fn set_expect_x10_mouse(&mut self, enabled: bool) {
171 self.expect_x10_mouse = enabled;
172 }
173
174 pub fn set_allow_legacy_mouse(&mut self, enabled: bool) {
181 self.allow_legacy_mouse = enabled;
182 }
183
184 #[must_use]
187 pub const fn has_pending_timeout_state(&self) -> bool {
188 matches!(self.state, ParserState::Escape | ParserState::Utf8 { .. })
189 }
190
191 pub fn timeout(&mut self) -> Option<Event> {
196 match self.state {
197 ParserState::Escape => {
198 self.state = ParserState::Ground;
199 Some(Event::Key(KeyEvent::new(KeyCode::Escape)))
200 }
201 ParserState::Utf8 { alt, .. } => {
202 self.state = ParserState::Ground;
205 self.utf8_buffer = [0; 4];
206 let mods = if alt { Modifiers::ALT } else { Modifiers::NONE };
207 Some(Event::Key(
208 KeyEvent::new(KeyCode::Char(std::char::REPLACEMENT_CHARACTER))
209 .with_modifiers(mods),
210 ))
211 }
212 _ => None,
213 }
214 }
215
216 pub fn parse(&mut self, input: &[u8]) -> Vec<Event> {
218 let mut events = Vec::with_capacity(Self::event_reserve_hint(input.len()));
219 self.parse_with(input, |event| events.push(event));
220 events
221 }
222
223 pub fn parse_with<F>(&mut self, input: &[u8], mut emit: F)
225 where
226 F: FnMut(Event),
227 {
228 let span = debug_span!("event.normalize", raw_byte_count = input.len());
229 let _guard = span.enter();
230 trace!("raw input bytes: {} bytes", input.len());
231
232 for &byte in input {
233 if let Some(event) = self.process_byte(byte) {
234 debug!(event_type = event.event_type_label(), "normalized event");
235 emit(event);
236 }
237 if let Some(pending) = self.pending_event.take() {
238 debug!(event_type = pending.event_type_label(), "normalized event");
239 emit(pending);
240 }
241 }
242 }
243
244 pub fn parse_into(&mut self, input: &[u8], events: &mut Vec<Event>) {
249 let needed = Self::event_reserve_hint(input.len());
250 let available = events.capacity().saturating_sub(events.len());
251 if available < needed {
252 events.reserve(needed - available);
253 }
254 self.parse_with(input, |event| events.push(event));
255 }
256
257 fn process_byte(&mut self, byte: u8) -> Option<Event> {
259 if self.in_paste {
261 return self.process_paste_byte(byte);
262 }
263
264 match self.state {
265 ParserState::Ground => self.process_ground(byte),
266 ParserState::Escape => self.process_escape(byte),
267 ParserState::Csi => self.process_csi(byte),
268 ParserState::CsiParam => self.process_csi_param(byte),
269 ParserState::CsiIgnore => self.process_csi_ignore(byte),
270 ParserState::Ss3 => self.process_ss3(byte),
271 ParserState::Osc => self.process_osc(byte),
272 ParserState::OscContent => self.process_osc_content(byte),
273 ParserState::OscEscape => self.process_osc_escape(byte),
274 ParserState::OscIgnore => self.process_osc_ignore(byte),
275 ParserState::DcsIgnore => self.process_dcs_ignore(byte),
276 ParserState::DcsEscape => self.process_dcs_escape(byte),
277 ParserState::Utf8 {
278 collected,
279 expected,
280 alt,
281 } => self.process_utf8(byte, collected, expected, alt),
282 ParserState::MouseX10 { .. } => self.process_mouse_x10(byte),
283 }
284 }
285
286 fn process_ground(&mut self, byte: u8) -> Option<Event> {
288 match byte {
289 0x1B => {
291 self.state = ParserState::Escape;
292 None
293 }
294 0x9B => {
296 self.state = ParserState::Csi;
297 self.buffer.clear();
298 None
299 }
300 0x8F => {
302 self.state = ParserState::Ss3;
303 None
304 }
305 0x9D => {
307 self.state = ParserState::Osc;
308 self.buffer.clear();
309 None
310 }
311 0x00 => Some(Event::Key(KeyEvent::new(KeyCode::Null))),
313 0x08 => Some(Event::Key(KeyEvent::new(KeyCode::Backspace))),
315 0x09 => Some(Event::Key(KeyEvent::new(KeyCode::Tab))),
317 0x0D => Some(Event::Key(KeyEvent::new(KeyCode::Enter))),
319 0x01..=0x07 | 0x0A..=0x0C | 0x0E..=0x1A => {
321 let c = (byte + b'a' - 1) as char;
322 Some(Event::Key(
323 KeyEvent::new(KeyCode::Char(c)).with_modifiers(Modifiers::CTRL),
324 ))
325 }
326 0x1C => Some(Event::Key(
328 KeyEvent::new(KeyCode::Char('\\')).with_modifiers(Modifiers::CTRL),
329 )),
330 0x1D => Some(Event::Key(
331 KeyEvent::new(KeyCode::Char(']')).with_modifiers(Modifiers::CTRL),
332 )),
333 0x1E => Some(Event::Key(
334 KeyEvent::new(KeyCode::Char('^')).with_modifiers(Modifiers::CTRL),
335 )),
336 0x1F => Some(Event::Key(
337 KeyEvent::new(KeyCode::Char('_')).with_modifiers(Modifiers::CTRL),
338 )),
339 0x7F => Some(Event::Key(KeyEvent::new(KeyCode::Backspace))),
341 0x20..=0x7E => Some(Event::Key(KeyEvent::new(KeyCode::Char(byte as char)))),
343 0xC2..=0xDF => {
345 self.utf8_buffer[0] = byte;
346 self.state = ParserState::Utf8 {
347 collected: 1,
348 expected: 2,
349 alt: false,
350 };
351 None
352 }
353 0xE0..=0xEF => {
354 self.utf8_buffer[0] = byte;
355 self.state = ParserState::Utf8 {
356 collected: 1,
357 expected: 3,
358 alt: false,
359 };
360 None
361 }
362 0xF0..=0xF4 => {
363 self.utf8_buffer[0] = byte;
364 self.state = ParserState::Utf8 {
365 collected: 1,
366 expected: 4,
367 alt: false,
368 };
369 None
370 }
371 0xC0..=0xC1 | 0xF5..=0xFF => Some(Event::Key(KeyEvent::new(KeyCode::Char(
373 std::char::REPLACEMENT_CHARACTER,
374 )))),
375 _ => None,
377 }
378 }
379
380 fn process_escape(&mut self, byte: u8) -> Option<Event> {
382 match byte {
383 b'[' => {
385 self.state = ParserState::Csi;
386 self.buffer.clear();
387 None
388 }
389 b'O' => {
391 self.state = ParserState::Ss3;
392 None
393 }
394 b']' => {
396 self.state = ParserState::Osc;
397 self.buffer.clear();
398 None
399 }
400 b'P' => {
418 self.state = ParserState::DcsIgnore;
419 self.buffer.clear();
420 None
421 }
422 0x1B => {
425 self.state = ParserState::Ground;
426 Some(Event::Key(
427 KeyEvent::new(KeyCode::Escape).with_modifiers(Modifiers::ALT),
428 ))
429 }
430 0x00..=0x1F => {
432 self.state = ParserState::Ground;
433 if let Some(mut event) = self.process_ground(byte) {
436 if let Event::Key(ref mut key) = event {
437 key.modifiers |= Modifiers::ALT;
438 }
439 Some(event)
440 } else {
441 None
442 }
443 }
444 0x20..=0x7E => {
446 self.state = ParserState::Ground;
447 Some(Event::Key(
448 KeyEvent::new(KeyCode::Char(byte as char)).with_modifiers(Modifiers::ALT),
449 ))
450 }
451 0x7F => {
453 self.state = ParserState::Ground;
454 Some(Event::Key(
455 KeyEvent::new(KeyCode::Backspace).with_modifiers(Modifiers::ALT),
456 ))
457 }
458 0xC2..=0xDF => {
463 self.utf8_buffer[0] = byte;
464 self.state = ParserState::Utf8 {
465 collected: 1,
466 expected: 2,
467 alt: true,
468 };
469 None
470 }
471 0xE0..=0xEF => {
472 self.utf8_buffer[0] = byte;
473 self.state = ParserState::Utf8 {
474 collected: 1,
475 expected: 3,
476 alt: true,
477 };
478 None
479 }
480 0xF0..=0xF4 => {
481 self.utf8_buffer[0] = byte;
482 self.state = ParserState::Utf8 {
483 collected: 1,
484 expected: 4,
485 alt: true,
486 };
487 None
488 }
489 0xC0..=0xC1 | 0xF5..=0xFF => {
492 self.state = ParserState::Ground;
493 Some(Event::Key(
494 KeyEvent::new(KeyCode::Char(std::char::REPLACEMENT_CHARACTER))
495 .with_modifiers(Modifiers::ALT),
496 ))
497 }
498 _ => {
501 self.state = ParserState::Ground;
502 None
503 }
504 }
505 }
506
507 fn process_csi(&mut self, byte: u8) -> Option<Event> {
509 if byte == 0x1B {
511 self.state = ParserState::Escape;
512 self.buffer.clear();
513 return None;
514 }
515
516 self.buffer.push(byte);
517
518 match byte {
519 0x20..=0x3F => {
521 self.state = ParserState::CsiParam;
522 None
523 }
524 0x40..=0x7E => {
526 if self.expect_x10_mouse && byte == b'M' && self.buffer.len() == 1 {
530 self.state = ParserState::MouseX10 {
531 collected: 0,
532 buffer: [0; 3],
533 };
534 self.buffer.clear();
535 return None;
536 }
537
538 self.state = ParserState::Ground;
539 self.parse_csi_sequence()
540 }
541 _ => {
546 self.state = ParserState::Ground;
547 self.buffer.clear();
548 self.process_ground(byte)
549 }
550 }
551 }
552
553 fn process_csi_param(&mut self, byte: u8) -> Option<Event> {
555 if byte == 0x1B {
557 self.state = ParserState::Escape;
558 self.buffer.clear();
559 return None;
560 }
561
562 if self.buffer.len() >= MAX_CSI_LEN && !(0x40..=0x7E).contains(&byte) {
568 self.state = ParserState::CsiIgnore;
569 self.buffer.clear();
570 return None;
571 }
572
573 self.buffer.push(byte);
574
575 match byte {
576 0x20..=0x3F => None,
578 0x40..=0x7E => {
580 self.state = ParserState::Ground;
581 self.parse_csi_sequence()
582 }
583 _ => {
585 self.state = ParserState::Ground;
586 self.buffer.clear();
587 self.process_ground(byte)
588 }
589 }
590 }
591
592 fn process_csi_ignore(&mut self, byte: u8) -> Option<Event> {
594 if byte == 0x1B {
596 self.state = ParserState::Escape;
597 return None;
598 }
599
600 if (0x40..=0x7E).contains(&byte) {
602 self.state = ParserState::Ground;
603 None
604 } else if (0x20..=0x3F).contains(&byte) {
605 None
607 } else {
608 self.state = ParserState::Ground;
610 self.process_ground(byte)
611 }
612 }
613
614 fn parse_csi_sequence(&mut self) -> Option<Event> {
616 let seq = std::mem::take(&mut self.buffer);
617 if seq.is_empty() {
618 return None;
619 }
620
621 let final_byte = *seq.last()?;
622 let params = &seq[..seq.len() - 1];
623
624 match (params, final_byte) {
626 ([], b'I') => return Some(Event::Focus(true)),
628 ([], b'O') => return Some(Event::Focus(false)),
629
630 (b"200", b'~') => {
632 self.in_paste = true;
633 self.paste_buffer.clear();
634 self.buffer.clear(); return None;
636 }
637 (b"201", b'~') => {
638 debug_assert!(!self.in_paste);
644 self.paste_buffer.clear();
645 return None;
646 }
647
648 _ if params.starts_with(b"<") && (final_byte == b'M' || final_byte == b'm') => {
650 return self.parse_sgr_mouse(params, final_byte);
651 }
652 _ if (self.allow_legacy_mouse || self.expect_x10_mouse) && final_byte == b'M' => {
659 if let Some(event) = self.parse_legacy_mouse(params) {
660 return Some(event);
661 }
662 }
663
664 _ => {}
665 }
666
667 match final_byte {
669 b'A' => Some(Event::Key(self.key_with_modifiers(KeyCode::Up, params))),
670 b'B' => Some(Event::Key(self.key_with_modifiers(KeyCode::Down, params))),
671 b'C' => Some(Event::Key(self.key_with_modifiers(KeyCode::Right, params))),
672 b'D' => Some(Event::Key(self.key_with_modifiers(KeyCode::Left, params))),
673 b'H' => Some(Event::Key(self.key_with_modifiers(KeyCode::Home, params))),
674 b'F' => Some(Event::Key(self.key_with_modifiers(KeyCode::End, params))),
675 b'P' => Some(Event::Key(self.key_with_modifiers(KeyCode::F(1), params))),
676 b'Q' => Some(Event::Key(self.key_with_modifiers(KeyCode::F(2), params))),
677 b'R' => Some(Event::Key(self.key_with_modifiers(KeyCode::F(3), params))),
678 b'S' => Some(Event::Key(self.key_with_modifiers(KeyCode::F(4), params))),
679 b'Z' => Some(Event::Key(
680 self.key_with_modifiers(KeyCode::BackTab, params),
681 )),
682 b'~' => self.parse_csi_tilde(params),
683 b'u' => self.parse_kitty_keyboard(params),
684 _ => None,
685 }
686 }
687
688 fn parse_csi_tilde(&self, params: &[u8]) -> Option<Event> {
690 let num = self.parse_first_param(params)?;
691 let (mods, kind) = self.parse_modifier_param(params);
692
693 let code = match num {
694 1 => KeyCode::Home,
695 2 => KeyCode::Insert,
696 3 => KeyCode::Delete,
697 4 => KeyCode::End,
698 5 => KeyCode::PageUp,
699 6 => KeyCode::PageDown,
700 15 => KeyCode::F(5),
701 17 => KeyCode::F(6),
702 18 => KeyCode::F(7),
703 19 => KeyCode::F(8),
704 20 => KeyCode::F(9),
705 21 => KeyCode::F(10),
706 23 => KeyCode::F(11),
707 24 => KeyCode::F(12),
708 _ => return None,
709 };
710
711 Some(Event::Key(
712 KeyEvent::new(code).with_modifiers(mods).with_kind(kind),
713 ))
714 }
715
716 fn parse_first_param(&self, params: &[u8]) -> Option<u32> {
718 let s = std::str::from_utf8(params).ok()?;
719 let first = s.split(';').next()?;
720 first.parse().ok()
721 }
722
723 fn parse_modifier_param(&self, params: &[u8]) -> (Modifiers, KeyEventKind) {
733 let s = match std::str::from_utf8(params) {
734 Ok(s) => s,
735 Err(_) => return (Modifiers::NONE, KeyEventKind::Press),
736 };
737
738 let mod_part = s.split(';').nth(1).unwrap_or("");
739 Self::kitty_modifiers_and_kind(mod_part)
740 }
741
742 fn parse_kitty_keyboard(&self, params: &[u8]) -> Option<Event> {
746 let s = std::str::from_utf8(params).ok()?;
747 if s.is_empty() {
748 return None;
749 }
750
751 let mut parts = s.split(';');
752 let key_part = parts.next().unwrap_or("");
753 let key_code_str = key_part.split(':').next().unwrap_or("");
754 let key_code: u32 = key_code_str.parse().ok()?;
755
756 let mod_part = parts.next().unwrap_or("");
757 let (modifiers, kind) = Self::kitty_modifiers_and_kind(mod_part);
758
759 let code = Self::kitty_keycode_to_keycode(key_code)?;
760 Some(Event::Key(
761 KeyEvent::new(code)
762 .with_modifiers(modifiers)
763 .with_kind(kind),
764 ))
765 }
766
767 fn kitty_modifiers_and_kind(mod_part: &str) -> (Modifiers, KeyEventKind) {
768 if mod_part.is_empty() {
769 return (Modifiers::NONE, KeyEventKind::Press);
770 }
771
772 let mut parts = mod_part.split(':');
773 let mod_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
774 let kind_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
775
776 let modifiers = Self::modifiers_from_xterm(mod_value);
777 let kind = match kind_value {
778 2 => KeyEventKind::Repeat,
779 3 => KeyEventKind::Release,
780 _ => KeyEventKind::Press,
781 };
782
783 (modifiers, kind)
784 }
785
786 fn kitty_keycode_to_keycode(key_code: u32) -> Option<KeyCode> {
787 match key_code {
788 9 => Some(KeyCode::Tab),
790 13 => Some(KeyCode::Enter),
791 27 => Some(KeyCode::Escape),
792 8 | 127 => Some(KeyCode::Backspace),
793 57_344 => Some(KeyCode::Escape),
795 57_345 => Some(KeyCode::Enter),
796 57_346 => Some(KeyCode::Tab),
797 57_347 => Some(KeyCode::Backspace),
798 57_348 => Some(KeyCode::Insert),
799 57_349 => Some(KeyCode::Delete),
800 57_350 => Some(KeyCode::Left),
801 57_351 => Some(KeyCode::Right),
802 57_352 => Some(KeyCode::Up),
803 57_353 => Some(KeyCode::Down),
804 57_354 => Some(KeyCode::PageUp),
805 57_355 => Some(KeyCode::PageDown),
806 57_356 => Some(KeyCode::Home),
807 57_357 => Some(KeyCode::End),
808 57_364..=57_387 => {
810 let f_num = key_code - 57_364 + 1;
813 debug_assert!(f_num <= 24, "F-key number {f_num} exceeds F24");
814 Some(KeyCode::F(f_num as u8))
815 }
816 57_358..=57_363 | 57_388..=63_743 => None,
818 _ => char::from_u32(key_code).map(KeyCode::Char),
820 }
821 }
822
823 fn modifiers_from_xterm(value: u32) -> Modifiers {
824 let bits = value.saturating_sub(1);
827 let mut mods = Modifiers::NONE;
828 if bits & 1 != 0 {
829 mods |= Modifiers::SHIFT;
830 }
831 if bits & 2 != 0 {
832 mods |= Modifiers::ALT;
833 }
834 if bits & 4 != 0 {
835 mods |= Modifiers::CTRL;
836 }
837 if bits & 8 != 0 {
838 mods |= Modifiers::SUPER;
839 }
840 mods
841 }
842
843 fn key_with_modifiers(&self, code: KeyCode, params: &[u8]) -> KeyEvent {
845 let (mods, kind) = self.parse_modifier_param(params);
846 KeyEvent::new(code).with_modifiers(mods).with_kind(kind)
847 }
848
849 fn parse_sgr_mouse(&self, params: &[u8], final_byte: u8) -> Option<Event> {
851 let params = ¶ms[1..];
854 let s = std::str::from_utf8(params).ok()?;
855 let mut parts = s.split(';');
856
857 let button_code_u32 = Self::parse_u32_prefix(parts.next()?)?;
860 let button_code = button_code_u32.min(u16::MAX as u32) as u16;
861 let x_raw = Self::parse_i32_prefix(parts.next()?)?;
862 let y_raw = Self::parse_i32_prefix(parts.next()?)?;
863
864 let (button, mods) = self.decode_mouse_button(button_code);
866
867 let kind = if final_byte == b'M' {
868 if button_code & 64 != 0 {
869 match button_code & 3 {
872 0 => MouseEventKind::ScrollUp,
873 1 => MouseEventKind::ScrollDown,
874 2 => MouseEventKind::ScrollLeft,
875 _ => MouseEventKind::ScrollRight,
876 }
877 } else if button_code & 32 != 0 {
878 if button_code & 3 == 3 {
881 MouseEventKind::Moved
882 } else {
883 MouseEventKind::Drag(button)
884 }
885 } else if (button_code & 3) == 3 {
886 MouseEventKind::Up(MouseButton::Left)
889 } else {
890 MouseEventKind::Down(button)
891 }
892 } else {
893 MouseEventKind::Up(button)
894 };
895
896 Some(Event::Mouse(MouseEvent {
897 kind,
898 x: Self::normalize_sgr_coord(x_raw),
899 y: Self::normalize_sgr_coord(y_raw),
900 modifiers: mods,
901 }))
902 }
903
904 #[inline]
905 fn parse_u32_prefix(token: &str) -> Option<u32> {
906 let bytes = token.as_bytes();
907 let digits = bytes.iter().take_while(|b| b.is_ascii_digit()).count();
908 if digits == 0 {
909 return None;
910 }
911 token[..digits].parse().ok()
912 }
913
914 #[inline]
915 fn parse_i32_prefix(token: &str) -> Option<i32> {
916 let bytes = token.as_bytes();
917 if bytes.is_empty() {
918 return None;
919 }
920 let start = if bytes[0] == b'-' || bytes[0] == b'+' {
921 1
922 } else {
923 0
924 };
925 let digits = bytes[start..]
926 .iter()
927 .take_while(|b| b.is_ascii_digit())
928 .count();
929 if digits == 0 {
930 return None;
931 }
932 token[..start + digits].parse().ok()
933 }
934
935 #[inline]
936 fn normalize_sgr_coord(raw: i32) -> u16 {
937 if raw <= 1 {
938 return 0;
939 }
940 let zero_indexed = raw - 1;
941 zero_indexed.min(i32::from(u16::MAX)) as u16
942 }
943
944 fn parse_legacy_mouse(&self, params: &[u8]) -> Option<Event> {
949 if params.is_empty() || params.starts_with(b"<") {
950 return None;
951 }
952
953 let s = std::str::from_utf8(params).ok()?;
954 let mut parts = s.split(';');
955 let button_code: u16 = parts.next()?.parse().ok()?;
956 let x: u16 = parts.next()?.parse().ok()?;
957 let y: u16 = parts.next()?.parse().ok()?;
958 if parts.next().is_some() {
960 return None;
961 }
962
963 let (button, mods) = self.decode_mouse_button(button_code);
964 let kind = if button_code & 64 != 0 {
965 match button_code & 3 {
967 0 => MouseEventKind::ScrollUp,
968 1 => MouseEventKind::ScrollDown,
969 2 => MouseEventKind::ScrollLeft,
970 _ => MouseEventKind::ScrollRight,
971 }
972 } else if button_code & 32 != 0 {
973 if button_code & 3 == 3 {
975 MouseEventKind::Moved
976 } else {
977 MouseEventKind::Drag(button)
978 }
979 } else if (button_code & 3) == 3 {
980 MouseEventKind::Up(MouseButton::Left)
982 } else {
983 MouseEventKind::Down(button)
984 };
985
986 Some(Event::Mouse(MouseEvent {
987 kind,
988 x: x.saturating_sub(1),
989 y: y.saturating_sub(1),
990 modifiers: mods,
991 }))
992 }
993
994 fn decode_mouse_button(&self, code: u16) -> (MouseButton, Modifiers) {
996 let button = match code & 0b11 {
997 0 => MouseButton::Left,
998 1 => MouseButton::Middle,
999 2 => MouseButton::Right,
1000 _ => MouseButton::Left,
1001 };
1002
1003 let mut mods = Modifiers::NONE;
1004 if code & 4 != 0 {
1005 mods |= Modifiers::SHIFT;
1006 }
1007 if code & 8 != 0 {
1008 mods |= Modifiers::ALT;
1009 }
1010 if code & 16 != 0 {
1011 mods |= Modifiers::CTRL;
1012 }
1013
1014 (button, mods)
1015 }
1016
1017 fn process_ss3(&mut self, byte: u8) -> Option<Event> {
1019 if byte == 0x1B {
1021 self.state = ParserState::Escape;
1022 return None;
1023 }
1024
1025 self.state = ParserState::Ground;
1026
1027 let code = match byte {
1028 b'P' => KeyCode::F(1),
1029 b'Q' => KeyCode::F(2),
1030 b'R' => KeyCode::F(3),
1031 b'S' => KeyCode::F(4),
1032 b'A' => KeyCode::Up,
1033 b'B' => KeyCode::Down,
1034 b'C' => KeyCode::Right,
1035 b'D' => KeyCode::Left,
1036 b'H' => KeyCode::Home,
1037 b'F' => KeyCode::End,
1038 _ => return None,
1039 };
1040
1041 Some(Event::Key(KeyEvent::new(code)))
1042 }
1043
1044 fn process_osc(&mut self, byte: u8) -> Option<Event> {
1046 if byte == 0x1B {
1048 self.state = ParserState::OscEscape;
1049 return None;
1050 }
1051
1052 self.buffer.push(byte);
1053
1054 match byte {
1055 0x07 => {
1057 self.state = ParserState::Ground;
1058 self.parse_osc_sequence()
1059 }
1060 _ => {
1062 self.state = ParserState::OscContent;
1063 None
1064 }
1065 }
1066 }
1067
1068 fn process_osc_content(&mut self, byte: u8) -> Option<Event> {
1070 if byte == 0x1B {
1072 self.state = ParserState::OscEscape;
1073 return None;
1074 }
1075
1076 if byte < 0x20 && byte != 0x07 {
1078 self.state = ParserState::Ground;
1079 self.buffer.clear();
1080 return self.process_ground(byte);
1081 }
1082
1083 match byte {
1084 0x07 => {
1089 self.state = ParserState::Ground;
1090 self.parse_osc_sequence()
1091 }
1092 _ => {
1094 if self.buffer.len() >= MAX_OSC_LEN {
1095 self.state = ParserState::OscIgnore;
1096 self.buffer.clear();
1097 return None;
1098 }
1099 self.buffer.push(byte);
1100 None
1101 }
1102 }
1103 }
1104
1105 fn process_osc_escape(&mut self, byte: u8) -> Option<Event> {
1107 if byte == b'\\' {
1108 self.state = ParserState::Ground;
1110 self.parse_osc_sequence()
1111 } else if byte == 0x1B {
1112 self.state = ParserState::Escape;
1114 self.buffer.clear();
1115 None
1116 } else {
1117 self.buffer.clear();
1123 self.state = ParserState::Escape;
1124 self.process_escape(byte)
1125 }
1126 }
1127
1128 fn process_osc_ignore(&mut self, byte: u8) -> Option<Event> {
1130 match byte {
1131 0x07 => {
1133 self.state = ParserState::Ground;
1134 None
1135 }
1136 0x1B => {
1138 self.state = ParserState::OscEscape;
1139 None
1140 }
1141 _ if byte < 0x20 => {
1143 self.state = ParserState::Ground;
1144 self.process_ground(byte)
1145 }
1146 _ => None,
1148 }
1149 }
1150
1151 fn process_dcs_ignore(&mut self, byte: u8) -> Option<Event> {
1158 match byte {
1159 0x07 => {
1161 self.state = ParserState::Ground;
1162 None
1163 }
1164 0x1B => {
1166 self.state = ParserState::DcsEscape;
1167 None
1168 }
1169 _ if byte < 0x20 => {
1172 self.state = ParserState::Ground;
1173 self.process_ground(byte)
1174 }
1175 _ => None,
1177 }
1178 }
1179
1180 fn process_dcs_escape(&mut self, byte: u8) -> Option<Event> {
1182 if byte == b'\\' {
1183 self.state = ParserState::Ground;
1185 None
1186 } else if byte == 0x1B {
1187 self.state = ParserState::Escape;
1189 None
1190 } else {
1191 self.state = ParserState::Escape;
1194 self.process_escape(byte)
1195 }
1196 }
1197
1198 fn parse_osc_sequence(&mut self) -> Option<Event> {
1200 let seq = std::mem::take(&mut self.buffer);
1201
1202 if seq.starts_with(b"52;") {
1204 return self.parse_osc52_clipboard(&seq);
1205 }
1206
1207 None
1209 }
1210
1211 fn parse_osc52_clipboard(&self, seq: &[u8]) -> Option<Event> {
1213 let content = &seq[3..]; if content.is_empty() {
1216 return None;
1217 }
1218
1219 let source = ClipboardSource::Osc52;
1222
1223 let base64_start = content.iter().position(|&b| b == b';').map(|i| i + 1)?;
1225 let base64_data = &content[base64_start..];
1226
1227 let decoded = self.decode_base64(base64_data)?;
1229
1230 Some(Event::Clipboard(ClipboardEvent::new(
1231 String::from_utf8_lossy(&decoded).into_owned(),
1232 source,
1233 )))
1234 }
1235
1236 fn decode_base64(&self, input: &[u8]) -> Option<Vec<u8>> {
1238 const DECODE_TABLE: [i8; 256] = {
1239 let mut table = [-1i8; 256];
1240 let mut i = 0u8;
1241 while i < 26 {
1242 table[(b'A' + i) as usize] = i as i8;
1243 table[(b'a' + i) as usize] = (i + 26) as i8;
1244 i += 1;
1245 }
1246 let mut i = 0u8;
1247 while i < 10 {
1248 table[(b'0' + i) as usize] = (i + 52) as i8;
1249 i += 1;
1250 }
1251 table[b'+' as usize] = 62;
1252 table[b'/' as usize] = 63;
1253 table
1254 };
1255
1256 let mut output = Vec::with_capacity(input.len() * 3 / 4);
1257 let mut buffer = 0u32;
1258 let mut bits = 0u8;
1259
1260 for &byte in input {
1261 if byte == b'=' {
1262 break;
1263 }
1264 let value = DECODE_TABLE[byte as usize];
1265 if value < 0 {
1266 continue; }
1268 buffer = (buffer << 6) | (value as u32);
1269 bits += 6;
1270 if bits >= 8 {
1271 bits -= 8;
1272 output.push((buffer >> bits) as u8);
1273 buffer &= (1 << bits) - 1;
1274 }
1275 }
1276
1277 Some(output)
1278 }
1279
1280 fn process_utf8(&mut self, byte: u8, collected: u8, expected: u8, alt: bool) -> Option<Event> {
1282 let alt_mods = if alt { Modifiers::ALT } else { Modifiers::NONE };
1283
1284 if (byte & 0xC0) != 0x80 {
1286 self.state = ParserState::Ground;
1289
1290 self.pending_event = self.process_ground(byte);
1292
1293 return Some(Event::Key(
1294 KeyEvent::new(KeyCode::Char(std::char::REPLACEMENT_CHARACTER))
1295 .with_modifiers(alt_mods),
1296 ));
1297 }
1298
1299 self.utf8_buffer[collected as usize] = byte;
1300 let new_collected = collected + 1;
1301
1302 if new_collected == expected {
1303 self.state = ParserState::Ground;
1305 match std::str::from_utf8(&self.utf8_buffer[..expected as usize]) {
1306 Ok(s) => {
1307 let c = s.chars().next()?;
1308 Some(Event::Key(
1309 KeyEvent::new(KeyCode::Char(c)).with_modifiers(alt_mods),
1310 ))
1311 }
1312 Err(_) => Some(Event::Key(
1313 KeyEvent::new(KeyCode::Char(std::char::REPLACEMENT_CHARACTER))
1314 .with_modifiers(alt_mods),
1315 )),
1316 }
1317 } else {
1318 self.state = ParserState::Utf8 {
1320 collected: new_collected,
1321 expected,
1322 alt,
1323 };
1324 None
1325 }
1326 }
1327
1328 fn process_mouse_x10(&mut self, byte: u8) -> Option<Event> {
1330 if let ParserState::MouseX10 {
1331 ref mut collected,
1332 ref mut buffer,
1333 } = self.state
1334 {
1335 buffer[*collected as usize] = byte;
1336 *collected += 1;
1337
1338 if *collected == 3 {
1339 let buf = *buffer;
1341 self.state = ParserState::Ground;
1342
1343 if buf[0] < 32 || buf[1] < 33 || buf[2] < 33 {
1347 return None;
1348 }
1349 let cb = buf[0].saturating_sub(32) as u16;
1350 let cx = buf[1].saturating_sub(33) as u16; let cy = buf[2].saturating_sub(33) as u16;
1352
1353 let (button, mods) = self.decode_mouse_button(cb);
1354
1355 let kind = if cb & 64 != 0 {
1362 match cb & 3 {
1364 0 => MouseEventKind::ScrollUp,
1365 1 => MouseEventKind::ScrollDown,
1366 2 => MouseEventKind::ScrollLeft,
1367 _ => MouseEventKind::ScrollRight,
1368 }
1369 } else if (cb & 3) == 3 {
1370 MouseEventKind::Up(MouseButton::Left)
1372 } else {
1373 MouseEventKind::Down(button)
1375 };
1376
1377 return Some(Event::Mouse(MouseEvent {
1378 kind,
1379 x: cx,
1380 y: cy,
1381 modifiers: mods,
1382 }));
1383 }
1384 }
1385 None
1386 }
1387
1388 fn process_paste_byte(&mut self, byte: u8) -> Option<Event> {
1390 const END_SEQ: &[u8] = b"\x1b[201~";
1391
1392 if self.paste_buffer.len() < MAX_PASTE_LEN {
1398 self.paste_buffer.push(byte);
1399
1400 if self.paste_buffer.ends_with(END_SEQ) {
1402 self.in_paste = false;
1403 let content_len = self.paste_buffer.len() - END_SEQ.len();
1405 let content =
1406 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
1407 self.paste_buffer.clear();
1408 return Some(Event::Paste(PasteEvent::bracketed(content)));
1409 }
1410 } else {
1411 self.buffer.push(byte);
1416 if self.buffer.len() > END_SEQ.len() {
1417 self.buffer.remove(0);
1418 }
1419
1420 let mut last_bytes = [0u8; 6];
1427 let tail_len = self.buffer.len();
1428 let paste_len = self.paste_buffer.len();
1429
1430 if tail_len + paste_len >= 6 {
1432 for i in 0..tail_len {
1434 last_bytes[6 - tail_len + i] = self.buffer[i];
1435 }
1436 let remaining = 6 - tail_len;
1438 if remaining > 0 {
1439 let start = paste_len - remaining;
1440 last_bytes[..remaining]
1441 .copy_from_slice(&self.paste_buffer[start..(remaining + start)]);
1442 }
1443
1444 if last_bytes == END_SEQ {
1445 self.in_paste = false;
1446
1447 let content_len = paste_len - remaining;
1452 let content =
1453 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
1454
1455 self.paste_buffer.clear();
1456 self.buffer.clear();
1457
1458 return Some(Event::Paste(PasteEvent::bracketed(content)));
1459 }
1460 }
1461 }
1462
1463 None
1464 }
1465}
1466
1467#[cfg(test)]
1468mod tests {
1469 use super::*;
1470
1471 #[test]
1472 fn csi_ignore_handles_final_bytes() {
1473 let mut parser = InputParser::new();
1474
1475 let mut seq = vec![0x1B, b'['];
1478 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100)); seq.push(b'@'); let events = parser.parse(&seq);
1482 assert_eq!(events.len(), 0);
1483
1484 let events = parser.parse(b"a");
1487 assert_eq!(events.len(), 1, "Subsequent char 'a' was swallowed");
1488 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1489 }
1490
1491 #[test]
1492 fn legacy_key_with_kitty_event_type_subparam_decodes_release_and_mods() {
1493 let mut parser = InputParser::new();
1496 let events = parser.parse(b"\x1b[1;1:3A");
1497 assert_eq!(events.len(), 1);
1498 assert!(matches!(
1499 events[0],
1500 Event::Key(k) if k.code == KeyCode::Up
1501 && k.modifiers == Modifiers::NONE
1502 && k.kind == KeyEventKind::Release
1503 ));
1504
1505 let events = parser.parse(b"\x1b[3;5:3~");
1507 assert_eq!(events.len(), 1);
1508 assert!(matches!(
1509 events[0],
1510 Event::Key(k) if k.code == KeyCode::Delete
1511 && k.modifiers == Modifiers::CTRL
1512 && k.kind == KeyEventKind::Release
1513 ));
1514
1515 let events = parser.parse(b"\x1b[1;5A");
1517 assert_eq!(events.len(), 1);
1518 assert!(matches!(
1519 events[0],
1520 Event::Key(k) if k.code == KeyCode::Up
1521 && k.modifiers == Modifiers::CTRL
1522 && k.kind == KeyEventKind::Press
1523 ));
1524 }
1525
1526 #[test]
1527 fn csi_final_byte_at_exact_dos_boundary_terminates() {
1528 let mut parser = InputParser::new();
1532 let mut seq = vec![0x1B, b'['];
1533 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN));
1534 seq.push(b'A'); let events = parser.parse(&seq);
1537 drop(events);
1540 let events = parser.parse(b"a");
1541 assert_eq!(events.len(), 1, "keystroke after boundary CSI swallowed");
1542 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1543 }
1544
1545 #[test]
1546 fn osc_bel_terminator_at_exact_dos_boundary_terminates() {
1547 let mut parser = InputParser::new();
1550 let mut seq = vec![0x1B, b']'];
1551 seq.extend(std::iter::repeat_n(b'x', MAX_OSC_LEN));
1552 seq.push(0x07); let events = parser.parse(&seq);
1555 drop(events);
1556 let events = parser.parse(b"a");
1557 assert_eq!(events.len(), 1, "keystroke after boundary OSC swallowed");
1558 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1559 }
1560
1561 #[test]
1562 fn stray_paste_terminator_is_ignored() {
1563 let mut parser = InputParser::new();
1565 let events = parser.parse(b"\x1b[201~");
1566 assert_eq!(events.len(), 0, "stray end-paste produced {events:?}");
1567
1568 let events = parser.parse(b"\x1b[200~hi\x1b[201~");
1570 assert_eq!(events.len(), 1);
1571 assert!(matches!(&events[0], Event::Paste(p) if p.text == "hi"));
1572 }
1573
1574 #[test]
1575 fn x10_mouse_scroll_codes_not_misread_as_button_events() {
1576 let mut parser = InputParser::new();
1579 parser.set_expect_x10_mouse(true);
1580
1581 let events = parser.parse(&[0x1B, b'[', b'M', 67 + 32, 33, 33]);
1583 assert_eq!(events.len(), 1);
1584 assert!(matches!(
1585 events[0],
1586 Event::Mouse(m) if m.kind == MouseEventKind::ScrollRight && m.x == 0 && m.y == 0
1587 ));
1588
1589 let events = parser.parse(&[0x1B, b'[', b'M', 64 + 32, 33, 33]);
1591 assert_eq!(events.len(), 1);
1592 assert!(matches!(
1593 events[0],
1594 Event::Mouse(m) if m.kind == MouseEventKind::ScrollUp
1595 ));
1596
1597 let events = parser.parse(&[0x1B, b'[', b'M', 3 + 32, 33, 33]);
1599 assert_eq!(events.len(), 1);
1600 assert!(matches!(
1601 events[0],
1602 Event::Mouse(m) if m.kind == MouseEventKind::Up(MouseButton::Left)
1603 ));
1604 }
1605
1606 #[test]
1607 fn csi_first_byte_control_char_is_reprocessed_not_swallowed() {
1608 let mut parser = InputParser::new();
1611 let events = parser.parse(b"\x1b[\r");
1612 assert_eq!(events.len(), 1);
1613 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Enter));
1614 }
1615
1616 #[test]
1617 fn alt_non_ascii_decodes_as_alt_char() {
1618 let mut parser = InputParser::new();
1620 let events = parser.parse(b"\x1b\xc3\xa9");
1621 assert_eq!(events.len(), 1, "Alt+\u{e9} dropped: {events:?}");
1622 assert!(matches!(
1623 events[0],
1624 Event::Key(k) if k.code == KeyCode::Char('\u{e9}')
1625 && k.modifiers == Modifiers::ALT
1626 ));
1627
1628 let events1 = parser.parse(b"\x1b\xc3");
1630 assert_eq!(events1.len(), 0);
1631 let events2 = parser.parse(b"\xa9");
1632 assert_eq!(events2.len(), 1);
1633 assert!(matches!(
1634 events2[0],
1635 Event::Key(k) if k.code == KeyCode::Char('\u{e9}')
1636 && k.modifiers == Modifiers::ALT
1637 ));
1638
1639 let events = parser.parse(b"\xc3\xa9");
1641 assert_eq!(events.len(), 1);
1642 assert!(matches!(
1643 events[0],
1644 Event::Key(k) if k.code == KeyCode::Char('\u{e9}')
1645 && k.modifiers == Modifiers::NONE
1646 ));
1647 }
1648
1649 #[test]
1650 fn ascii_characters_parsed() {
1651 let mut parser = InputParser::new();
1652
1653 let events = parser.parse(b"abc");
1654 assert_eq!(events.len(), 3);
1655 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1656 assert!(matches!(events[1], Event::Key(k) if k.code == KeyCode::Char('b')));
1657 assert!(matches!(events[2], Event::Key(k) if k.code == KeyCode::Char('c')));
1658 }
1659
1660 #[test]
1661 fn control_characters() {
1662 let mut parser = InputParser::new();
1663
1664 let events = parser.parse(&[0x01]);
1666 assert_eq!(events.len(), 1);
1667 assert!(matches!(
1668 events[0],
1669 Event::Key(k) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::CTRL)
1670 ));
1671
1672 let events = parser.parse(&[0x7F]);
1674 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace));
1675 }
1676
1677 #[test]
1678 fn arrow_keys() {
1679 let mut parser = InputParser::new();
1680
1681 assert!(matches!(
1682 parser.parse(b"\x1b[A").first(),
1683 Some(Event::Key(k)) if k.code == KeyCode::Up
1684 ));
1685 assert!(matches!(
1686 parser.parse(b"\x1b[B").first(),
1687 Some(Event::Key(k)) if k.code == KeyCode::Down
1688 ));
1689 assert!(matches!(
1690 parser.parse(b"\x1b[C").first(),
1691 Some(Event::Key(k)) if k.code == KeyCode::Right
1692 ));
1693 assert!(matches!(
1694 parser.parse(b"\x1b[D").first(),
1695 Some(Event::Key(k)) if k.code == KeyCode::Left
1696 ));
1697 }
1698
1699 #[test]
1700 fn c1_csi_arrow_keys() {
1701 let mut parser = InputParser::new();
1702
1703 assert!(matches!(
1704 parser.parse(&[0x9B, b'A']).first(),
1705 Some(Event::Key(k)) if k.code == KeyCode::Up
1706 ));
1707 assert!(matches!(
1708 parser.parse(&[0x9B, b'B']).first(),
1709 Some(Event::Key(k)) if k.code == KeyCode::Down
1710 ));
1711 }
1712
1713 #[test]
1714 fn c1_csi_mouse_sgr_protocol() {
1715 let mut parser = InputParser::new();
1716
1717 let events = parser.parse(&[0x9B, b'<', b'0', b';', b'1', b'0', b';', b'2', b'0', b'M']);
1718 assert!(matches!(
1719 events.first(),
1720 Some(Event::Mouse(m)) if m.x == 9 && m.y == 19
1721 ));
1722 }
1723
1724 #[test]
1725 fn function_keys_ss3() {
1726 let mut parser = InputParser::new();
1727
1728 assert!(matches!(
1729 parser.parse(b"\x1bOP").first(),
1730 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1731 ));
1732 assert!(matches!(
1733 parser.parse(b"\x1bOQ").first(),
1734 Some(Event::Key(k)) if k.code == KeyCode::F(2)
1735 ));
1736 assert!(matches!(
1737 parser.parse(b"\x1bOR").first(),
1738 Some(Event::Key(k)) if k.code == KeyCode::F(3)
1739 ));
1740 assert!(matches!(
1741 parser.parse(b"\x1bOS").first(),
1742 Some(Event::Key(k)) if k.code == KeyCode::F(4)
1743 ));
1744 }
1745
1746 #[test]
1747 fn function_keys_csi() {
1748 let mut parser = InputParser::new();
1749
1750 assert!(matches!(
1751 parser.parse(b"\x1b[15~").first(),
1752 Some(Event::Key(k)) if k.code == KeyCode::F(5)
1753 ));
1754 assert!(matches!(
1755 parser.parse(b"\x1b[17~").first(),
1756 Some(Event::Key(k)) if k.code == KeyCode::F(6)
1757 ));
1758 }
1759
1760 #[test]
1761 fn modifiers_in_csi() {
1762 let mut parser = InputParser::new();
1763
1764 let events = parser.parse(b"\x1b[1;2A");
1766 assert!(matches!(
1767 events.first(),
1768 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::SHIFT)
1769 ));
1770
1771 let events = parser.parse(b"\x1b[1;5A");
1773 assert!(matches!(
1774 events.first(),
1775 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::CTRL)
1776 ));
1777 }
1778
1779 #[test]
1780 fn modifiers_in_csi_alt_ctrl() {
1781 let mut parser = InputParser::new();
1782
1783 let events = parser.parse(b"\x1b[1;7A");
1785 assert!(matches!(
1786 events.first(),
1787 Some(Event::Key(k))
1788 if k.code == KeyCode::Up
1789 && k.modifiers.contains(Modifiers::ALT)
1790 && k.modifiers.contains(Modifiers::CTRL)
1791 ));
1792 }
1793
1794 #[test]
1795 fn kitty_keyboard_basic_char() {
1796 let mut parser = InputParser::new();
1797
1798 let events = parser.parse(b"\x1b[97u");
1799 assert!(matches!(
1800 events.first(),
1801 Some(Event::Key(k))
1802 if k.code == KeyCode::Char('a')
1803 && k.modifiers == Modifiers::NONE
1804 && k.kind == KeyEventKind::Press
1805 ));
1806 }
1807
1808 #[test]
1809 fn kitty_keyboard_with_modifiers_and_kind() {
1810 let mut parser = InputParser::new();
1811
1812 let events = parser.parse(b"\x1b[97;5:2u");
1814 assert!(matches!(
1815 events.first(),
1816 Some(Event::Key(k))
1817 if k.code == KeyCode::Char('a')
1818 && k.modifiers.contains(Modifiers::CTRL)
1819 && k.kind == KeyEventKind::Repeat
1820 ));
1821 }
1822
1823 #[test]
1824 fn kitty_keyboard_function_key() {
1825 let mut parser = InputParser::new();
1826
1827 let events = parser.parse(b"\x1b[57364;1u");
1828 assert!(matches!(
1829 events.first(),
1830 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1831 ));
1832 }
1833
1834 #[test]
1835 fn alt_key_escapes() {
1836 let mut parser = InputParser::new();
1837
1838 let events = parser.parse(b"\x1ba");
1839 assert!(matches!(
1840 events.first(),
1841 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::ALT)
1842 ));
1843 }
1844
1845 #[test]
1846 fn alt_backspace() {
1847 let mut parser = InputParser::new();
1848
1849 let events = parser.parse(b"\x1b\x7f");
1850 assert!(matches!(
1851 events.first(),
1852 Some(Event::Key(k))
1853 if k.code == KeyCode::Backspace && k.modifiers.contains(Modifiers::ALT)
1854 ));
1855 }
1856
1857 #[test]
1858 fn escape_escape_resets_state() {
1859 let mut parser = InputParser::new();
1860
1861 let events = parser.parse(b"\x1b\x1b");
1862 assert!(matches!(
1863 events.first(),
1864 Some(Event::Key(k)) if k.code == KeyCode::Escape && k.modifiers.contains(Modifiers::ALT)
1865 ));
1866
1867 let events = parser.parse(b"a");
1868 assert!(matches!(
1869 events.first(),
1870 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers == Modifiers::NONE
1871 ));
1872 }
1873
1874 #[test]
1875 fn focus_events() {
1876 let mut parser = InputParser::new();
1877
1878 assert!(matches!(
1879 parser.parse(b"\x1b[I").first(),
1880 Some(Event::Focus(true))
1881 ));
1882 assert!(matches!(
1883 parser.parse(b"\x1b[O").first(),
1884 Some(Event::Focus(false))
1885 ));
1886 }
1887
1888 #[test]
1889 fn bracketed_paste() {
1890 let mut parser = InputParser::new();
1891
1892 let events = parser.parse(b"\x1b[200~hello world\x1b[201~");
1894 assert_eq!(events.len(), 1);
1895 assert!(matches!(
1896 &events[0],
1897 Event::Paste(p) if p.text == "hello world"
1898 ));
1899 }
1900
1901 #[test]
1902 fn mouse_sgr_protocol() {
1903 let mut parser = InputParser::new();
1904
1905 let events = parser.parse(b"\x1b[<0;10;20M");
1907 assert!(matches!(
1908 events.first(),
1909 Some(Event::Mouse(m)) if m.x == 9 && m.y == 19 ));
1911 }
1912
1913 #[test]
1914 fn mouse_sgr_protocol_with_subparams() {
1915 let mut parser = InputParser::new();
1916
1917 let events = parser.parse(b"\x1b[<0:0;10:0;20:0M");
1919 assert!(matches!(
1920 events.first(),
1921 Some(Event::Mouse(m))
1922 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1923 && m.x == 9
1924 && m.y == 19
1925 ));
1926 }
1927
1928 #[test]
1929 fn mouse_sgr_protocol_large_coords_clamped() {
1930 let mut parser = InputParser::new();
1931
1932 let events = parser.parse(b"\x1b[<0;70000;80000M");
1934 assert!(matches!(
1935 events.first(),
1936 Some(Event::Mouse(m))
1937 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1938 && m.x == u16::MAX
1939 && m.y == u16::MAX
1940 ));
1941 }
1942
1943 #[test]
1944 fn mouse_sgr_protocol_negative_coords_clamped() {
1945 let mut parser = InputParser::new();
1946
1947 let events = parser.parse(b"\x1b[<0;-12;-3M");
1950 assert!(matches!(
1951 events.first(),
1952 Some(Event::Mouse(m))
1953 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1954 && m.x == 0
1955 && m.y == 0
1956 ));
1957 }
1958
1959 #[test]
1960 fn mouse_sgr_modifiers() {
1961 let mut parser = InputParser::new();
1962
1963 let events = parser.parse(b"\x1b[<28;3;4M");
1965 assert!(matches!(
1966 events.first(),
1967 Some(Event::Mouse(m))
1968 if m.modifiers.contains(Modifiers::SHIFT)
1969 && m.modifiers.contains(Modifiers::ALT)
1970 && m.modifiers.contains(Modifiers::CTRL)
1971 ));
1972 }
1973
1974 #[test]
1975 fn mouse_sgr_scroll_up() {
1976 let mut parser = InputParser::new();
1977
1978 let events = parser.parse(b"\x1b[<64;5;5M");
1980 assert!(matches!(
1981 events.first(),
1982 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollUp)
1983 ));
1984 }
1985
1986 #[test]
1987 fn mouse_sgr_scroll_down() {
1988 let mut parser = InputParser::new();
1989
1990 let events = parser.parse(b"\x1b[<65;5;5M");
1992 assert!(matches!(
1993 events.first(),
1994 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollDown)
1995 ));
1996 }
1997
1998 #[test]
1999 fn mouse_sgr_scroll_left() {
2000 let mut parser = InputParser::new();
2001
2002 let events = parser.parse(b"\x1b[<66;5;5M");
2004 assert!(matches!(
2005 events.first(),
2006 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollLeft)
2007 ));
2008 }
2009
2010 #[test]
2011 fn mouse_sgr_scroll_right() {
2012 let mut parser = InputParser::new();
2013
2014 let events = parser.parse(b"\x1b[<67;5;5M");
2016 assert!(matches!(
2017 events.first(),
2018 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollRight)
2019 ));
2020 }
2021
2022 #[test]
2023 fn mouse_sgr_drag_left() {
2024 let mut parser = InputParser::new();
2025
2026 let events = parser.parse(b"\x1b[<32;10;20M");
2028 assert!(matches!(
2029 events.first(),
2030 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Drag(MouseButton::Left))
2031 ));
2032 }
2033
2034 #[test]
2035 fn utf8_characters() {
2036 let mut parser = InputParser::new();
2037
2038 let events = parser.parse(&[0xC3, 0xA9]);
2040 assert!(matches!(
2041 events.first(),
2042 Some(Event::Key(k)) if k.code == KeyCode::Char('é')
2043 ));
2044 }
2045
2046 #[test]
2047 fn invalid_utf8_emits_replacement_then_reprocesses_byte() {
2048 let mut parser = InputParser::new();
2049
2050 let events = parser.parse(&[0xE2, 0x28]);
2052 assert_eq!(events.len(), 2);
2053 assert!(matches!(
2054 events[0],
2055 Event::Key(k) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)
2056 ));
2057 assert!(matches!(
2058 events[1],
2059 Event::Key(k) if k.code == KeyCode::Char('(')
2060 ));
2061 }
2062
2063 #[test]
2064 fn dos_protection_csi() {
2065 let mut parser = InputParser::new();
2066
2067 let mut seq = vec![0x1B, b'['];
2069 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
2070 seq.push(b'A');
2071
2072 let events = parser.parse(&seq);
2075 assert_eq!(
2076 events.len(),
2077 0,
2078 "Oversized CSI sequence should produce no events"
2079 );
2080
2081 let events = parser.parse(b"\x1b[A");
2084 assert!(matches!(
2085 events.first(),
2086 Some(Event::Key(k)) if k.code == KeyCode::Up
2087 ));
2088 }
2089
2090 #[test]
2091 fn incomplete_csi_sequence_emits_no_event() {
2092 let mut parser = InputParser::new();
2093 let events = parser.parse(b"\x1b[");
2094 assert!(events.is_empty());
2095 }
2096
2097 #[test]
2098 fn dos_protection_paste() {
2099 let mut parser = InputParser::new();
2100
2101 parser.parse(b"\x1b[200~");
2103
2104 let content = vec![b'x'; MAX_PASTE_LEN - 100]; parser.parse(&content);
2107
2108 let events = parser.parse(b"\x1b[201~");
2110
2111 assert!(matches!(
2113 events.first(),
2114 Some(Event::Paste(p)) if p.text.len() <= MAX_PASTE_LEN
2115 ));
2116 }
2117
2118 #[test]
2119 fn dos_protection_paste_overflow_terminator() {
2120 let mut parser = InputParser::new();
2121
2122 parser.parse(b"\x1b[200~");
2124
2125 let overflow = 100;
2129 let content = vec![b'a'; MAX_PASTE_LEN + overflow];
2130 parser.parse(&content);
2131
2132 let events = parser.parse(b"\x1b[201~");
2135
2136 assert_eq!(events.len(), 1, "Should emit paste event");
2137 match &events[0] {
2138 Event::Paste(p) => {
2139 assert_eq!(
2142 p.text.len(),
2143 MAX_PASTE_LEN,
2144 "Paste should be capped at MAX_PASTE_LEN bytes"
2145 );
2146 assert!(p.text.chars().all(|c| c == 'a'));
2148 }
2149 _ => unreachable!("Expected Paste event"),
2150 }
2151
2152 let events = parser.parse(b"b");
2154 assert_eq!(events.len(), 1);
2155 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('b')));
2156 }
2157
2158 #[test]
2159 fn no_panic_on_invalid_input() {
2160 let mut parser = InputParser::new();
2161
2162 let garbage = [0xFF, 0xFE, 0x00, 0x1B, 0x1B, 0x1B, b'[', 0xFF, b']', 0x00];
2164
2165 let _ = parser.parse(&garbage);
2167 }
2168
2169 #[test]
2170 fn dos_protection_paste_boundary() {
2171 let mut parser = InputParser::new();
2172 parser.parse(b"\x1b[200~");
2174
2175 let content = vec![b'x'; MAX_PASTE_LEN];
2177 parser.parse(&content);
2178
2179 let events = parser.parse(b"\x1b[201~");
2182
2183 assert!(
2184 !events.is_empty(),
2185 "Parser trapped in paste mode after hitting limit"
2186 );
2187 assert!(matches!(events[0], Event::Paste(_)));
2188 }
2189
2190 #[test]
2193 fn csi_tilde_home() {
2194 let mut parser = InputParser::new();
2195 let events = parser.parse(b"\x1b[1~");
2196 assert!(matches!(
2197 events.first(),
2198 Some(Event::Key(k)) if k.code == KeyCode::Home
2199 ));
2200 }
2201
2202 #[test]
2203 fn csi_tilde_insert() {
2204 let mut parser = InputParser::new();
2205 let events = parser.parse(b"\x1b[2~");
2206 assert!(matches!(
2207 events.first(),
2208 Some(Event::Key(k)) if k.code == KeyCode::Insert
2209 ));
2210 }
2211
2212 #[test]
2213 fn csi_tilde_delete() {
2214 let mut parser = InputParser::new();
2215 let events = parser.parse(b"\x1b[3~");
2216 assert!(matches!(
2217 events.first(),
2218 Some(Event::Key(k)) if k.code == KeyCode::Delete
2219 ));
2220 }
2221
2222 #[test]
2223 fn csi_tilde_end() {
2224 let mut parser = InputParser::new();
2225 let events = parser.parse(b"\x1b[4~");
2226 assert!(matches!(
2227 events.first(),
2228 Some(Event::Key(k)) if k.code == KeyCode::End
2229 ));
2230 }
2231
2232 #[test]
2233 fn csi_tilde_page_up() {
2234 let mut parser = InputParser::new();
2235 let events = parser.parse(b"\x1b[5~");
2236 assert!(matches!(
2237 events.first(),
2238 Some(Event::Key(k)) if k.code == KeyCode::PageUp
2239 ));
2240 }
2241
2242 #[test]
2243 fn csi_tilde_page_down() {
2244 let mut parser = InputParser::new();
2245 let events = parser.parse(b"\x1b[6~");
2246 assert!(matches!(
2247 events.first(),
2248 Some(Event::Key(k)) if k.code == KeyCode::PageDown
2249 ));
2250 }
2251
2252 #[test]
2255 fn csi_home_and_end() {
2256 let mut parser = InputParser::new();
2257 assert!(matches!(
2258 parser.parse(b"\x1b[H").first(),
2259 Some(Event::Key(k)) if k.code == KeyCode::Home
2260 ));
2261 assert!(matches!(
2262 parser.parse(b"\x1b[F").first(),
2263 Some(Event::Key(k)) if k.code == KeyCode::End
2264 ));
2265 }
2266
2267 #[test]
2270 fn ss3_home_and_end() {
2271 let mut parser = InputParser::new();
2272 assert!(matches!(
2273 parser.parse(b"\x1bOH").first(),
2274 Some(Event::Key(k)) if k.code == KeyCode::Home
2275 ));
2276 assert!(matches!(
2277 parser.parse(b"\x1bOF").first(),
2278 Some(Event::Key(k)) if k.code == KeyCode::End
2279 ));
2280 }
2281
2282 #[test]
2285 fn backtab_csi_z() {
2286 let mut parser = InputParser::new();
2287 let events = parser.parse(b"\x1b[Z");
2288 assert!(matches!(
2289 events.first(),
2290 Some(Event::Key(k)) if k.code == KeyCode::BackTab
2291 ));
2292 }
2293
2294 #[test]
2297 fn function_keys_f7_to_f12() {
2298 let mut parser = InputParser::new();
2299 assert!(matches!(
2300 parser.parse(b"\x1b[18~").first(),
2301 Some(Event::Key(k)) if k.code == KeyCode::F(7)
2302 ));
2303 assert!(matches!(
2304 parser.parse(b"\x1b[19~").first(),
2305 Some(Event::Key(k)) if k.code == KeyCode::F(8)
2306 ));
2307 assert!(matches!(
2308 parser.parse(b"\x1b[20~").first(),
2309 Some(Event::Key(k)) if k.code == KeyCode::F(9)
2310 ));
2311 assert!(matches!(
2312 parser.parse(b"\x1b[21~").first(),
2313 Some(Event::Key(k)) if k.code == KeyCode::F(10)
2314 ));
2315 assert!(matches!(
2316 parser.parse(b"\x1b[23~").first(),
2317 Some(Event::Key(k)) if k.code == KeyCode::F(11)
2318 ));
2319 assert!(matches!(
2320 parser.parse(b"\x1b[24~").first(),
2321 Some(Event::Key(k)) if k.code == KeyCode::F(12)
2322 ));
2323 }
2324
2325 #[test]
2328 fn ctrl_home_and_alt_end() {
2329 let mut parser = InputParser::new();
2330
2331 let events = parser.parse(b"\x1b[1;5H");
2333 assert!(matches!(
2334 events.first(),
2335 Some(Event::Key(k)) if k.code == KeyCode::Home && k.modifiers.contains(Modifiers::CTRL)
2336 ));
2337
2338 let events = parser.parse(b"\x1b[1;3F");
2340 assert!(matches!(
2341 events.first(),
2342 Some(Event::Key(k)) if k.code == KeyCode::End && k.modifiers.contains(Modifiers::ALT)
2343 ));
2344 }
2345
2346 #[test]
2347 fn shift_ctrl_arrow() {
2348 let mut parser = InputParser::new();
2349
2350 let events = parser.parse(b"\x1b[1;6C");
2352 assert!(matches!(
2353 events.first(),
2354 Some(Event::Key(k)) if k.code == KeyCode::Right
2355 && k.modifiers.contains(Modifiers::SHIFT)
2356 && k.modifiers.contains(Modifiers::CTRL)
2357 ));
2358 }
2359
2360 #[test]
2361 fn modifiers_on_tilde_keys() {
2362 let mut parser = InputParser::new();
2363
2364 let events = parser.parse(b"\x1b[3;5~");
2366 assert!(matches!(
2367 events.first(),
2368 Some(Event::Key(k)) if k.code == KeyCode::Delete && k.modifiers.contains(Modifiers::CTRL)
2369 ));
2370
2371 let events = parser.parse(b"\x1b[5;2~");
2373 assert!(matches!(
2374 events.first(),
2375 Some(Event::Key(k)) if k.code == KeyCode::PageUp && k.modifiers.contains(Modifiers::SHIFT)
2376 ));
2377 }
2378
2379 #[test]
2382 fn mouse_sgr_right_click() {
2383 let mut parser = InputParser::new();
2384 let events = parser.parse(b"\x1b[<2;15;10M");
2386 assert!(matches!(
2387 events.first(),
2388 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Right))
2389 && m.x == 14 && m.y == 9
2390 ));
2391 }
2392
2393 #[test]
2394 fn mouse_sgr_middle_click() {
2395 let mut parser = InputParser::new();
2396 let events = parser.parse(b"\x1b[<1;5;5M");
2398 assert!(matches!(
2399 events.first(),
2400 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Middle))
2401 ));
2402 }
2403
2404 #[test]
2405 fn mouse_sgr_button_release() {
2406 let mut parser = InputParser::new();
2407 let events = parser.parse(b"\x1b[<0;10;20m");
2409 assert!(matches!(
2410 events.first(),
2411 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Up(MouseButton::Left))
2412 ));
2413 }
2414
2415 #[test]
2416 fn mouse_sgr_button_release_uppercase_m_compat() {
2417 let mut parser = InputParser::new();
2418 let events = parser.parse(b"\x1b[<3;10;20M");
2421 assert!(matches!(
2422 events.first(),
2423 Some(Event::Mouse(m))
2424 if matches!(m.kind, MouseEventKind::Up(MouseButton::Left))
2425 && m.x == 9
2426 && m.y == 19
2427 ));
2428 }
2429
2430 #[test]
2431 fn mouse_sgr_moved() {
2432 let mut parser = InputParser::new();
2433 let events = parser.parse(b"\x1b[<35;10;20M");
2435 assert!(matches!(
2436 events.first(),
2437 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Moved)
2438 ));
2439 }
2440
2441 #[test]
2442 fn mouse_sgr_with_modifiers() {
2443 let mut parser = InputParser::new();
2444 let events = parser.parse(b"\x1b[<4;5;5M");
2446 assert!(matches!(
2447 events.first(),
2448 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2449 && m.modifiers.contains(Modifiers::SHIFT)
2450 ));
2451
2452 let events = parser.parse(b"\x1b[<16;5;5M");
2454 assert!(matches!(
2455 events.first(),
2456 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2457 && m.modifiers.contains(Modifiers::CTRL)
2458 ));
2459
2460 let events = parser.parse(b"\x1b[<8;5;5M");
2462 assert!(matches!(
2463 events.first(),
2464 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2465 && m.modifiers.contains(Modifiers::ALT)
2466 ));
2467 }
2468
2469 #[test]
2470 fn mouse_legacy_1015_when_enabled() {
2471 let mut parser = InputParser::new();
2472 parser.set_expect_x10_mouse(true);
2473
2474 let events = parser.parse(b"\x1b[0;10;20M");
2475 assert!(matches!(
2476 events.first(),
2477 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2478 && m.x == 9 && m.y == 19
2479 ));
2480 }
2481
2482 #[test]
2483 fn mouse_legacy_1015_with_fallback_enabled() {
2484 let mut parser = InputParser::new();
2485 parser.set_allow_legacy_mouse(true);
2486
2487 let events = parser.parse(b"\x1b[0;10;20M");
2488 assert!(matches!(
2489 events.first(),
2490 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2491 && m.x == 9 && m.y == 19
2492 ));
2493 }
2494
2495 #[test]
2496 fn mouse_legacy_1015_ignored_when_disabled() {
2497 let mut parser = InputParser::new();
2498 let events = parser.parse(b"\x1b[0;10;20M");
2499 assert!(
2500 events.is_empty(),
2501 "legacy mouse should require explicit opt-in"
2502 );
2503 }
2504
2505 #[test]
2506 fn mouse_x10_when_enabled() {
2507 let mut parser = InputParser::new();
2508 parser.set_expect_x10_mouse(true);
2509
2510 let events = parser.parse(&[0x1B, b'[', b'M', 32, 42, 52]);
2511 assert!(matches!(
2512 events.first(),
2513 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2514 && m.x == 9 && m.y == 19
2515 ));
2516 }
2517
2518 #[test]
2519 fn mouse_x10_malformed_packet_ignored() {
2520 let mut parser = InputParser::new();
2521 parser.set_expect_x10_mouse(true);
2522
2523 let events = parser.parse(&[0x1B, b'[', b'M', 31, 0, 10]);
2525 assert!(
2526 events.iter().all(|event| !matches!(event, Event::Mouse(_))),
2527 "malformed X10 payload must not emit mouse events"
2528 );
2529 }
2530
2531 #[test]
2534 fn kitty_keyboard_release_event() {
2535 let mut parser = InputParser::new();
2536 let events = parser.parse(b"\x1b[97;1:3u");
2538 assert!(matches!(
2539 events.first(),
2540 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.kind == KeyEventKind::Release
2541 ));
2542
2543 let events = parser.parse(b"\x1b[65;5:3u");
2545 assert!(matches!(
2546 events.first(),
2547 Some(Event::Key(k))
2548 if k.code == KeyCode::Char('A')
2549 && k.modifiers.contains(Modifiers::CTRL)
2550 && k.kind == KeyEventKind::Release
2551 ));
2552 }
2553
2554 #[test]
2555 fn kitty_keyboard_special_keys() {
2556 let mut parser = InputParser::new();
2557
2558 assert!(matches!(
2560 parser.parse(b"\x1b[57344u").first(),
2561 Some(Event::Key(k)) if k.code == KeyCode::Escape
2562 ));
2563
2564 assert!(matches!(
2566 parser.parse(b"\x1b[57345u").first(),
2567 Some(Event::Key(k)) if k.code == KeyCode::Enter
2568 ));
2569
2570 assert!(matches!(
2572 parser.parse(b"\x1b[57346u").first(),
2573 Some(Event::Key(k)) if k.code == KeyCode::Tab
2574 ));
2575
2576 assert!(matches!(
2578 parser.parse(b"\x1b[57347u").first(),
2579 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2580 ));
2581
2582 assert!(matches!(
2584 parser.parse(b"\x1b[57348u").first(),
2585 Some(Event::Key(k)) if k.code == KeyCode::Insert
2586 ));
2587
2588 assert!(matches!(
2590 parser.parse(b"\x1b[57349u").first(),
2591 Some(Event::Key(k)) if k.code == KeyCode::Delete
2592 ));
2593 }
2594
2595 #[test]
2596 fn kitty_keyboard_navigation_keys() {
2597 let mut parser = InputParser::new();
2598
2599 assert!(matches!(
2601 parser.parse(b"\x1b[57350u").first(),
2602 Some(Event::Key(k)) if k.code == KeyCode::Left
2603 ));
2604 assert!(matches!(
2606 parser.parse(b"\x1b[57351u").first(),
2607 Some(Event::Key(k)) if k.code == KeyCode::Right
2608 ));
2609 assert!(matches!(
2611 parser.parse(b"\x1b[57352u").first(),
2612 Some(Event::Key(k)) if k.code == KeyCode::Up
2613 ));
2614 assert!(matches!(
2616 parser.parse(b"\x1b[57353u").first(),
2617 Some(Event::Key(k)) if k.code == KeyCode::Down
2618 ));
2619 assert!(matches!(
2621 parser.parse(b"\x1b[57354u").first(),
2622 Some(Event::Key(k)) if k.code == KeyCode::PageUp
2623 ));
2624 assert!(matches!(
2626 parser.parse(b"\x1b[57355u").first(),
2627 Some(Event::Key(k)) if k.code == KeyCode::PageDown
2628 ));
2629 assert!(matches!(
2631 parser.parse(b"\x1b[57356u").first(),
2632 Some(Event::Key(k)) if k.code == KeyCode::Home
2633 ));
2634 assert!(matches!(
2636 parser.parse(b"\x1b[57357u").first(),
2637 Some(Event::Key(k)) if k.code == KeyCode::End
2638 ));
2639 }
2640
2641 #[test]
2642 fn kitty_keyboard_f_keys() {
2643 let mut parser = InputParser::new();
2644 assert!(matches!(
2646 parser.parse(b"\x1b[57364u").first(),
2647 Some(Event::Key(k)) if k.code == KeyCode::F(1)
2648 ));
2649 assert!(matches!(
2651 parser.parse(b"\x1b[57375u").first(),
2652 Some(Event::Key(k)) if k.code == KeyCode::F(12)
2653 ));
2654 assert!(matches!(
2656 parser.parse(b"\x1b[57387u").first(),
2657 Some(Event::Key(k)) if k.code == KeyCode::F(24)
2658 ));
2659 }
2660
2661 #[test]
2662 fn kitty_keyboard_ascii_as_standard() {
2663 let mut parser = InputParser::new();
2664 assert!(matches!(
2666 parser.parse(b"\x1b[9u").first(),
2667 Some(Event::Key(k)) if k.code == KeyCode::Tab
2668 ));
2669 assert!(matches!(
2670 parser.parse(b"\x1b[13u").first(),
2671 Some(Event::Key(k)) if k.code == KeyCode::Enter
2672 ));
2673 assert!(matches!(
2674 parser.parse(b"\x1b[27u").first(),
2675 Some(Event::Key(k)) if k.code == KeyCode::Escape
2676 ));
2677 assert!(matches!(
2678 parser.parse(b"\x1b[127u").first(),
2679 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2680 ));
2681 assert!(matches!(
2683 parser.parse(b"\x1b[8u").first(),
2684 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2685 ));
2686 }
2687
2688 #[test]
2691 fn osc52_clipboard_bel_terminated() {
2692 let mut parser = InputParser::new();
2693 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x07");
2696 assert!(matches!(
2697 events.first(),
2698 Some(Event::Clipboard(c)) if c.content == "hello" && c.source == ClipboardSource::Osc52
2699 ));
2700 }
2701
2702 #[test]
2703 fn osc52_clipboard_st_terminated() {
2704 let mut parser = InputParser::new();
2705 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x1b\\");
2707 assert!(matches!(
2708 events.first(),
2709 Some(Event::Clipboard(c)) if c.content == "hello"
2710 ));
2711 }
2712
2713 #[test]
2716 fn dcs_xtgettcap_reply_produces_no_events() {
2717 let mut parser = InputParser::new();
2718 let events = parser.parse(b"\x1bP1+r524742=8/8/8\x1b\\");
2722 assert!(
2723 events.is_empty(),
2724 "a DCS reply must produce no events, got: {events:?}"
2725 );
2726 }
2727
2728 #[test]
2729 fn dcs_then_real_key_recovers_to_ground() {
2730 let mut parser = InputParser::new();
2731 let events = parser.parse(b"\x1bP1+r524742=8/8/8\x1b\\a");
2734 assert!(
2735 matches!(events.as_slice(), [Event::Key(k)] if k.code == KeyCode::Char('a')),
2736 "parser must recover to Ground after a DCS, got: {events:?}"
2737 );
2738 }
2739
2740 #[test]
2741 fn dcs_bel_terminated_is_ignored() {
2742 let mut parser = InputParser::new();
2743 let events = parser.parse(b"\x1bPsome-payload\x07b");
2744 assert!(
2745 matches!(events.as_slice(), [Event::Key(k)] if k.code == KeyCode::Char('b')),
2746 "BEL-terminated DCS ignored, then key parses, got: {events:?}"
2747 );
2748 }
2749
2750 #[test]
2751 fn sos_pm_apc_introducers_stay_alt_keys() {
2752 for &introducer in b"X^_" {
2756 let mut parser = InputParser::new();
2757 let events = parser.parse(&[0x1b, introducer]);
2758 assert!(
2759 matches!(
2760 events.as_slice(),
2761 [Event::Key(k)]
2762 if k.code == KeyCode::Char(introducer as char)
2763 && k.modifiers.contains(Modifiers::ALT)
2764 ),
2765 "ESC {} must stay an Alt key, got: {events:?}",
2766 introducer as char
2767 );
2768 }
2769 }
2770
2771 #[test]
2772 fn dcs_esc_then_csi_recovers_to_arrow_key() {
2773 let mut parser = InputParser::new();
2774 let events = parser.parse(b"\x1bP payload \x1b[A");
2777 assert!(
2778 matches!(events.as_slice(), [Event::Key(k)] if k.code == KeyCode::Up),
2779 "ESC-mid-DCS then a CSI must recover and parse the arrow, got: {events:?}"
2780 );
2781 }
2782
2783 #[test]
2784 fn dcs_aborts_on_control_char_so_input_is_not_swallowed() {
2785 let mut parser = InputParser::new();
2786 let events = parser.parse(b"\x1bPunterminated\r");
2790 assert!(
2791 !events.is_empty(),
2792 "a control char must abort a stuck DCS and emit the key, got: {events:?}"
2793 );
2794 }
2795
2796 #[test]
2797 fn osc52_clipboard_primary_selection() {
2798 let mut parser = InputParser::new();
2799 let events = parser.parse(b"\x1b]52;p;YWJj\x07");
2802 assert!(matches!(
2803 events.first(),
2804 Some(Event::Clipboard(c)) if c.content == "abc"
2805 ));
2806 }
2807
2808 #[test]
2811 fn ctrl_space_is_null() {
2812 let mut parser = InputParser::new();
2813 let events = parser.parse(&[0x00]);
2814 assert!(matches!(
2815 events.first(),
2816 Some(Event::Key(k)) if k.code == KeyCode::Null
2817 ));
2818 }
2819
2820 #[test]
2821 fn all_ctrl_letter_keys() {
2822 let mut parser = InputParser::new();
2823 for byte in 0x01..=0x1Au8 {
2825 let events = parser.parse(&[byte]);
2826 assert_eq!(
2827 events.len(),
2828 1,
2829 "Ctrl+{} should produce one event",
2830 (byte + b'a' - 1) as char
2831 );
2832 match byte {
2833 0x08 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace)),
2834 0x09 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Tab)),
2835 0x0D => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Enter)),
2836 _ => {
2837 let expected_char = (byte + b'a' - 1) as char;
2838 match &events[0] {
2839 Event::Key(k) => {
2840 assert_eq!(
2841 k.code,
2842 KeyCode::Char(expected_char),
2843 "Byte 0x{byte:02X} should produce Ctrl+{expected_char}"
2844 );
2845 assert!(
2846 k.modifiers.contains(Modifiers::CTRL),
2847 "Byte 0x{byte:02X} should have Ctrl modifier"
2848 );
2849 }
2850 other => {
2851 panic!("Byte 0x{byte:02X}: expected Key event, got {other:?}");
2852 }
2853 }
2854 }
2855 }
2856 }
2857 }
2858
2859 #[test]
2862 fn utf8_3byte_cjk() {
2863 let mut parser = InputParser::new();
2864 let events = parser.parse(&[0xE4, 0xB8, 0xAD]);
2866 assert!(matches!(
2867 events.first(),
2868 Some(Event::Key(k)) if k.code == KeyCode::Char('中')
2869 ));
2870 }
2871
2872 #[test]
2873 fn utf8_4byte_emoji() {
2874 let mut parser = InputParser::new();
2875 let events = parser.parse(&[0xF0, 0x9F, 0xA6, 0x80]);
2877 assert!(matches!(
2878 events.first(),
2879 Some(Event::Key(k)) if k.code == KeyCode::Char('🦀')
2880 ));
2881 }
2882
2883 #[test]
2886 fn empty_input_returns_no_events() {
2887 let mut parser = InputParser::new();
2888 let events = parser.parse(b"");
2889 assert!(events.is_empty());
2890 }
2891
2892 #[test]
2895 fn unknown_csi_tilde_ignored() {
2896 let mut parser = InputParser::new();
2897 let events = parser.parse(b"\x1b[99~");
2899 assert!(events.is_empty());
2900
2901 let events = parser.parse(b"a");
2903 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('a')));
2904 }
2905
2906 #[test]
2909 fn alt_special_chars() {
2910 let mut parser = InputParser::new();
2911
2912 let events = parser.parse(b"\x1b ");
2914 assert!(matches!(
2915 events.first(),
2916 Some(Event::Key(k)) if k.code == KeyCode::Char(' ') && k.modifiers.contains(Modifiers::ALT)
2917 ));
2918
2919 let events = parser.parse(b"\x1b5");
2921 assert!(matches!(
2922 events.first(),
2923 Some(Event::Key(k)) if k.code == KeyCode::Char('5') && k.modifiers.contains(Modifiers::ALT)
2924 ));
2925
2926 let events = parser.parse(b"\x1b}");
2928 assert!(matches!(
2929 events.first(),
2930 Some(Event::Key(k)) if k.code == KeyCode::Char('}') && k.modifiers.contains(Modifiers::ALT)
2931 ));
2932 }
2933
2934 #[test]
2935 fn alt_ctrl_key_combinations() {
2936 let mut parser = InputParser::new();
2937
2938 let events = parser.parse(&[0x1B, 0x01]);
2940 assert_eq!(events.len(), 1);
2941 match &events[0] {
2942 Event::Key(k) => {
2943 assert_eq!(k.code, KeyCode::Char('a'));
2944 assert!(k.modifiers.contains(Modifiers::ALT));
2945 assert!(k.modifiers.contains(Modifiers::CTRL));
2946 }
2947 _ => panic!("Expected Key event"),
2948 }
2949
2950 let events = parser.parse(&[0x1B, 0x08]);
2954 assert_eq!(events.len(), 1);
2955 match &events[0] {
2956 Event::Key(k) => {
2957 assert_eq!(k.code, KeyCode::Backspace);
2958 assert!(k.modifiers.contains(Modifiers::ALT));
2959 }
2960 _ => panic!("Expected Key event"),
2961 }
2962 }
2963
2964 #[test]
2967 fn ss3_arrow_keys() {
2968 let mut parser = InputParser::new();
2969 assert!(matches!(
2970 parser.parse(b"\x1bOA").first(),
2971 Some(Event::Key(k)) if k.code == KeyCode::Up
2972 ));
2973 assert!(matches!(
2974 parser.parse(b"\x1bOB").first(),
2975 Some(Event::Key(k)) if k.code == KeyCode::Down
2976 ));
2977 assert!(matches!(
2978 parser.parse(b"\x1bOC").first(),
2979 Some(Event::Key(k)) if k.code == KeyCode::Right
2980 ));
2981 assert!(matches!(
2982 parser.parse(b"\x1bOD").first(),
2983 Some(Event::Key(k)) if k.code == KeyCode::Left
2984 ));
2985 }
2986
2987 #[test]
2990 fn xterm_modifier_encoding() {
2991 assert_eq!(InputParser::modifiers_from_xterm(1), Modifiers::NONE);
2993 assert_eq!(InputParser::modifiers_from_xterm(2), Modifiers::SHIFT);
2994 assert_eq!(InputParser::modifiers_from_xterm(3), Modifiers::ALT);
2995 assert_eq!(
2996 InputParser::modifiers_from_xterm(4),
2997 Modifiers::SHIFT | Modifiers::ALT
2998 );
2999 assert_eq!(InputParser::modifiers_from_xterm(5), Modifiers::CTRL);
3000 assert_eq!(
3001 InputParser::modifiers_from_xterm(6),
3002 Modifiers::SHIFT | Modifiers::CTRL
3003 );
3004 assert_eq!(InputParser::modifiers_from_xterm(9), Modifiers::SUPER);
3005 }
3006
3007 #[test]
3010 fn ss3_interrupted_by_esc() {
3011 let mut parser = InputParser::new();
3012 let events = parser.parse(b"\x1bO\x1b[A");
3014 assert!(matches!(
3016 events.first(),
3017 Some(Event::Key(k)) if k.code == KeyCode::Up
3018 ));
3019 }
3020
3021 #[test]
3024 fn kitty_keyboard_reserved_keycode_ignored() {
3025 let mut parser = InputParser::new();
3026 let events = parser.parse(b"\x1b[57360u");
3028 assert!(events.is_empty());
3029
3030 let events = parser.parse(b"x");
3032 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('x')));
3033 }
3034 #[test]
3035 fn utf8_invalid_sequence_emits_replacement() {
3036 let mut parser = InputParser::new();
3037
3038 let events = parser.parse(&[0xE0, 0x41]);
3042 assert_eq!(events.len(), 2);
3043
3044 match &events[0] {
3045 Event::Key(k) => assert_eq!(k.code, KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
3046 _ => panic!("Expected replacement character"),
3047 }
3048
3049 match &events[1] {
3050 Event::Key(k) => assert_eq!(k.code, KeyCode::Char('A')),
3051 _ => panic!("Expected character 'A'"),
3052 }
3053 }
3054
3055 #[test]
3056 fn utf8_invalid_lead_emits_replacement() {
3057 let mut parser = InputParser::new();
3058
3059 let events = parser.parse(&[0xC0, b'a']);
3061 assert!(
3062 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
3063 "Expected replacement for invalid lead"
3064 );
3065 assert!(
3066 events
3067 .iter()
3068 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('a'))),
3069 "Expected subsequent ASCII to be preserved"
3070 );
3071
3072 let events = parser.parse(&[0xF5, b'b']);
3074 assert!(
3075 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
3076 "Expected replacement for out-of-range lead"
3077 );
3078 assert!(
3079 events
3080 .iter()
3081 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('b'))),
3082 "Expected subsequent ASCII to be preserved"
3083 );
3084 }
3085}
3086
3087#[cfg(test)]
3088mod proptest_fuzz {
3089 use super::*;
3090 use proptest::prelude::*;
3091
3092 fn arb_byte() -> impl Strategy<Value = u8> {
3096 any::<u8>()
3097 }
3098
3099 fn arb_byte_vec(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
3100 prop::collection::vec(arb_byte(), 0..=max_len)
3101 }
3102
3103 fn csi_sequence() -> impl Strategy<Value = Vec<u8>> {
3105 let params = prop::collection::vec(0x30u8..=0x3F, 0..=20);
3106 let final_byte = 0x40u8..=0x7E;
3107 (params, final_byte).prop_map(|(p, f)| {
3108 let mut buf = vec![0x1B, b'['];
3109 buf.extend_from_slice(&p);
3110 buf.push(f);
3111 buf
3112 })
3113 }
3114
3115 fn osc_sequence() -> impl Strategy<Value = Vec<u8>> {
3117 let content = prop::collection::vec(0x20u8..=0x7E, 0..=64);
3118 let terminator = prop_oneof![
3119 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
3122 (content, terminator).prop_map(|(c, t)| {
3123 let mut buf = vec![0x1B, b']'];
3124 buf.extend_from_slice(&c);
3125 buf.extend_from_slice(&t);
3126 buf
3127 })
3128 }
3129
3130 fn ss3_sequence() -> impl Strategy<Value = Vec<u8>> {
3132 (0x40u8..=0x7E).prop_map(|f| vec![0x1B, b'O', f])
3133 }
3134
3135 fn paste_sequence() -> impl Strategy<Value = Vec<u8>> {
3137 prop::collection::vec(0x20u8..=0x7E, 0..=128).prop_map(|content| {
3138 let mut buf = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
3139 buf.extend_from_slice(&content);
3140 buf.extend_from_slice(b"\x1b[201~");
3141 buf
3142 })
3143 }
3144
3145 fn mixed_adversarial() -> impl Strategy<Value = Vec<u8>> {
3147 let fragment = prop_oneof![
3148 csi_sequence(),
3149 osc_sequence(),
3150 ss3_sequence(),
3151 paste_sequence(),
3152 arb_byte_vec(16), Just(vec![0x1B]), Just(vec![0x1B, b'[']), Just(vec![0x1B, b']']), prop::collection::vec(0x80u8..=0xFF, 1..=4), ];
3158 prop::collection::vec(fragment, 1..=8)
3159 .prop_map(|frags| frags.into_iter().flatten().collect())
3160 }
3161
3162 proptest! {
3165 #[test]
3167 fn random_bytes_never_panic(input in arb_byte_vec(512)) {
3168 let mut parser = InputParser::new();
3169 let _ = parser.parse(&input);
3170 }
3171
3172 #[test]
3174 fn parser_recovers_after_garbage(input in arb_byte_vec(256)) {
3175 let mut parser = InputParser::new();
3176 let _ = parser.parse(&input);
3177
3178 let events = parser.parse(b"z");
3180 let _ = events;
3183 }
3184
3185 #[test]
3187 fn mixed_sequences_never_panic(input in mixed_adversarial()) {
3188 let mut parser = InputParser::new();
3189 let _ = parser.parse(&input);
3190 }
3191
3192 #[test]
3194 fn events_are_well_formed(input in arb_byte_vec(256)) {
3195 let mut parser = InputParser::new();
3196 let events = parser.parse(&input);
3197 for event in &events {
3198 let _ = format!("{event:?}");
3200 }
3201 }
3202
3203 #[test]
3205 fn csi_event_count_bounded(seq in csi_sequence()) {
3206 let mut parser = InputParser::new();
3207 let events = parser.parse(&seq);
3208 prop_assert!(events.len() <= seq.len(),
3209 "Got {} events from {} bytes", events.len(), seq.len());
3210 }
3211
3212 #[test]
3214 fn osc_event_count_bounded(seq in osc_sequence()) {
3215 let mut parser = InputParser::new();
3216 let events = parser.parse(&seq);
3217 prop_assert!(events.len() <= seq.len(),
3218 "Got {} events from {} bytes", events.len(), seq.len());
3219 }
3220
3221 #[test]
3223 fn paste_content_bounded(content in prop::collection::vec(arb_byte(), 0..=2048)) {
3224 let mut parser = InputParser::new();
3225 let mut input = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
3226 input.extend_from_slice(&content);
3227 input.extend_from_slice(b"\x1b[201~");
3228
3229 let events = parser.parse(&input);
3230 for event in &events {
3231 if let Event::Paste(p) = event {
3232 prop_assert!(p.text.len() <= MAX_PASTE_LEN,
3233 "Paste text {} exceeds limit {}", p.text.len(), MAX_PASTE_LEN);
3234 }
3235 }
3236 }
3237
3238 #[test]
3240 fn incremental_matches_bulk(input in arb_byte_vec(128)) {
3241 let mut bulk_parser = InputParser::new();
3242 let bulk_events = bulk_parser.parse(&input);
3243
3244 let mut incr_parser = InputParser::new();
3245 let mut incr_events = Vec::new();
3246 for byte in &input {
3247 incr_events.extend(incr_parser.parse(std::slice::from_ref(byte)));
3248 }
3249
3250 let bulk_dbg: Vec<String> = bulk_events.iter().map(|e| format!("{e:?}")).collect();
3251 let incr_dbg: Vec<String> = incr_events.iter().map(|e| format!("{e:?}")).collect();
3252 prop_assert_eq!(bulk_dbg, incr_dbg,
3253 "Bulk vs incremental mismatch for input {:?}", input);
3254 }
3255
3256 #[test]
3259 fn deterministic_output(input in arb_byte_vec(128)) {
3260 let mut parser1 = InputParser::new();
3261 let events1 = parser1.parse(&input);
3262
3263 let mut parser2 = InputParser::new();
3264 let events2 = parser2.parse(&input);
3265
3266 let dbg1: Vec<String> = events1.iter().map(|e| format!("{e:?}")).collect();
3267 let dbg2: Vec<String> = events2.iter().map(|e| format!("{e:?}")).collect();
3268 prop_assert_eq!(dbg1, dbg2);
3269 }
3270 }
3271
3272 #[test]
3276 fn no_quadratic_blowup() {
3277 let mut parser = InputParser::new();
3278
3279 let garbage: Vec<u8> = (0..65536).map(|i| (i % 256) as u8).collect();
3281 let _ = parser.parse(&garbage);
3282
3283 let events = parser.parse(b"a");
3285 let _ = events; }
3287
3288 #[test]
3290 fn oversized_csi_transitions_to_ignore() {
3291 let mut parser = InputParser::new();
3292
3293 let mut input = vec![0x1B, b'['];
3295 input.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
3296 input.push(b'm');
3297
3298 let _ = parser.parse(&input);
3299
3300 let events = parser.parse(b"x");
3302 assert_eq!(events.len(), 1);
3303 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('x')));
3304 }
3305
3306 #[test]
3308 fn oversized_osc_transitions_to_ignore() {
3309 let mut parser = InputParser::new();
3310
3311 let mut input = vec![0x1B, b']'];
3313 input.extend(std::iter::repeat_n(b'a', MAX_OSC_LEN + 100));
3314 input.push(0x07); let _ = parser.parse(&input);
3317
3318 let events = parser.parse(b"y");
3320 assert_eq!(events.len(), 1);
3321 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('y')));
3322 }
3323
3324 #[test]
3326 fn rapid_esc_toggle() {
3327 let mut parser = InputParser::new();
3328
3329 let input: Vec<u8> = vec![0x1B; 1000];
3331 let _ = parser.parse(&input);
3332
3333 let events = parser.parse(b"k");
3335 assert!(!events.is_empty());
3336 }
3337
3338 #[test]
3340 fn unterminated_paste_recovery() {
3341 let mut parser = InputParser::new();
3342
3343 let mut input = b"\x1b[200~".to_vec();
3345 input.extend(std::iter::repeat_n(b'x', 2048));
3346
3347 let _ = parser.parse(&input);
3348
3349 let events = parser.parse(b"\x1b[201~");
3351 assert!(
3352 !events.is_empty(),
3353 "Parser should emit paste event on terminator"
3354 );
3355 }
3356
3357 #[test]
3359 fn truncated_utf8_lead_bytes() {
3360 let mut parser = InputParser::new();
3361
3362 for lead in [0xC2, 0xE0, 0xF0] {
3364 let _ = parser.parse(&[lead]);
3365 let events = parser.parse(b"a");
3367 let _ = events;
3369 }
3370 }
3371
3372 #[test]
3374 fn null_bytes_interleaved() {
3375 let mut parser = InputParser::new();
3376
3377 let input = b"\x00A\x00\x1b[A\x00B\x00";
3378 let events = parser.parse(input);
3379 assert!(
3381 events.len() >= 2,
3382 "Expected at least 2 events, got {}",
3383 events.len()
3384 );
3385 }
3386
3387 fn osc52_sequence() -> impl Strategy<Value = Vec<u8>> {
3391 let selector = prop_oneof![Just(b'c'), Just(b'p'), Just(b's')];
3392 let payload = prop::collection::vec(
3394 prop_oneof![
3395 0x41u8..=0x5A, 0x61u8..=0x7A, 0x30u8..=0x39, Just(b'+'),
3399 Just(b'/'),
3400 Just(b'='),
3401 ],
3402 0..=128,
3403 );
3404 let terminator = prop_oneof![
3405 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
3408 (selector, payload, terminator).prop_map(|(sel, pay, term)| {
3409 let mut buf = vec![0x1B, b']', b'5', b'2', b';', sel, b';'];
3410 buf.extend_from_slice(&pay);
3411 buf.extend_from_slice(&term);
3412 buf
3413 })
3414 }
3415
3416 fn sgr_mouse_sequence() -> impl Strategy<Value = Vec<u8>> {
3418 let button_code = 0u16..128;
3419 let x = 1u16..300;
3420 let y = 1u16..100;
3421 let final_byte = prop_oneof![Just(b'M'), Just(b'm')];
3422 (button_code, x, y, final_byte)
3423 .prop_map(|(btn, x, y, fb)| format!("\x1b[<{btn};{x};{y}{}", fb as char).into_bytes())
3424 }
3425
3426 fn kitty_keyboard_sequence() -> impl Strategy<Value = Vec<u8>> {
3428 let keycode = prop_oneof![
3429 0x20u32..0x7F, 0x57344u32..0x57400, 0x100u32..0x200, ];
3433 let modifier = 1u32..16;
3434 let kind = prop_oneof![Just(1u32), Just(2u32), Just(3u32)]; (keycode, prop::option::of(modifier), prop::option::of(kind)).prop_map(
3436 |(kc, mods, kind)| match (mods, kind) {
3437 (Some(m), Some(k)) => format!("\x1b[{kc};{m}:{k}u").into_bytes(),
3438 (Some(m), None) => format!("\x1b[{kc};{m}u").into_bytes(),
3439 _ => format!("\x1b[{kc}u").into_bytes(),
3440 },
3441 )
3442 }
3443
3444 proptest! {
3445 #[test]
3449 fn osc52_never_panics(seq in osc52_sequence()) {
3450 let mut parser = InputParser::new();
3451 let events = parser.parse(&seq);
3452 for event in &events {
3454 if let Event::Clipboard(c) = event {
3455 prop_assert!(!c.content.is_empty() || c.content.is_empty(),
3456 "Clipboard event must have a content field");
3457 }
3458 }
3459 }
3460
3461 #[test]
3463 fn osc52_corrupt_base64_safe(payload in arb_byte_vec(128)) {
3464 let mut parser = InputParser::new();
3465 let mut input = b"\x1b]52;c;".to_vec();
3466 input.extend_from_slice(&payload);
3467 input.push(0x07); let _ = parser.parse(&input);
3469 }
3470
3471 #[test]
3475 fn sgr_mouse_never_panics(seq in sgr_mouse_sequence()) {
3476 let mut parser = InputParser::new();
3477 let events = parser.parse(&seq);
3478 for event in &events {
3479 let _ = format!("{event:?}");
3481 }
3482 }
3483
3484 #[test]
3486 fn sgr_mouse_extreme_coords(
3487 btn in 0u16..128,
3488 x in 0u16..=65535,
3489 y in 0u16..=65535,
3490 ) {
3491 let mut parser = InputParser::new();
3492 let input = format!("\x1b[<{btn};{x};{y}M").into_bytes();
3493 let events = parser.parse(&input);
3494 for event in &events {
3495 if let Event::Mouse(m) = event {
3496 prop_assert!(m.x <= x, "Mouse x {} > input x {}", m.x, x);
3497 prop_assert!(m.y <= y, "Mouse y {} > input y {}", m.y, y);
3498 }
3499 }
3500 }
3501
3502 #[test]
3506 fn kitty_keyboard_never_panics(seq in kitty_keyboard_sequence()) {
3507 let mut parser = InputParser::new();
3508 let _ = parser.parse(&seq);
3509 }
3510
3511 #[test]
3515 fn truncated_csi_then_valid(
3516 params in prop::collection::vec(0x30u8..=0x3F, 1..=10),
3517 valid_char in 0x20u8..0x7F,
3518 ) {
3519 let mut parser = InputParser::new();
3520
3521 let mut partial = vec![0x1B, b'['];
3523 partial.extend_from_slice(¶ms);
3524 let _ = parser.parse(&partial);
3525
3526 let events = parser.parse(&[0x1B, b'[', b'A']); let _ = events;
3531
3532 let events = parser.parse(&[valid_char]);
3534 let _ = events;
3535 }
3536
3537 #[test]
3539 fn truncated_osc_then_valid(
3540 content in prop::collection::vec(0x20u8..=0x7E, 1..=32),
3541 ) {
3542 let mut parser = InputParser::new();
3543
3544 let mut partial = vec![0x1B, b']'];
3546 partial.extend_from_slice(&content);
3547 let _ = parser.parse(&partial);
3548
3549 let events = parser.parse(b"\x1bz");
3551 let _ = events;
3552 }
3553
3554 #[test]
3558 fn csi_near_limit_produces_event(
3559 fill_byte in 0x30u8..=0x39, ) {
3561 let mut parser = InputParser::new();
3562
3563 let mut input = vec![0x1B, b'['];
3564 input.extend(std::iter::repeat_n(fill_byte, MAX_CSI_LEN - 1));
3566 input.push(b'm'); let events = parser.parse(&input);
3569 let _ = events;
3572
3573 let events = parser.parse(b"a");
3575 prop_assert!(!events.is_empty(), "Parser stuck after near-limit CSI");
3576 }
3577
3578 #[test]
3580 fn osc_near_limit_processes(
3581 fill_byte in 0x20u8..=0x7E,
3582 ) {
3583 let mut parser = InputParser::new();
3584
3585 let mut input = vec![0x1B, b']'];
3586 input.extend(std::iter::repeat_n(fill_byte, MAX_OSC_LEN - 1));
3587 input.push(0x07); let _ = parser.parse(&input);
3590
3591 let events = parser.parse(b"b");
3593 prop_assert!(!events.is_empty(), "Parser stuck after near-limit OSC");
3594 }
3595
3596 #[test]
3600 fn consecutive_pastes_emit_events(count in 2usize..=5) {
3601 let mut parser = InputParser::new();
3602 let mut input = Vec::new();
3603
3604 for i in 0..count {
3605 input.extend_from_slice(b"\x1b[200~");
3606 input.extend_from_slice(format!("paste_{i}").as_bytes());
3607 input.extend_from_slice(b"\x1b[201~");
3608 }
3609
3610 let events = parser.parse(&input);
3611 let paste_events: Vec<_> = events.iter()
3612 .filter(|e| matches!(e, Event::Paste(_)))
3613 .collect();
3614
3615 prop_assert_eq!(paste_events.len(), count,
3616 "Expected {} paste events, got {}", count, paste_events.len());
3617 }
3618
3619 #[test]
3621 fn paste_with_invalid_utf8(content in arb_byte_vec(256)) {
3622 let mut parser = InputParser::new();
3623 let mut input = b"\x1b[200~".to_vec();
3624 input.extend_from_slice(&content);
3625 input.extend_from_slice(b"\x1b[201~");
3626
3627 let events = parser.parse(&input);
3628 for event in &events {
3629 if let Event::Paste(p) = event {
3630 prop_assert!(p.text.is_char_boundary(0), "Paste text is not valid UTF-8");
3632 }
3633 }
3634 }
3635
3636 #[test]
3640 fn recovery_via_esc_reset(garbage in arb_byte_vec(256)) {
3641 let mut parser = InputParser::new();
3642 let _ = parser.parse(&garbage);
3643
3644 let _ = parser.parse(b"\x07\x1b\\\x1b");
3647 let _ = parser.parse(b"\x1b");
3648
3649 let _ = parser.parse(b"z");
3651
3652 let events = parser.parse(b"q");
3654 prop_assert!(!events.is_empty(),
3657 "Parser did not recover after garbage + reset");
3658 }
3659 }
3660}