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 mut key_codes = key_part.split(':');
754 let key_code: u32 = key_codes.next().unwrap_or("").parse().ok()?;
755 let shifted = key_codes
757 .next()
758 .and_then(|field| field.parse().ok())
759 .and_then(char::from_u32);
760
761 let mod_part = parts.next().unwrap_or("");
762 let (modifiers, kind) = Self::kitty_modifiers_and_kind(mod_part);
763
764 let mut code = Self::kitty_keycode_to_keycode(key_code)?;
765 if let KeyCode::Char(base) = code
766 && !(57_344..=63_743).contains(&key_code)
767 {
768 let caps_lock = mod_part
769 .split(':')
770 .next()
771 .and_then(|value| value.parse::<u32>().ok())
772 .is_some_and(|value| value.saturating_sub(1) & 64 != 0);
773 code = KeyCode::Char(Self::kitty_typed_char(
774 base,
775 shifted,
776 modifiers.contains(Modifiers::SHIFT),
777 caps_lock,
778 ));
779 }
780 Some(Event::Key(
781 KeyEvent::new(code)
782 .with_modifiers(modifiers)
783 .with_kind(kind),
784 ))
785 }
786
787 fn kitty_typed_char(base: char, shifted: Option<char>, shift: bool, caps_lock: bool) -> char {
797 let upper = |c: char| {
799 let mut chars = c.to_uppercase();
800 match (chars.next(), chars.next()) {
801 (Some(u), None) => u,
802 _ => c,
803 }
804 };
805 let lower = |c: char| {
806 let mut chars = c.to_lowercase();
807 match (chars.next(), chars.next()) {
808 (Some(l), None) => l,
809 _ => c,
810 }
811 };
812 let typed = if shift {
813 shifted.unwrap_or_else(|| upper(base))
814 } else {
815 base
816 };
817 if caps_lock && typed.is_alphabetic() {
818 if typed.is_lowercase() {
819 upper(typed)
820 } else {
821 lower(typed)
822 }
823 } else {
824 typed
825 }
826 }
827
828 fn kitty_modifiers_and_kind(mod_part: &str) -> (Modifiers, KeyEventKind) {
829 if mod_part.is_empty() {
830 return (Modifiers::NONE, KeyEventKind::Press);
831 }
832
833 let mut parts = mod_part.split(':');
834 let mod_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
835 let kind_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
836
837 let modifiers = Self::modifiers_from_xterm(mod_value);
838 let kind = match kind_value {
839 2 => KeyEventKind::Repeat,
840 3 => KeyEventKind::Release,
841 _ => KeyEventKind::Press,
842 };
843
844 (modifiers, kind)
845 }
846
847 fn kitty_keycode_to_keycode(key_code: u32) -> Option<KeyCode> {
848 match key_code {
849 9 => Some(KeyCode::Tab),
851 13 => Some(KeyCode::Enter),
852 27 => Some(KeyCode::Escape),
853 8 | 127 => Some(KeyCode::Backspace),
854 57_344 => Some(KeyCode::Escape),
856 57_345 => Some(KeyCode::Enter),
857 57_346 => Some(KeyCode::Tab),
858 57_347 => Some(KeyCode::Backspace),
859 57_348 => Some(KeyCode::Insert),
860 57_349 => Some(KeyCode::Delete),
861 57_350 => Some(KeyCode::Left),
862 57_351 => Some(KeyCode::Right),
863 57_352 => Some(KeyCode::Up),
864 57_353 => Some(KeyCode::Down),
865 57_354 => Some(KeyCode::PageUp),
866 57_355 => Some(KeyCode::PageDown),
867 57_356 => Some(KeyCode::Home),
868 57_357 => Some(KeyCode::End),
869 57_364..=57_387 => {
871 let f_num = key_code - 57_364 + 1;
874 debug_assert!(f_num <= 24, "F-key number {f_num} exceeds F24");
875 Some(KeyCode::F(f_num as u8))
876 }
877 57_399..=57_408 => char::from_digit(key_code - 57_399, 10).map(KeyCode::Char),
881 57_409 => Some(KeyCode::Char('.')),
882 57_410 => Some(KeyCode::Char('/')),
883 57_411 => Some(KeyCode::Char('*')),
884 57_412 => Some(KeyCode::Char('-')),
885 57_413 => Some(KeyCode::Char('+')),
886 57_414 => Some(KeyCode::Enter),
887 57_415 => Some(KeyCode::Char('=')),
888 57_416 => Some(KeyCode::Char(',')),
889 57_417 => Some(KeyCode::Left),
890 57_418 => Some(KeyCode::Right),
891 57_419 => Some(KeyCode::Up),
892 57_420 => Some(KeyCode::Down),
893 57_421 => Some(KeyCode::PageUp),
894 57_422 => Some(KeyCode::PageDown),
895 57_423 => Some(KeyCode::Home),
896 57_424 => Some(KeyCode::End),
897 57_425 => Some(KeyCode::Insert),
898 57_426 => Some(KeyCode::Delete),
899 57_430 => Some(KeyCode::MediaPlayPause),
901 57_432 => Some(KeyCode::MediaStop),
902 57_435 => Some(KeyCode::MediaNextTrack),
903 57_436 => Some(KeyCode::MediaPrevTrack),
904 57_358..=57_363 | 57_388..=63_743 => None,
906 _ => char::from_u32(key_code).map(KeyCode::Char),
908 }
909 }
910
911 fn modifiers_from_xterm(value: u32) -> Modifiers {
912 let bits = value.saturating_sub(1);
915 let mut mods = Modifiers::NONE;
916 if bits & 1 != 0 {
917 mods |= Modifiers::SHIFT;
918 }
919 if bits & 2 != 0 {
920 mods |= Modifiers::ALT;
921 }
922 if bits & 4 != 0 {
923 mods |= Modifiers::CTRL;
924 }
925 if bits & 8 != 0 {
926 mods |= Modifiers::SUPER;
927 }
928 mods
929 }
930
931 fn key_with_modifiers(&self, code: KeyCode, params: &[u8]) -> KeyEvent {
933 let (mods, kind) = self.parse_modifier_param(params);
934 KeyEvent::new(code).with_modifiers(mods).with_kind(kind)
935 }
936
937 fn parse_sgr_mouse(&self, params: &[u8], final_byte: u8) -> Option<Event> {
939 let params = ¶ms[1..];
942 let s = std::str::from_utf8(params).ok()?;
943 let mut parts = s.split(';');
944
945 let button_code_u32 = Self::parse_u32_prefix(parts.next()?)?;
948 let button_code = button_code_u32.min(u16::MAX as u32) as u16;
949 let x_raw = Self::parse_i32_prefix(parts.next()?)?;
950 let y_raw = Self::parse_i32_prefix(parts.next()?)?;
951
952 let (button, mods) = self.decode_mouse_button(button_code);
954
955 let kind = if final_byte == b'M' {
956 if button_code & 64 != 0 {
957 match button_code & 3 {
960 0 => MouseEventKind::ScrollUp,
961 1 => MouseEventKind::ScrollDown,
962 2 => MouseEventKind::ScrollLeft,
963 _ => MouseEventKind::ScrollRight,
964 }
965 } else if button_code & 32 != 0 {
966 if button_code & 3 == 3 {
969 MouseEventKind::Moved
970 } else {
971 MouseEventKind::Drag(button)
972 }
973 } else if (button_code & 3) == 3 {
974 MouseEventKind::Up(MouseButton::Left)
977 } else {
978 MouseEventKind::Down(button)
979 }
980 } else {
981 MouseEventKind::Up(button)
982 };
983
984 Some(Event::Mouse(MouseEvent {
985 kind,
986 x: Self::normalize_sgr_coord(x_raw),
987 y: Self::normalize_sgr_coord(y_raw),
988 modifiers: mods,
989 }))
990 }
991
992 #[inline]
993 fn parse_u32_prefix(token: &str) -> Option<u32> {
994 let bytes = token.as_bytes();
995 let digits = bytes.iter().take_while(|b| b.is_ascii_digit()).count();
996 if digits == 0 {
997 return None;
998 }
999 token[..digits].parse().ok()
1000 }
1001
1002 #[inline]
1003 fn parse_i32_prefix(token: &str) -> Option<i32> {
1004 let bytes = token.as_bytes();
1005 if bytes.is_empty() {
1006 return None;
1007 }
1008 let start = if bytes[0] == b'-' || bytes[0] == b'+' {
1009 1
1010 } else {
1011 0
1012 };
1013 let digits = bytes[start..]
1014 .iter()
1015 .take_while(|b| b.is_ascii_digit())
1016 .count();
1017 if digits == 0 {
1018 return None;
1019 }
1020 token[..start + digits].parse().ok()
1021 }
1022
1023 #[inline]
1024 fn normalize_sgr_coord(raw: i32) -> u16 {
1025 if raw <= 1 {
1026 return 0;
1027 }
1028 let zero_indexed = raw - 1;
1029 zero_indexed.min(i32::from(u16::MAX)) as u16
1030 }
1031
1032 fn parse_legacy_mouse(&self, params: &[u8]) -> Option<Event> {
1037 if params.is_empty() || params.starts_with(b"<") {
1038 return None;
1039 }
1040
1041 let s = std::str::from_utf8(params).ok()?;
1042 let mut parts = s.split(';');
1043 let button_code: u16 = parts.next()?.parse().ok()?;
1044 let x: u16 = parts.next()?.parse().ok()?;
1045 let y: u16 = parts.next()?.parse().ok()?;
1046 if parts.next().is_some() {
1048 return None;
1049 }
1050
1051 let (button, mods) = self.decode_mouse_button(button_code);
1052 let kind = if button_code & 64 != 0 {
1053 match button_code & 3 {
1055 0 => MouseEventKind::ScrollUp,
1056 1 => MouseEventKind::ScrollDown,
1057 2 => MouseEventKind::ScrollLeft,
1058 _ => MouseEventKind::ScrollRight,
1059 }
1060 } else if button_code & 32 != 0 {
1061 if button_code & 3 == 3 {
1063 MouseEventKind::Moved
1064 } else {
1065 MouseEventKind::Drag(button)
1066 }
1067 } else if (button_code & 3) == 3 {
1068 MouseEventKind::Up(MouseButton::Left)
1070 } else {
1071 MouseEventKind::Down(button)
1072 };
1073
1074 Some(Event::Mouse(MouseEvent {
1075 kind,
1076 x: x.saturating_sub(1),
1077 y: y.saturating_sub(1),
1078 modifiers: mods,
1079 }))
1080 }
1081
1082 fn decode_mouse_button(&self, code: u16) -> (MouseButton, Modifiers) {
1084 let button = match code & 0b11 {
1085 0 => MouseButton::Left,
1086 1 => MouseButton::Middle,
1087 2 => MouseButton::Right,
1088 _ => MouseButton::Left,
1089 };
1090
1091 let mut mods = Modifiers::NONE;
1092 if code & 4 != 0 {
1093 mods |= Modifiers::SHIFT;
1094 }
1095 if code & 8 != 0 {
1096 mods |= Modifiers::ALT;
1097 }
1098 if code & 16 != 0 {
1099 mods |= Modifiers::CTRL;
1100 }
1101
1102 (button, mods)
1103 }
1104
1105 fn process_ss3(&mut self, byte: u8) -> Option<Event> {
1107 if byte == 0x1B {
1109 self.state = ParserState::Escape;
1110 return None;
1111 }
1112
1113 self.state = ParserState::Ground;
1114
1115 let code = match byte {
1116 b'P' => KeyCode::F(1),
1117 b'Q' => KeyCode::F(2),
1118 b'R' => KeyCode::F(3),
1119 b'S' => KeyCode::F(4),
1120 b'A' => KeyCode::Up,
1121 b'B' => KeyCode::Down,
1122 b'C' => KeyCode::Right,
1123 b'D' => KeyCode::Left,
1124 b'H' => KeyCode::Home,
1125 b'F' => KeyCode::End,
1126 _ => return None,
1127 };
1128
1129 Some(Event::Key(KeyEvent::new(code)))
1130 }
1131
1132 fn process_osc(&mut self, byte: u8) -> Option<Event> {
1134 if byte == 0x1B {
1136 self.state = ParserState::OscEscape;
1137 return None;
1138 }
1139
1140 self.buffer.push(byte);
1141
1142 match byte {
1143 0x07 => {
1145 self.state = ParserState::Ground;
1146 self.parse_osc_sequence()
1147 }
1148 _ => {
1150 self.state = ParserState::OscContent;
1151 None
1152 }
1153 }
1154 }
1155
1156 fn process_osc_content(&mut self, byte: u8) -> Option<Event> {
1158 if byte == 0x1B {
1160 self.state = ParserState::OscEscape;
1161 return None;
1162 }
1163
1164 if byte < 0x20 && byte != 0x07 {
1166 self.state = ParserState::Ground;
1167 self.buffer.clear();
1168 return self.process_ground(byte);
1169 }
1170
1171 match byte {
1172 0x07 => {
1177 self.state = ParserState::Ground;
1178 self.parse_osc_sequence()
1179 }
1180 _ => {
1182 if self.buffer.len() >= MAX_OSC_LEN {
1183 self.state = ParserState::OscIgnore;
1184 self.buffer.clear();
1185 return None;
1186 }
1187 self.buffer.push(byte);
1188 None
1189 }
1190 }
1191 }
1192
1193 fn process_osc_escape(&mut self, byte: u8) -> Option<Event> {
1195 if byte == b'\\' {
1196 self.state = ParserState::Ground;
1198 self.parse_osc_sequence()
1199 } else if byte == 0x1B {
1200 self.state = ParserState::Escape;
1202 self.buffer.clear();
1203 None
1204 } else {
1205 self.buffer.clear();
1211 self.state = ParserState::Escape;
1212 self.process_escape(byte)
1213 }
1214 }
1215
1216 fn process_osc_ignore(&mut self, byte: u8) -> Option<Event> {
1218 match byte {
1219 0x07 => {
1221 self.state = ParserState::Ground;
1222 None
1223 }
1224 0x1B => {
1226 self.state = ParserState::OscEscape;
1227 None
1228 }
1229 _ if byte < 0x20 => {
1231 self.state = ParserState::Ground;
1232 self.process_ground(byte)
1233 }
1234 _ => None,
1236 }
1237 }
1238
1239 fn process_dcs_ignore(&mut self, byte: u8) -> Option<Event> {
1246 match byte {
1247 0x07 => {
1249 self.state = ParserState::Ground;
1250 None
1251 }
1252 0x1B => {
1254 self.state = ParserState::DcsEscape;
1255 None
1256 }
1257 _ if byte < 0x20 => {
1260 self.state = ParserState::Ground;
1261 self.process_ground(byte)
1262 }
1263 _ => None,
1265 }
1266 }
1267
1268 fn process_dcs_escape(&mut self, byte: u8) -> Option<Event> {
1270 if byte == b'\\' {
1271 self.state = ParserState::Ground;
1273 None
1274 } else if byte == 0x1B {
1275 self.state = ParserState::Escape;
1277 None
1278 } else {
1279 self.state = ParserState::Escape;
1282 self.process_escape(byte)
1283 }
1284 }
1285
1286 fn parse_osc_sequence(&mut self) -> Option<Event> {
1288 let seq = std::mem::take(&mut self.buffer);
1289
1290 if seq.starts_with(b"52;") {
1292 return self.parse_osc52_clipboard(&seq);
1293 }
1294
1295 None
1297 }
1298
1299 fn parse_osc52_clipboard(&self, seq: &[u8]) -> Option<Event> {
1301 let content = &seq[3..]; if content.is_empty() {
1304 return None;
1305 }
1306
1307 let source = ClipboardSource::Osc52;
1310
1311 let base64_start = content.iter().position(|&b| b == b';').map(|i| i + 1)?;
1313 let base64_data = &content[base64_start..];
1314
1315 let decoded = self.decode_base64(base64_data)?;
1317
1318 Some(Event::Clipboard(ClipboardEvent::new(
1319 String::from_utf8_lossy(&decoded).into_owned(),
1320 source,
1321 )))
1322 }
1323
1324 fn decode_base64(&self, input: &[u8]) -> Option<Vec<u8>> {
1326 const DECODE_TABLE: [i8; 256] = {
1327 let mut table = [-1i8; 256];
1328 let mut i = 0u8;
1329 while i < 26 {
1330 table[(b'A' + i) as usize] = i as i8;
1331 table[(b'a' + i) as usize] = (i + 26) as i8;
1332 i += 1;
1333 }
1334 let mut i = 0u8;
1335 while i < 10 {
1336 table[(b'0' + i) as usize] = (i + 52) as i8;
1337 i += 1;
1338 }
1339 table[b'+' as usize] = 62;
1340 table[b'/' as usize] = 63;
1341 table
1342 };
1343
1344 let mut output = Vec::with_capacity(input.len() * 3 / 4);
1345 let mut buffer = 0u32;
1346 let mut bits = 0u8;
1347
1348 for &byte in input {
1349 if byte == b'=' {
1350 break;
1351 }
1352 let value = DECODE_TABLE[byte as usize];
1353 if value < 0 {
1354 continue; }
1356 buffer = (buffer << 6) | (value as u32);
1357 bits += 6;
1358 if bits >= 8 {
1359 bits -= 8;
1360 output.push((buffer >> bits) as u8);
1361 buffer &= (1 << bits) - 1;
1362 }
1363 }
1364
1365 Some(output)
1366 }
1367
1368 fn process_utf8(&mut self, byte: u8, collected: u8, expected: u8, alt: bool) -> Option<Event> {
1370 let alt_mods = if alt { Modifiers::ALT } else { Modifiers::NONE };
1371
1372 if (byte & 0xC0) != 0x80 {
1374 self.state = ParserState::Ground;
1377
1378 self.pending_event = self.process_ground(byte);
1380
1381 return Some(Event::Key(
1382 KeyEvent::new(KeyCode::Char(std::char::REPLACEMENT_CHARACTER))
1383 .with_modifiers(alt_mods),
1384 ));
1385 }
1386
1387 self.utf8_buffer[collected as usize] = byte;
1388 let new_collected = collected + 1;
1389
1390 if new_collected == expected {
1391 self.state = ParserState::Ground;
1393 match std::str::from_utf8(&self.utf8_buffer[..expected as usize]) {
1394 Ok(s) => {
1395 let c = s.chars().next()?;
1396 Some(Event::Key(
1397 KeyEvent::new(KeyCode::Char(c)).with_modifiers(alt_mods),
1398 ))
1399 }
1400 Err(_) => Some(Event::Key(
1401 KeyEvent::new(KeyCode::Char(std::char::REPLACEMENT_CHARACTER))
1402 .with_modifiers(alt_mods),
1403 )),
1404 }
1405 } else {
1406 self.state = ParserState::Utf8 {
1408 collected: new_collected,
1409 expected,
1410 alt,
1411 };
1412 None
1413 }
1414 }
1415
1416 fn process_mouse_x10(&mut self, byte: u8) -> Option<Event> {
1418 if let ParserState::MouseX10 {
1419 ref mut collected,
1420 ref mut buffer,
1421 } = self.state
1422 {
1423 buffer[*collected as usize] = byte;
1424 *collected += 1;
1425
1426 if *collected == 3 {
1427 let buf = *buffer;
1429 self.state = ParserState::Ground;
1430
1431 if buf[0] < 32 || buf[1] < 33 || buf[2] < 33 {
1435 return None;
1436 }
1437 let cb = buf[0].saturating_sub(32) as u16;
1438 let cx = buf[1].saturating_sub(33) as u16; let cy = buf[2].saturating_sub(33) as u16;
1440
1441 let (button, mods) = self.decode_mouse_button(cb);
1442
1443 let kind = if cb & 64 != 0 {
1450 match cb & 3 {
1452 0 => MouseEventKind::ScrollUp,
1453 1 => MouseEventKind::ScrollDown,
1454 2 => MouseEventKind::ScrollLeft,
1455 _ => MouseEventKind::ScrollRight,
1456 }
1457 } else if (cb & 3) == 3 {
1458 MouseEventKind::Up(MouseButton::Left)
1460 } else {
1461 MouseEventKind::Down(button)
1463 };
1464
1465 return Some(Event::Mouse(MouseEvent {
1466 kind,
1467 x: cx,
1468 y: cy,
1469 modifiers: mods,
1470 }));
1471 }
1472 }
1473 None
1474 }
1475
1476 fn process_paste_byte(&mut self, byte: u8) -> Option<Event> {
1478 const END_SEQ: &[u8] = b"\x1b[201~";
1479
1480 if self.paste_buffer.len() < MAX_PASTE_LEN {
1486 self.paste_buffer.push(byte);
1487
1488 if self.paste_buffer.ends_with(END_SEQ) {
1490 self.in_paste = false;
1491 let content_len = self.paste_buffer.len() - END_SEQ.len();
1493 let content =
1494 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
1495 self.paste_buffer.clear();
1496 return Some(Event::Paste(PasteEvent::bracketed(content)));
1497 }
1498 } else {
1499 self.buffer.push(byte);
1504 if self.buffer.len() > END_SEQ.len() {
1505 self.buffer.remove(0);
1506 }
1507
1508 let mut last_bytes = [0u8; 6];
1515 let tail_len = self.buffer.len();
1516 let paste_len = self.paste_buffer.len();
1517
1518 if tail_len + paste_len >= 6 {
1520 for i in 0..tail_len {
1522 last_bytes[6 - tail_len + i] = self.buffer[i];
1523 }
1524 let remaining = 6 - tail_len;
1526 if remaining > 0 {
1527 let start = paste_len - remaining;
1528 last_bytes[..remaining]
1529 .copy_from_slice(&self.paste_buffer[start..(remaining + start)]);
1530 }
1531
1532 if last_bytes == END_SEQ {
1533 self.in_paste = false;
1534
1535 let content_len = paste_len - remaining;
1540 let content =
1541 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
1542
1543 self.paste_buffer.clear();
1544 self.buffer.clear();
1545
1546 return Some(Event::Paste(PasteEvent::bracketed(content)));
1547 }
1548 }
1549 }
1550
1551 None
1552 }
1553}
1554
1555#[cfg(test)]
1556mod tests {
1557 use super::*;
1558
1559 #[test]
1560 fn csi_ignore_handles_final_bytes() {
1561 let mut parser = InputParser::new();
1562
1563 let mut seq = vec![0x1B, b'['];
1566 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100)); seq.push(b'@'); let events = parser.parse(&seq);
1570 assert_eq!(events.len(), 0);
1571
1572 let events = parser.parse(b"a");
1575 assert_eq!(events.len(), 1, "Subsequent char 'a' was swallowed");
1576 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1577 }
1578
1579 #[test]
1580 fn legacy_key_with_kitty_event_type_subparam_decodes_release_and_mods() {
1581 let mut parser = InputParser::new();
1584 let events = parser.parse(b"\x1b[1;1:3A");
1585 assert_eq!(events.len(), 1);
1586 assert!(matches!(
1587 events[0],
1588 Event::Key(k) if k.code == KeyCode::Up
1589 && k.modifiers == Modifiers::NONE
1590 && k.kind == KeyEventKind::Release
1591 ));
1592
1593 let events = parser.parse(b"\x1b[3;5:3~");
1595 assert_eq!(events.len(), 1);
1596 assert!(matches!(
1597 events[0],
1598 Event::Key(k) if k.code == KeyCode::Delete
1599 && k.modifiers == Modifiers::CTRL
1600 && k.kind == KeyEventKind::Release
1601 ));
1602
1603 let events = parser.parse(b"\x1b[1;5A");
1605 assert_eq!(events.len(), 1);
1606 assert!(matches!(
1607 events[0],
1608 Event::Key(k) if k.code == KeyCode::Up
1609 && k.modifiers == Modifiers::CTRL
1610 && k.kind == KeyEventKind::Press
1611 ));
1612 }
1613
1614 #[test]
1615 fn csi_final_byte_at_exact_dos_boundary_terminates() {
1616 let mut parser = InputParser::new();
1620 let mut seq = vec![0x1B, b'['];
1621 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN));
1622 seq.push(b'A'); let events = parser.parse(&seq);
1625 drop(events);
1628 let events = parser.parse(b"a");
1629 assert_eq!(events.len(), 1, "keystroke after boundary CSI swallowed");
1630 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1631 }
1632
1633 #[test]
1634 fn osc_bel_terminator_at_exact_dos_boundary_terminates() {
1635 let mut parser = InputParser::new();
1638 let mut seq = vec![0x1B, b']'];
1639 seq.extend(std::iter::repeat_n(b'x', MAX_OSC_LEN));
1640 seq.push(0x07); let events = parser.parse(&seq);
1643 drop(events);
1644 let events = parser.parse(b"a");
1645 assert_eq!(events.len(), 1, "keystroke after boundary OSC swallowed");
1646 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1647 }
1648
1649 #[test]
1650 fn stray_paste_terminator_is_ignored() {
1651 let mut parser = InputParser::new();
1653 let events = parser.parse(b"\x1b[201~");
1654 assert_eq!(events.len(), 0, "stray end-paste produced {events:?}");
1655
1656 let events = parser.parse(b"\x1b[200~hi\x1b[201~");
1658 assert_eq!(events.len(), 1);
1659 assert!(matches!(&events[0], Event::Paste(p) if p.text == "hi"));
1660 }
1661
1662 #[test]
1663 fn x10_mouse_scroll_codes_not_misread_as_button_events() {
1664 let mut parser = InputParser::new();
1667 parser.set_expect_x10_mouse(true);
1668
1669 let events = parser.parse(&[0x1B, b'[', b'M', 67 + 32, 33, 33]);
1671 assert_eq!(events.len(), 1);
1672 assert!(matches!(
1673 events[0],
1674 Event::Mouse(m) if m.kind == MouseEventKind::ScrollRight && m.x == 0 && m.y == 0
1675 ));
1676
1677 let events = parser.parse(&[0x1B, b'[', b'M', 64 + 32, 33, 33]);
1679 assert_eq!(events.len(), 1);
1680 assert!(matches!(
1681 events[0],
1682 Event::Mouse(m) if m.kind == MouseEventKind::ScrollUp
1683 ));
1684
1685 let events = parser.parse(&[0x1B, b'[', b'M', 3 + 32, 33, 33]);
1687 assert_eq!(events.len(), 1);
1688 assert!(matches!(
1689 events[0],
1690 Event::Mouse(m) if m.kind == MouseEventKind::Up(MouseButton::Left)
1691 ));
1692 }
1693
1694 #[test]
1695 fn csi_first_byte_control_char_is_reprocessed_not_swallowed() {
1696 let mut parser = InputParser::new();
1699 let events = parser.parse(b"\x1b[\r");
1700 assert_eq!(events.len(), 1);
1701 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Enter));
1702 }
1703
1704 #[test]
1705 fn alt_non_ascii_decodes_as_alt_char() {
1706 let mut parser = InputParser::new();
1708 let events = parser.parse(b"\x1b\xc3\xa9");
1709 assert_eq!(events.len(), 1, "Alt+\u{e9} dropped: {events:?}");
1710 assert!(matches!(
1711 events[0],
1712 Event::Key(k) if k.code == KeyCode::Char('\u{e9}')
1713 && k.modifiers == Modifiers::ALT
1714 ));
1715
1716 let events1 = parser.parse(b"\x1b\xc3");
1718 assert_eq!(events1.len(), 0);
1719 let events2 = parser.parse(b"\xa9");
1720 assert_eq!(events2.len(), 1);
1721 assert!(matches!(
1722 events2[0],
1723 Event::Key(k) if k.code == KeyCode::Char('\u{e9}')
1724 && k.modifiers == Modifiers::ALT
1725 ));
1726
1727 let events = parser.parse(b"\xc3\xa9");
1729 assert_eq!(events.len(), 1);
1730 assert!(matches!(
1731 events[0],
1732 Event::Key(k) if k.code == KeyCode::Char('\u{e9}')
1733 && k.modifiers == Modifiers::NONE
1734 ));
1735 }
1736
1737 #[test]
1738 fn ascii_characters_parsed() {
1739 let mut parser = InputParser::new();
1740
1741 let events = parser.parse(b"abc");
1742 assert_eq!(events.len(), 3);
1743 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1744 assert!(matches!(events[1], Event::Key(k) if k.code == KeyCode::Char('b')));
1745 assert!(matches!(events[2], Event::Key(k) if k.code == KeyCode::Char('c')));
1746 }
1747
1748 #[test]
1749 fn control_characters() {
1750 let mut parser = InputParser::new();
1751
1752 let events = parser.parse(&[0x01]);
1754 assert_eq!(events.len(), 1);
1755 assert!(matches!(
1756 events[0],
1757 Event::Key(k) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::CTRL)
1758 ));
1759
1760 let events = parser.parse(&[0x7F]);
1762 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace));
1763 }
1764
1765 #[test]
1766 fn arrow_keys() {
1767 let mut parser = InputParser::new();
1768
1769 assert!(matches!(
1770 parser.parse(b"\x1b[A").first(),
1771 Some(Event::Key(k)) if k.code == KeyCode::Up
1772 ));
1773 assert!(matches!(
1774 parser.parse(b"\x1b[B").first(),
1775 Some(Event::Key(k)) if k.code == KeyCode::Down
1776 ));
1777 assert!(matches!(
1778 parser.parse(b"\x1b[C").first(),
1779 Some(Event::Key(k)) if k.code == KeyCode::Right
1780 ));
1781 assert!(matches!(
1782 parser.parse(b"\x1b[D").first(),
1783 Some(Event::Key(k)) if k.code == KeyCode::Left
1784 ));
1785 }
1786
1787 #[test]
1788 fn c1_csi_arrow_keys() {
1789 let mut parser = InputParser::new();
1790
1791 assert!(matches!(
1792 parser.parse(&[0x9B, b'A']).first(),
1793 Some(Event::Key(k)) if k.code == KeyCode::Up
1794 ));
1795 assert!(matches!(
1796 parser.parse(&[0x9B, b'B']).first(),
1797 Some(Event::Key(k)) if k.code == KeyCode::Down
1798 ));
1799 }
1800
1801 #[test]
1802 fn c1_csi_mouse_sgr_protocol() {
1803 let mut parser = InputParser::new();
1804
1805 let events = parser.parse(&[0x9B, b'<', b'0', b';', b'1', b'0', b';', b'2', b'0', b'M']);
1806 assert!(matches!(
1807 events.first(),
1808 Some(Event::Mouse(m)) if m.x == 9 && m.y == 19
1809 ));
1810 }
1811
1812 #[test]
1813 fn function_keys_ss3() {
1814 let mut parser = InputParser::new();
1815
1816 assert!(matches!(
1817 parser.parse(b"\x1bOP").first(),
1818 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1819 ));
1820 assert!(matches!(
1821 parser.parse(b"\x1bOQ").first(),
1822 Some(Event::Key(k)) if k.code == KeyCode::F(2)
1823 ));
1824 assert!(matches!(
1825 parser.parse(b"\x1bOR").first(),
1826 Some(Event::Key(k)) if k.code == KeyCode::F(3)
1827 ));
1828 assert!(matches!(
1829 parser.parse(b"\x1bOS").first(),
1830 Some(Event::Key(k)) if k.code == KeyCode::F(4)
1831 ));
1832 }
1833
1834 #[test]
1835 fn function_keys_csi() {
1836 let mut parser = InputParser::new();
1837
1838 assert!(matches!(
1839 parser.parse(b"\x1b[15~").first(),
1840 Some(Event::Key(k)) if k.code == KeyCode::F(5)
1841 ));
1842 assert!(matches!(
1843 parser.parse(b"\x1b[17~").first(),
1844 Some(Event::Key(k)) if k.code == KeyCode::F(6)
1845 ));
1846 }
1847
1848 #[test]
1849 fn modifiers_in_csi() {
1850 let mut parser = InputParser::new();
1851
1852 let events = parser.parse(b"\x1b[1;2A");
1854 assert!(matches!(
1855 events.first(),
1856 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::SHIFT)
1857 ));
1858
1859 let events = parser.parse(b"\x1b[1;5A");
1861 assert!(matches!(
1862 events.first(),
1863 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::CTRL)
1864 ));
1865 }
1866
1867 #[test]
1868 fn modifiers_in_csi_alt_ctrl() {
1869 let mut parser = InputParser::new();
1870
1871 let events = parser.parse(b"\x1b[1;7A");
1873 assert!(matches!(
1874 events.first(),
1875 Some(Event::Key(k))
1876 if k.code == KeyCode::Up
1877 && k.modifiers.contains(Modifiers::ALT)
1878 && k.modifiers.contains(Modifiers::CTRL)
1879 ));
1880 }
1881
1882 #[test]
1883 fn kitty_keyboard_basic_char() {
1884 let mut parser = InputParser::new();
1885
1886 let events = parser.parse(b"\x1b[97u");
1887 assert!(matches!(
1888 events.first(),
1889 Some(Event::Key(k))
1890 if k.code == KeyCode::Char('a')
1891 && k.modifiers == Modifiers::NONE
1892 && k.kind == KeyEventKind::Press
1893 ));
1894 }
1895
1896 #[test]
1897 fn kitty_keyboard_with_modifiers_and_kind() {
1898 let mut parser = InputParser::new();
1899
1900 let events = parser.parse(b"\x1b[97;5:2u");
1902 assert!(matches!(
1903 events.first(),
1904 Some(Event::Key(k))
1905 if k.code == KeyCode::Char('a')
1906 && k.modifiers.contains(Modifiers::CTRL)
1907 && k.kind == KeyEventKind::Repeat
1908 ));
1909 }
1910
1911 #[test]
1912 fn kitty_keyboard_function_key() {
1913 let mut parser = InputParser::new();
1914
1915 let events = parser.parse(b"\x1b[57364;1u");
1916 assert!(matches!(
1917 events.first(),
1918 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1919 ));
1920 }
1921
1922 #[test]
1923 fn kitty_keyboard_types_the_shifted_character() {
1924 let key = |input: &[u8]| match InputParser::new().parse(input).first() {
1925 Some(Event::Key(k)) => (k.code, k.modifiers),
1926 other => panic!("{input:?}: {other:?}"),
1927 };
1928 let shift = Modifiers::SHIFT;
1929
1930 assert_eq!(key(b"\x1b[97:65;2u"), (KeyCode::Char('A'), shift));
1932 assert_eq!(key(b"\x1b[97;2u"), (KeyCode::Char('A'), shift));
1933 assert_eq!(key(b"\x1b[49:33;2u"), (KeyCode::Char('!'), shift));
1935 assert_eq!(key(b"\x1b[97::97;2u"), (KeyCode::Char('A'), shift));
1937 assert_eq!(key(b"\x1b[97;65u"), (KeyCode::Char('A'), Modifiers::NONE));
1939 assert_eq!(key(b"\x1b[97:65;66u"), (KeyCode::Char('a'), shift));
1940 assert_eq!(key(b"\x1b[49;65u"), (KeyCode::Char('1'), Modifiers::NONE));
1941 assert_eq!(key(b"\x1b[97u"), (KeyCode::Char('a'), Modifiers::NONE));
1943 assert_eq!(key(b"\x1b[97;5u"), (KeyCode::Char('a'), Modifiers::CTRL));
1944 assert_eq!(
1945 key(b"\x1b[97:65;6u"),
1946 (KeyCode::Char('A'), Modifiers::CTRL | shift)
1947 );
1948 }
1949
1950 #[test]
1951 fn kitty_keyboard_keypad_and_media_keys() {
1952 let key = |input: &[u8]| match InputParser::new().parse(input).first() {
1953 Some(Event::Key(k)) => (k.code, k.modifiers, k.kind),
1954 other => panic!("{input:?}: {other:?}"),
1955 };
1956 let press = KeyEventKind::Press;
1957
1958 assert_eq!(key(b"\x1b[57399u").0, KeyCode::Char('0'));
1959 assert_eq!(key(b"\x1b[57404u").0, KeyCode::Char('5'));
1960 assert_eq!(key(b"\x1b[57408u").0, KeyCode::Char('9'));
1961 assert_eq!(key(b"\x1b[57409u").0, KeyCode::Char('.'));
1962 assert_eq!(key(b"\x1b[57413u").0, KeyCode::Char('+'));
1963 assert_eq!(key(b"\x1b[57414u").0, KeyCode::Enter);
1964 assert_eq!(key(b"\x1b[57426u").0, KeyCode::Delete);
1965 assert_eq!(
1966 key(b"\x1b[57419;1:3u"),
1967 (KeyCode::Up, Modifiers::NONE, KeyEventKind::Release)
1968 );
1969 assert_eq!(
1971 key(b"\x1b[57400;2u"),
1972 (KeyCode::Char('1'), Modifiers::SHIFT, press)
1973 );
1974 assert_eq!(key(b"\x1b[57430u").0, KeyCode::MediaPlayPause);
1975 assert_eq!(key(b"\x1b[57436u").0, KeyCode::MediaPrevTrack);
1976 assert!(InputParser::new().parse(b"\x1b[57441u").is_empty());
1978 }
1979
1980 #[test]
1981 fn alt_key_escapes() {
1982 let mut parser = InputParser::new();
1983
1984 let events = parser.parse(b"\x1ba");
1985 assert!(matches!(
1986 events.first(),
1987 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::ALT)
1988 ));
1989 }
1990
1991 #[test]
1992 fn alt_backspace() {
1993 let mut parser = InputParser::new();
1994
1995 let events = parser.parse(b"\x1b\x7f");
1996 assert!(matches!(
1997 events.first(),
1998 Some(Event::Key(k))
1999 if k.code == KeyCode::Backspace && k.modifiers.contains(Modifiers::ALT)
2000 ));
2001 }
2002
2003 #[test]
2004 fn escape_escape_resets_state() {
2005 let mut parser = InputParser::new();
2006
2007 let events = parser.parse(b"\x1b\x1b");
2008 assert!(matches!(
2009 events.first(),
2010 Some(Event::Key(k)) if k.code == KeyCode::Escape && k.modifiers.contains(Modifiers::ALT)
2011 ));
2012
2013 let events = parser.parse(b"a");
2014 assert!(matches!(
2015 events.first(),
2016 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers == Modifiers::NONE
2017 ));
2018 }
2019
2020 #[test]
2021 fn focus_events() {
2022 let mut parser = InputParser::new();
2023
2024 assert!(matches!(
2025 parser.parse(b"\x1b[I").first(),
2026 Some(Event::Focus(true))
2027 ));
2028 assert!(matches!(
2029 parser.parse(b"\x1b[O").first(),
2030 Some(Event::Focus(false))
2031 ));
2032 }
2033
2034 #[test]
2035 fn bracketed_paste() {
2036 let mut parser = InputParser::new();
2037
2038 let events = parser.parse(b"\x1b[200~hello world\x1b[201~");
2040 assert_eq!(events.len(), 1);
2041 assert!(matches!(
2042 &events[0],
2043 Event::Paste(p) if p.text == "hello world"
2044 ));
2045 }
2046
2047 #[test]
2048 fn mouse_sgr_protocol() {
2049 let mut parser = InputParser::new();
2050
2051 let events = parser.parse(b"\x1b[<0;10;20M");
2053 assert!(matches!(
2054 events.first(),
2055 Some(Event::Mouse(m)) if m.x == 9 && m.y == 19 ));
2057 }
2058
2059 #[test]
2060 fn mouse_sgr_protocol_with_subparams() {
2061 let mut parser = InputParser::new();
2062
2063 let events = parser.parse(b"\x1b[<0:0;10:0;20:0M");
2065 assert!(matches!(
2066 events.first(),
2067 Some(Event::Mouse(m))
2068 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2069 && m.x == 9
2070 && m.y == 19
2071 ));
2072 }
2073
2074 #[test]
2075 fn mouse_sgr_protocol_large_coords_clamped() {
2076 let mut parser = InputParser::new();
2077
2078 let events = parser.parse(b"\x1b[<0;70000;80000M");
2080 assert!(matches!(
2081 events.first(),
2082 Some(Event::Mouse(m))
2083 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2084 && m.x == u16::MAX
2085 && m.y == u16::MAX
2086 ));
2087 }
2088
2089 #[test]
2090 fn mouse_sgr_protocol_negative_coords_clamped() {
2091 let mut parser = InputParser::new();
2092
2093 let events = parser.parse(b"\x1b[<0;-12;-3M");
2096 assert!(matches!(
2097 events.first(),
2098 Some(Event::Mouse(m))
2099 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2100 && m.x == 0
2101 && m.y == 0
2102 ));
2103 }
2104
2105 #[test]
2106 fn mouse_sgr_modifiers() {
2107 let mut parser = InputParser::new();
2108
2109 let events = parser.parse(b"\x1b[<28;3;4M");
2111 assert!(matches!(
2112 events.first(),
2113 Some(Event::Mouse(m))
2114 if m.modifiers.contains(Modifiers::SHIFT)
2115 && m.modifiers.contains(Modifiers::ALT)
2116 && m.modifiers.contains(Modifiers::CTRL)
2117 ));
2118 }
2119
2120 #[test]
2121 fn mouse_sgr_scroll_up() {
2122 let mut parser = InputParser::new();
2123
2124 let events = parser.parse(b"\x1b[<64;5;5M");
2126 assert!(matches!(
2127 events.first(),
2128 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollUp)
2129 ));
2130 }
2131
2132 #[test]
2133 fn mouse_sgr_scroll_down() {
2134 let mut parser = InputParser::new();
2135
2136 let events = parser.parse(b"\x1b[<65;5;5M");
2138 assert!(matches!(
2139 events.first(),
2140 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollDown)
2141 ));
2142 }
2143
2144 #[test]
2145 fn mouse_sgr_scroll_left() {
2146 let mut parser = InputParser::new();
2147
2148 let events = parser.parse(b"\x1b[<66;5;5M");
2150 assert!(matches!(
2151 events.first(),
2152 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollLeft)
2153 ));
2154 }
2155
2156 #[test]
2157 fn mouse_sgr_scroll_right() {
2158 let mut parser = InputParser::new();
2159
2160 let events = parser.parse(b"\x1b[<67;5;5M");
2162 assert!(matches!(
2163 events.first(),
2164 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollRight)
2165 ));
2166 }
2167
2168 #[test]
2169 fn mouse_sgr_drag_left() {
2170 let mut parser = InputParser::new();
2171
2172 let events = parser.parse(b"\x1b[<32;10;20M");
2174 assert!(matches!(
2175 events.first(),
2176 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Drag(MouseButton::Left))
2177 ));
2178 }
2179
2180 #[test]
2181 fn utf8_characters() {
2182 let mut parser = InputParser::new();
2183
2184 let events = parser.parse(&[0xC3, 0xA9]);
2186 assert!(matches!(
2187 events.first(),
2188 Some(Event::Key(k)) if k.code == KeyCode::Char('é')
2189 ));
2190 }
2191
2192 #[test]
2193 fn invalid_utf8_emits_replacement_then_reprocesses_byte() {
2194 let mut parser = InputParser::new();
2195
2196 let events = parser.parse(&[0xE2, 0x28]);
2198 assert_eq!(events.len(), 2);
2199 assert!(matches!(
2200 events[0],
2201 Event::Key(k) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)
2202 ));
2203 assert!(matches!(
2204 events[1],
2205 Event::Key(k) if k.code == KeyCode::Char('(')
2206 ));
2207 }
2208
2209 #[test]
2210 fn dos_protection_csi() {
2211 let mut parser = InputParser::new();
2212
2213 let mut seq = vec![0x1B, b'['];
2215 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
2216 seq.push(b'A');
2217
2218 let events = parser.parse(&seq);
2221 assert_eq!(
2222 events.len(),
2223 0,
2224 "Oversized CSI sequence should produce no events"
2225 );
2226
2227 let events = parser.parse(b"\x1b[A");
2230 assert!(matches!(
2231 events.first(),
2232 Some(Event::Key(k)) if k.code == KeyCode::Up
2233 ));
2234 }
2235
2236 #[test]
2237 fn incomplete_csi_sequence_emits_no_event() {
2238 let mut parser = InputParser::new();
2239 let events = parser.parse(b"\x1b[");
2240 assert!(events.is_empty());
2241 }
2242
2243 #[test]
2244 fn dos_protection_paste() {
2245 let mut parser = InputParser::new();
2246
2247 parser.parse(b"\x1b[200~");
2249
2250 let content = vec![b'x'; MAX_PASTE_LEN - 100]; parser.parse(&content);
2253
2254 let events = parser.parse(b"\x1b[201~");
2256
2257 assert!(matches!(
2259 events.first(),
2260 Some(Event::Paste(p)) if p.text.len() <= MAX_PASTE_LEN
2261 ));
2262 }
2263
2264 #[test]
2265 fn dos_protection_paste_overflow_terminator() {
2266 let mut parser = InputParser::new();
2267
2268 parser.parse(b"\x1b[200~");
2270
2271 let overflow = 100;
2275 let content = vec![b'a'; MAX_PASTE_LEN + overflow];
2276 parser.parse(&content);
2277
2278 let events = parser.parse(b"\x1b[201~");
2281
2282 assert_eq!(events.len(), 1, "Should emit paste event");
2283 match &events[0] {
2284 Event::Paste(p) => {
2285 assert_eq!(
2288 p.text.len(),
2289 MAX_PASTE_LEN,
2290 "Paste should be capped at MAX_PASTE_LEN bytes"
2291 );
2292 assert!(p.text.chars().all(|c| c == 'a'));
2294 }
2295 _ => unreachable!("Expected Paste event"),
2296 }
2297
2298 let events = parser.parse(b"b");
2300 assert_eq!(events.len(), 1);
2301 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('b')));
2302 }
2303
2304 #[test]
2305 fn no_panic_on_invalid_input() {
2306 let mut parser = InputParser::new();
2307
2308 let garbage = [0xFF, 0xFE, 0x00, 0x1B, 0x1B, 0x1B, b'[', 0xFF, b']', 0x00];
2310
2311 let _ = parser.parse(&garbage);
2313 }
2314
2315 #[test]
2316 fn dos_protection_paste_boundary() {
2317 let mut parser = InputParser::new();
2318 parser.parse(b"\x1b[200~");
2320
2321 let content = vec![b'x'; MAX_PASTE_LEN];
2323 parser.parse(&content);
2324
2325 let events = parser.parse(b"\x1b[201~");
2328
2329 assert!(
2330 !events.is_empty(),
2331 "Parser trapped in paste mode after hitting limit"
2332 );
2333 assert!(matches!(events[0], Event::Paste(_)));
2334 }
2335
2336 #[test]
2339 fn csi_tilde_home() {
2340 let mut parser = InputParser::new();
2341 let events = parser.parse(b"\x1b[1~");
2342 assert!(matches!(
2343 events.first(),
2344 Some(Event::Key(k)) if k.code == KeyCode::Home
2345 ));
2346 }
2347
2348 #[test]
2349 fn csi_tilde_insert() {
2350 let mut parser = InputParser::new();
2351 let events = parser.parse(b"\x1b[2~");
2352 assert!(matches!(
2353 events.first(),
2354 Some(Event::Key(k)) if k.code == KeyCode::Insert
2355 ));
2356 }
2357
2358 #[test]
2359 fn csi_tilde_delete() {
2360 let mut parser = InputParser::new();
2361 let events = parser.parse(b"\x1b[3~");
2362 assert!(matches!(
2363 events.first(),
2364 Some(Event::Key(k)) if k.code == KeyCode::Delete
2365 ));
2366 }
2367
2368 #[test]
2369 fn csi_tilde_end() {
2370 let mut parser = InputParser::new();
2371 let events = parser.parse(b"\x1b[4~");
2372 assert!(matches!(
2373 events.first(),
2374 Some(Event::Key(k)) if k.code == KeyCode::End
2375 ));
2376 }
2377
2378 #[test]
2379 fn csi_tilde_page_up() {
2380 let mut parser = InputParser::new();
2381 let events = parser.parse(b"\x1b[5~");
2382 assert!(matches!(
2383 events.first(),
2384 Some(Event::Key(k)) if k.code == KeyCode::PageUp
2385 ));
2386 }
2387
2388 #[test]
2389 fn csi_tilde_page_down() {
2390 let mut parser = InputParser::new();
2391 let events = parser.parse(b"\x1b[6~");
2392 assert!(matches!(
2393 events.first(),
2394 Some(Event::Key(k)) if k.code == KeyCode::PageDown
2395 ));
2396 }
2397
2398 #[test]
2401 fn csi_home_and_end() {
2402 let mut parser = InputParser::new();
2403 assert!(matches!(
2404 parser.parse(b"\x1b[H").first(),
2405 Some(Event::Key(k)) if k.code == KeyCode::Home
2406 ));
2407 assert!(matches!(
2408 parser.parse(b"\x1b[F").first(),
2409 Some(Event::Key(k)) if k.code == KeyCode::End
2410 ));
2411 }
2412
2413 #[test]
2416 fn ss3_home_and_end() {
2417 let mut parser = InputParser::new();
2418 assert!(matches!(
2419 parser.parse(b"\x1bOH").first(),
2420 Some(Event::Key(k)) if k.code == KeyCode::Home
2421 ));
2422 assert!(matches!(
2423 parser.parse(b"\x1bOF").first(),
2424 Some(Event::Key(k)) if k.code == KeyCode::End
2425 ));
2426 }
2427
2428 #[test]
2431 fn backtab_csi_z() {
2432 let mut parser = InputParser::new();
2433 let events = parser.parse(b"\x1b[Z");
2434 assert!(matches!(
2435 events.first(),
2436 Some(Event::Key(k)) if k.code == KeyCode::BackTab
2437 ));
2438 }
2439
2440 #[test]
2443 fn function_keys_f7_to_f12() {
2444 let mut parser = InputParser::new();
2445 assert!(matches!(
2446 parser.parse(b"\x1b[18~").first(),
2447 Some(Event::Key(k)) if k.code == KeyCode::F(7)
2448 ));
2449 assert!(matches!(
2450 parser.parse(b"\x1b[19~").first(),
2451 Some(Event::Key(k)) if k.code == KeyCode::F(8)
2452 ));
2453 assert!(matches!(
2454 parser.parse(b"\x1b[20~").first(),
2455 Some(Event::Key(k)) if k.code == KeyCode::F(9)
2456 ));
2457 assert!(matches!(
2458 parser.parse(b"\x1b[21~").first(),
2459 Some(Event::Key(k)) if k.code == KeyCode::F(10)
2460 ));
2461 assert!(matches!(
2462 parser.parse(b"\x1b[23~").first(),
2463 Some(Event::Key(k)) if k.code == KeyCode::F(11)
2464 ));
2465 assert!(matches!(
2466 parser.parse(b"\x1b[24~").first(),
2467 Some(Event::Key(k)) if k.code == KeyCode::F(12)
2468 ));
2469 }
2470
2471 #[test]
2474 fn ctrl_home_and_alt_end() {
2475 let mut parser = InputParser::new();
2476
2477 let events = parser.parse(b"\x1b[1;5H");
2479 assert!(matches!(
2480 events.first(),
2481 Some(Event::Key(k)) if k.code == KeyCode::Home && k.modifiers.contains(Modifiers::CTRL)
2482 ));
2483
2484 let events = parser.parse(b"\x1b[1;3F");
2486 assert!(matches!(
2487 events.first(),
2488 Some(Event::Key(k)) if k.code == KeyCode::End && k.modifiers.contains(Modifiers::ALT)
2489 ));
2490 }
2491
2492 #[test]
2493 fn shift_ctrl_arrow() {
2494 let mut parser = InputParser::new();
2495
2496 let events = parser.parse(b"\x1b[1;6C");
2498 assert!(matches!(
2499 events.first(),
2500 Some(Event::Key(k)) if k.code == KeyCode::Right
2501 && k.modifiers.contains(Modifiers::SHIFT)
2502 && k.modifiers.contains(Modifiers::CTRL)
2503 ));
2504 }
2505
2506 #[test]
2507 fn modifiers_on_tilde_keys() {
2508 let mut parser = InputParser::new();
2509
2510 let events = parser.parse(b"\x1b[3;5~");
2512 assert!(matches!(
2513 events.first(),
2514 Some(Event::Key(k)) if k.code == KeyCode::Delete && k.modifiers.contains(Modifiers::CTRL)
2515 ));
2516
2517 let events = parser.parse(b"\x1b[5;2~");
2519 assert!(matches!(
2520 events.first(),
2521 Some(Event::Key(k)) if k.code == KeyCode::PageUp && k.modifiers.contains(Modifiers::SHIFT)
2522 ));
2523 }
2524
2525 #[test]
2528 fn mouse_sgr_right_click() {
2529 let mut parser = InputParser::new();
2530 let events = parser.parse(b"\x1b[<2;15;10M");
2532 assert!(matches!(
2533 events.first(),
2534 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Right))
2535 && m.x == 14 && m.y == 9
2536 ));
2537 }
2538
2539 #[test]
2540 fn mouse_sgr_middle_click() {
2541 let mut parser = InputParser::new();
2542 let events = parser.parse(b"\x1b[<1;5;5M");
2544 assert!(matches!(
2545 events.first(),
2546 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Middle))
2547 ));
2548 }
2549
2550 #[test]
2551 fn mouse_sgr_button_release() {
2552 let mut parser = InputParser::new();
2553 let events = parser.parse(b"\x1b[<0;10;20m");
2555 assert!(matches!(
2556 events.first(),
2557 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Up(MouseButton::Left))
2558 ));
2559 }
2560
2561 #[test]
2562 fn mouse_sgr_button_release_uppercase_m_compat() {
2563 let mut parser = InputParser::new();
2564 let events = parser.parse(b"\x1b[<3;10;20M");
2567 assert!(matches!(
2568 events.first(),
2569 Some(Event::Mouse(m))
2570 if matches!(m.kind, MouseEventKind::Up(MouseButton::Left))
2571 && m.x == 9
2572 && m.y == 19
2573 ));
2574 }
2575
2576 #[test]
2577 fn mouse_sgr_moved() {
2578 let mut parser = InputParser::new();
2579 let events = parser.parse(b"\x1b[<35;10;20M");
2581 assert!(matches!(
2582 events.first(),
2583 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Moved)
2584 ));
2585 }
2586
2587 #[test]
2588 fn mouse_sgr_with_modifiers() {
2589 let mut parser = InputParser::new();
2590 let events = parser.parse(b"\x1b[<4;5;5M");
2592 assert!(matches!(
2593 events.first(),
2594 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2595 && m.modifiers.contains(Modifiers::SHIFT)
2596 ));
2597
2598 let events = parser.parse(b"\x1b[<16;5;5M");
2600 assert!(matches!(
2601 events.first(),
2602 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2603 && m.modifiers.contains(Modifiers::CTRL)
2604 ));
2605
2606 let events = parser.parse(b"\x1b[<8;5;5M");
2608 assert!(matches!(
2609 events.first(),
2610 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2611 && m.modifiers.contains(Modifiers::ALT)
2612 ));
2613 }
2614
2615 #[test]
2616 fn mouse_legacy_1015_when_enabled() {
2617 let mut parser = InputParser::new();
2618 parser.set_expect_x10_mouse(true);
2619
2620 let events = parser.parse(b"\x1b[0;10;20M");
2621 assert!(matches!(
2622 events.first(),
2623 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2624 && m.x == 9 && m.y == 19
2625 ));
2626 }
2627
2628 #[test]
2629 fn mouse_legacy_1015_with_fallback_enabled() {
2630 let mut parser = InputParser::new();
2631 parser.set_allow_legacy_mouse(true);
2632
2633 let events = parser.parse(b"\x1b[0;10;20M");
2634 assert!(matches!(
2635 events.first(),
2636 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2637 && m.x == 9 && m.y == 19
2638 ));
2639 }
2640
2641 #[test]
2642 fn mouse_legacy_1015_ignored_when_disabled() {
2643 let mut parser = InputParser::new();
2644 let events = parser.parse(b"\x1b[0;10;20M");
2645 assert!(
2646 events.is_empty(),
2647 "legacy mouse should require explicit opt-in"
2648 );
2649 }
2650
2651 #[test]
2652 fn mouse_x10_when_enabled() {
2653 let mut parser = InputParser::new();
2654 parser.set_expect_x10_mouse(true);
2655
2656 let events = parser.parse(&[0x1B, b'[', b'M', 32, 42, 52]);
2657 assert!(matches!(
2658 events.first(),
2659 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2660 && m.x == 9 && m.y == 19
2661 ));
2662 }
2663
2664 #[test]
2665 fn mouse_x10_malformed_packet_ignored() {
2666 let mut parser = InputParser::new();
2667 parser.set_expect_x10_mouse(true);
2668
2669 let events = parser.parse(&[0x1B, b'[', b'M', 31, 0, 10]);
2671 assert!(
2672 events.iter().all(|event| !matches!(event, Event::Mouse(_))),
2673 "malformed X10 payload must not emit mouse events"
2674 );
2675 }
2676
2677 #[test]
2680 fn kitty_keyboard_release_event() {
2681 let mut parser = InputParser::new();
2682 let events = parser.parse(b"\x1b[97;1:3u");
2684 assert!(matches!(
2685 events.first(),
2686 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.kind == KeyEventKind::Release
2687 ));
2688
2689 let events = parser.parse(b"\x1b[65;5:3u");
2691 assert!(matches!(
2692 events.first(),
2693 Some(Event::Key(k))
2694 if k.code == KeyCode::Char('A')
2695 && k.modifiers.contains(Modifiers::CTRL)
2696 && k.kind == KeyEventKind::Release
2697 ));
2698 }
2699
2700 #[test]
2701 fn kitty_keyboard_special_keys() {
2702 let mut parser = InputParser::new();
2703
2704 assert!(matches!(
2706 parser.parse(b"\x1b[57344u").first(),
2707 Some(Event::Key(k)) if k.code == KeyCode::Escape
2708 ));
2709
2710 assert!(matches!(
2712 parser.parse(b"\x1b[57345u").first(),
2713 Some(Event::Key(k)) if k.code == KeyCode::Enter
2714 ));
2715
2716 assert!(matches!(
2718 parser.parse(b"\x1b[57346u").first(),
2719 Some(Event::Key(k)) if k.code == KeyCode::Tab
2720 ));
2721
2722 assert!(matches!(
2724 parser.parse(b"\x1b[57347u").first(),
2725 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2726 ));
2727
2728 assert!(matches!(
2730 parser.parse(b"\x1b[57348u").first(),
2731 Some(Event::Key(k)) if k.code == KeyCode::Insert
2732 ));
2733
2734 assert!(matches!(
2736 parser.parse(b"\x1b[57349u").first(),
2737 Some(Event::Key(k)) if k.code == KeyCode::Delete
2738 ));
2739 }
2740
2741 #[test]
2742 fn kitty_keyboard_navigation_keys() {
2743 let mut parser = InputParser::new();
2744
2745 assert!(matches!(
2747 parser.parse(b"\x1b[57350u").first(),
2748 Some(Event::Key(k)) if k.code == KeyCode::Left
2749 ));
2750 assert!(matches!(
2752 parser.parse(b"\x1b[57351u").first(),
2753 Some(Event::Key(k)) if k.code == KeyCode::Right
2754 ));
2755 assert!(matches!(
2757 parser.parse(b"\x1b[57352u").first(),
2758 Some(Event::Key(k)) if k.code == KeyCode::Up
2759 ));
2760 assert!(matches!(
2762 parser.parse(b"\x1b[57353u").first(),
2763 Some(Event::Key(k)) if k.code == KeyCode::Down
2764 ));
2765 assert!(matches!(
2767 parser.parse(b"\x1b[57354u").first(),
2768 Some(Event::Key(k)) if k.code == KeyCode::PageUp
2769 ));
2770 assert!(matches!(
2772 parser.parse(b"\x1b[57355u").first(),
2773 Some(Event::Key(k)) if k.code == KeyCode::PageDown
2774 ));
2775 assert!(matches!(
2777 parser.parse(b"\x1b[57356u").first(),
2778 Some(Event::Key(k)) if k.code == KeyCode::Home
2779 ));
2780 assert!(matches!(
2782 parser.parse(b"\x1b[57357u").first(),
2783 Some(Event::Key(k)) if k.code == KeyCode::End
2784 ));
2785 }
2786
2787 #[test]
2788 fn kitty_keyboard_f_keys() {
2789 let mut parser = InputParser::new();
2790 assert!(matches!(
2792 parser.parse(b"\x1b[57364u").first(),
2793 Some(Event::Key(k)) if k.code == KeyCode::F(1)
2794 ));
2795 assert!(matches!(
2797 parser.parse(b"\x1b[57375u").first(),
2798 Some(Event::Key(k)) if k.code == KeyCode::F(12)
2799 ));
2800 assert!(matches!(
2802 parser.parse(b"\x1b[57387u").first(),
2803 Some(Event::Key(k)) if k.code == KeyCode::F(24)
2804 ));
2805 }
2806
2807 #[test]
2808 fn kitty_keyboard_ascii_as_standard() {
2809 let mut parser = InputParser::new();
2810 assert!(matches!(
2812 parser.parse(b"\x1b[9u").first(),
2813 Some(Event::Key(k)) if k.code == KeyCode::Tab
2814 ));
2815 assert!(matches!(
2816 parser.parse(b"\x1b[13u").first(),
2817 Some(Event::Key(k)) if k.code == KeyCode::Enter
2818 ));
2819 assert!(matches!(
2820 parser.parse(b"\x1b[27u").first(),
2821 Some(Event::Key(k)) if k.code == KeyCode::Escape
2822 ));
2823 assert!(matches!(
2824 parser.parse(b"\x1b[127u").first(),
2825 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2826 ));
2827 assert!(matches!(
2829 parser.parse(b"\x1b[8u").first(),
2830 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2831 ));
2832 }
2833
2834 #[test]
2837 fn osc52_clipboard_bel_terminated() {
2838 let mut parser = InputParser::new();
2839 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x07");
2842 assert!(matches!(
2843 events.first(),
2844 Some(Event::Clipboard(c)) if c.content == "hello" && c.source == ClipboardSource::Osc52
2845 ));
2846 }
2847
2848 #[test]
2849 fn osc52_clipboard_st_terminated() {
2850 let mut parser = InputParser::new();
2851 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x1b\\");
2853 assert!(matches!(
2854 events.first(),
2855 Some(Event::Clipboard(c)) if c.content == "hello"
2856 ));
2857 }
2858
2859 #[test]
2862 fn dcs_xtgettcap_reply_produces_no_events() {
2863 let mut parser = InputParser::new();
2864 let events = parser.parse(b"\x1bP1+r524742=8/8/8\x1b\\");
2868 assert!(
2869 events.is_empty(),
2870 "a DCS reply must produce no events, got: {events:?}"
2871 );
2872 }
2873
2874 #[test]
2875 fn dcs_then_real_key_recovers_to_ground() {
2876 let mut parser = InputParser::new();
2877 let events = parser.parse(b"\x1bP1+r524742=8/8/8\x1b\\a");
2880 assert!(
2881 matches!(events.as_slice(), [Event::Key(k)] if k.code == KeyCode::Char('a')),
2882 "parser must recover to Ground after a DCS, got: {events:?}"
2883 );
2884 }
2885
2886 #[test]
2887 fn dcs_bel_terminated_is_ignored() {
2888 let mut parser = InputParser::new();
2889 let events = parser.parse(b"\x1bPsome-payload\x07b");
2890 assert!(
2891 matches!(events.as_slice(), [Event::Key(k)] if k.code == KeyCode::Char('b')),
2892 "BEL-terminated DCS ignored, then key parses, got: {events:?}"
2893 );
2894 }
2895
2896 #[test]
2897 fn sos_pm_apc_introducers_stay_alt_keys() {
2898 for &introducer in b"X^_" {
2902 let mut parser = InputParser::new();
2903 let events = parser.parse(&[0x1b, introducer]);
2904 assert!(
2905 matches!(
2906 events.as_slice(),
2907 [Event::Key(k)]
2908 if k.code == KeyCode::Char(introducer as char)
2909 && k.modifiers.contains(Modifiers::ALT)
2910 ),
2911 "ESC {} must stay an Alt key, got: {events:?}",
2912 introducer as char
2913 );
2914 }
2915 }
2916
2917 #[test]
2918 fn dcs_esc_then_csi_recovers_to_arrow_key() {
2919 let mut parser = InputParser::new();
2920 let events = parser.parse(b"\x1bP payload \x1b[A");
2923 assert!(
2924 matches!(events.as_slice(), [Event::Key(k)] if k.code == KeyCode::Up),
2925 "ESC-mid-DCS then a CSI must recover and parse the arrow, got: {events:?}"
2926 );
2927 }
2928
2929 #[test]
2930 fn dcs_aborts_on_control_char_so_input_is_not_swallowed() {
2931 let mut parser = InputParser::new();
2932 let events = parser.parse(b"\x1bPunterminated\r");
2936 assert!(
2937 !events.is_empty(),
2938 "a control char must abort a stuck DCS and emit the key, got: {events:?}"
2939 );
2940 }
2941
2942 #[test]
2943 fn osc52_clipboard_primary_selection() {
2944 let mut parser = InputParser::new();
2945 let events = parser.parse(b"\x1b]52;p;YWJj\x07");
2948 assert!(matches!(
2949 events.first(),
2950 Some(Event::Clipboard(c)) if c.content == "abc"
2951 ));
2952 }
2953
2954 #[test]
2957 fn ctrl_space_is_null() {
2958 let mut parser = InputParser::new();
2959 let events = parser.parse(&[0x00]);
2960 assert!(matches!(
2961 events.first(),
2962 Some(Event::Key(k)) if k.code == KeyCode::Null
2963 ));
2964 }
2965
2966 #[test]
2967 fn all_ctrl_letter_keys() {
2968 let mut parser = InputParser::new();
2969 for byte in 0x01..=0x1Au8 {
2971 let events = parser.parse(&[byte]);
2972 assert_eq!(
2973 events.len(),
2974 1,
2975 "Ctrl+{} should produce one event",
2976 (byte + b'a' - 1) as char
2977 );
2978 match byte {
2979 0x08 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace)),
2980 0x09 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Tab)),
2981 0x0D => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Enter)),
2982 _ => {
2983 let expected_char = (byte + b'a' - 1) as char;
2984 match &events[0] {
2985 Event::Key(k) => {
2986 assert_eq!(
2987 k.code,
2988 KeyCode::Char(expected_char),
2989 "Byte 0x{byte:02X} should produce Ctrl+{expected_char}"
2990 );
2991 assert!(
2992 k.modifiers.contains(Modifiers::CTRL),
2993 "Byte 0x{byte:02X} should have Ctrl modifier"
2994 );
2995 }
2996 other => {
2997 panic!("Byte 0x{byte:02X}: expected Key event, got {other:?}");
2998 }
2999 }
3000 }
3001 }
3002 }
3003 }
3004
3005 #[test]
3008 fn utf8_3byte_cjk() {
3009 let mut parser = InputParser::new();
3010 let events = parser.parse(&[0xE4, 0xB8, 0xAD]);
3012 assert!(matches!(
3013 events.first(),
3014 Some(Event::Key(k)) if k.code == KeyCode::Char('中')
3015 ));
3016 }
3017
3018 #[test]
3019 fn utf8_4byte_emoji() {
3020 let mut parser = InputParser::new();
3021 let events = parser.parse(&[0xF0, 0x9F, 0xA6, 0x80]);
3023 assert!(matches!(
3024 events.first(),
3025 Some(Event::Key(k)) if k.code == KeyCode::Char('🦀')
3026 ));
3027 }
3028
3029 #[test]
3032 fn empty_input_returns_no_events() {
3033 let mut parser = InputParser::new();
3034 let events = parser.parse(b"");
3035 assert!(events.is_empty());
3036 }
3037
3038 #[test]
3041 fn unknown_csi_tilde_ignored() {
3042 let mut parser = InputParser::new();
3043 let events = parser.parse(b"\x1b[99~");
3045 assert!(events.is_empty());
3046
3047 let events = parser.parse(b"a");
3049 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('a')));
3050 }
3051
3052 #[test]
3055 fn alt_special_chars() {
3056 let mut parser = InputParser::new();
3057
3058 let events = parser.parse(b"\x1b ");
3060 assert!(matches!(
3061 events.first(),
3062 Some(Event::Key(k)) if k.code == KeyCode::Char(' ') && k.modifiers.contains(Modifiers::ALT)
3063 ));
3064
3065 let events = parser.parse(b"\x1b5");
3067 assert!(matches!(
3068 events.first(),
3069 Some(Event::Key(k)) if k.code == KeyCode::Char('5') && k.modifiers.contains(Modifiers::ALT)
3070 ));
3071
3072 let events = parser.parse(b"\x1b}");
3074 assert!(matches!(
3075 events.first(),
3076 Some(Event::Key(k)) if k.code == KeyCode::Char('}') && k.modifiers.contains(Modifiers::ALT)
3077 ));
3078 }
3079
3080 #[test]
3081 fn alt_ctrl_key_combinations() {
3082 let mut parser = InputParser::new();
3083
3084 let events = parser.parse(&[0x1B, 0x01]);
3086 assert_eq!(events.len(), 1);
3087 match &events[0] {
3088 Event::Key(k) => {
3089 assert_eq!(k.code, KeyCode::Char('a'));
3090 assert!(k.modifiers.contains(Modifiers::ALT));
3091 assert!(k.modifiers.contains(Modifiers::CTRL));
3092 }
3093 _ => panic!("Expected Key event"),
3094 }
3095
3096 let events = parser.parse(&[0x1B, 0x08]);
3100 assert_eq!(events.len(), 1);
3101 match &events[0] {
3102 Event::Key(k) => {
3103 assert_eq!(k.code, KeyCode::Backspace);
3104 assert!(k.modifiers.contains(Modifiers::ALT));
3105 }
3106 _ => panic!("Expected Key event"),
3107 }
3108 }
3109
3110 #[test]
3113 fn ss3_arrow_keys() {
3114 let mut parser = InputParser::new();
3115 assert!(matches!(
3116 parser.parse(b"\x1bOA").first(),
3117 Some(Event::Key(k)) if k.code == KeyCode::Up
3118 ));
3119 assert!(matches!(
3120 parser.parse(b"\x1bOB").first(),
3121 Some(Event::Key(k)) if k.code == KeyCode::Down
3122 ));
3123 assert!(matches!(
3124 parser.parse(b"\x1bOC").first(),
3125 Some(Event::Key(k)) if k.code == KeyCode::Right
3126 ));
3127 assert!(matches!(
3128 parser.parse(b"\x1bOD").first(),
3129 Some(Event::Key(k)) if k.code == KeyCode::Left
3130 ));
3131 }
3132
3133 #[test]
3136 fn xterm_modifier_encoding() {
3137 assert_eq!(InputParser::modifiers_from_xterm(1), Modifiers::NONE);
3139 assert_eq!(InputParser::modifiers_from_xterm(2), Modifiers::SHIFT);
3140 assert_eq!(InputParser::modifiers_from_xterm(3), Modifiers::ALT);
3141 assert_eq!(
3142 InputParser::modifiers_from_xterm(4),
3143 Modifiers::SHIFT | Modifiers::ALT
3144 );
3145 assert_eq!(InputParser::modifiers_from_xterm(5), Modifiers::CTRL);
3146 assert_eq!(
3147 InputParser::modifiers_from_xterm(6),
3148 Modifiers::SHIFT | Modifiers::CTRL
3149 );
3150 assert_eq!(InputParser::modifiers_from_xterm(9), Modifiers::SUPER);
3151 }
3152
3153 #[test]
3156 fn ss3_interrupted_by_esc() {
3157 let mut parser = InputParser::new();
3158 let events = parser.parse(b"\x1bO\x1b[A");
3160 assert!(matches!(
3162 events.first(),
3163 Some(Event::Key(k)) if k.code == KeyCode::Up
3164 ));
3165 }
3166
3167 #[test]
3170 fn kitty_keyboard_reserved_keycode_ignored() {
3171 let mut parser = InputParser::new();
3172 let events = parser.parse(b"\x1b[57360u");
3174 assert!(events.is_empty());
3175
3176 let events = parser.parse(b"x");
3178 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('x')));
3179 }
3180 #[test]
3181 fn utf8_invalid_sequence_emits_replacement() {
3182 let mut parser = InputParser::new();
3183
3184 let events = parser.parse(&[0xE0, 0x41]);
3188 assert_eq!(events.len(), 2);
3189
3190 match &events[0] {
3191 Event::Key(k) => assert_eq!(k.code, KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
3192 _ => panic!("Expected replacement character"),
3193 }
3194
3195 match &events[1] {
3196 Event::Key(k) => assert_eq!(k.code, KeyCode::Char('A')),
3197 _ => panic!("Expected character 'A'"),
3198 }
3199 }
3200
3201 #[test]
3202 fn utf8_invalid_lead_emits_replacement() {
3203 let mut parser = InputParser::new();
3204
3205 let events = parser.parse(&[0xC0, b'a']);
3207 assert!(
3208 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
3209 "Expected replacement for invalid lead"
3210 );
3211 assert!(
3212 events
3213 .iter()
3214 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('a'))),
3215 "Expected subsequent ASCII to be preserved"
3216 );
3217
3218 let events = parser.parse(&[0xF5, b'b']);
3220 assert!(
3221 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
3222 "Expected replacement for out-of-range lead"
3223 );
3224 assert!(
3225 events
3226 .iter()
3227 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('b'))),
3228 "Expected subsequent ASCII to be preserved"
3229 );
3230 }
3231}
3232
3233#[cfg(test)]
3234mod proptest_fuzz {
3235 use super::*;
3236 use proptest::prelude::*;
3237
3238 fn arb_byte() -> impl Strategy<Value = u8> {
3242 any::<u8>()
3243 }
3244
3245 fn arb_byte_vec(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
3246 prop::collection::vec(arb_byte(), 0..=max_len)
3247 }
3248
3249 fn csi_sequence() -> impl Strategy<Value = Vec<u8>> {
3251 let params = prop::collection::vec(0x30u8..=0x3F, 0..=20);
3252 let final_byte = 0x40u8..=0x7E;
3253 (params, final_byte).prop_map(|(p, f)| {
3254 let mut buf = vec![0x1B, b'['];
3255 buf.extend_from_slice(&p);
3256 buf.push(f);
3257 buf
3258 })
3259 }
3260
3261 fn osc_sequence() -> impl Strategy<Value = Vec<u8>> {
3263 let content = prop::collection::vec(0x20u8..=0x7E, 0..=64);
3264 let terminator = prop_oneof![
3265 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
3268 (content, terminator).prop_map(|(c, t)| {
3269 let mut buf = vec![0x1B, b']'];
3270 buf.extend_from_slice(&c);
3271 buf.extend_from_slice(&t);
3272 buf
3273 })
3274 }
3275
3276 fn ss3_sequence() -> impl Strategy<Value = Vec<u8>> {
3278 (0x40u8..=0x7E).prop_map(|f| vec![0x1B, b'O', f])
3279 }
3280
3281 fn paste_sequence() -> impl Strategy<Value = Vec<u8>> {
3283 prop::collection::vec(0x20u8..=0x7E, 0..=128).prop_map(|content| {
3284 let mut buf = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
3285 buf.extend_from_slice(&content);
3286 buf.extend_from_slice(b"\x1b[201~");
3287 buf
3288 })
3289 }
3290
3291 fn mixed_adversarial() -> impl Strategy<Value = Vec<u8>> {
3293 let fragment = prop_oneof![
3294 csi_sequence(),
3295 osc_sequence(),
3296 ss3_sequence(),
3297 paste_sequence(),
3298 arb_byte_vec(16), Just(vec![0x1B]), Just(vec![0x1B, b'[']), Just(vec![0x1B, b']']), prop::collection::vec(0x80u8..=0xFF, 1..=4), ];
3304 prop::collection::vec(fragment, 1..=8)
3305 .prop_map(|frags| frags.into_iter().flatten().collect())
3306 }
3307
3308 proptest! {
3311 #[test]
3313 fn random_bytes_never_panic(input in arb_byte_vec(512)) {
3314 let mut parser = InputParser::new();
3315 let _ = parser.parse(&input);
3316 }
3317
3318 #[test]
3320 fn parser_recovers_after_garbage(input in arb_byte_vec(256)) {
3321 let mut parser = InputParser::new();
3322 let _ = parser.parse(&input);
3323
3324 let events = parser.parse(b"z");
3326 let _ = events;
3329 }
3330
3331 #[test]
3333 fn mixed_sequences_never_panic(input in mixed_adversarial()) {
3334 let mut parser = InputParser::new();
3335 let _ = parser.parse(&input);
3336 }
3337
3338 #[test]
3340 fn events_are_well_formed(input in arb_byte_vec(256)) {
3341 let mut parser = InputParser::new();
3342 let events = parser.parse(&input);
3343 for event in &events {
3344 let _ = format!("{event:?}");
3346 }
3347 }
3348
3349 #[test]
3351 fn csi_event_count_bounded(seq in csi_sequence()) {
3352 let mut parser = InputParser::new();
3353 let events = parser.parse(&seq);
3354 prop_assert!(events.len() <= seq.len(),
3355 "Got {} events from {} bytes", events.len(), seq.len());
3356 }
3357
3358 #[test]
3360 fn osc_event_count_bounded(seq in osc_sequence()) {
3361 let mut parser = InputParser::new();
3362 let events = parser.parse(&seq);
3363 prop_assert!(events.len() <= seq.len(),
3364 "Got {} events from {} bytes", events.len(), seq.len());
3365 }
3366
3367 #[test]
3369 fn paste_content_bounded(content in prop::collection::vec(arb_byte(), 0..=2048)) {
3370 let mut parser = InputParser::new();
3371 let mut input = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
3372 input.extend_from_slice(&content);
3373 input.extend_from_slice(b"\x1b[201~");
3374
3375 let events = parser.parse(&input);
3376 for event in &events {
3377 if let Event::Paste(p) = event {
3378 prop_assert!(p.text.len() <= MAX_PASTE_LEN,
3379 "Paste text {} exceeds limit {}", p.text.len(), MAX_PASTE_LEN);
3380 }
3381 }
3382 }
3383
3384 #[test]
3386 fn incremental_matches_bulk(input in arb_byte_vec(128)) {
3387 let mut bulk_parser = InputParser::new();
3388 let bulk_events = bulk_parser.parse(&input);
3389
3390 let mut incr_parser = InputParser::new();
3391 let mut incr_events = Vec::new();
3392 for byte in &input {
3393 incr_events.extend(incr_parser.parse(std::slice::from_ref(byte)));
3394 }
3395
3396 let bulk_dbg: Vec<String> = bulk_events.iter().map(|e| format!("{e:?}")).collect();
3397 let incr_dbg: Vec<String> = incr_events.iter().map(|e| format!("{e:?}")).collect();
3398 prop_assert_eq!(bulk_dbg, incr_dbg,
3399 "Bulk vs incremental mismatch for input {:?}", input);
3400 }
3401
3402 #[test]
3405 fn deterministic_output(input in arb_byte_vec(128)) {
3406 let mut parser1 = InputParser::new();
3407 let events1 = parser1.parse(&input);
3408
3409 let mut parser2 = InputParser::new();
3410 let events2 = parser2.parse(&input);
3411
3412 let dbg1: Vec<String> = events1.iter().map(|e| format!("{e:?}")).collect();
3413 let dbg2: Vec<String> = events2.iter().map(|e| format!("{e:?}")).collect();
3414 prop_assert_eq!(dbg1, dbg2);
3415 }
3416 }
3417
3418 #[test]
3422 fn no_quadratic_blowup() {
3423 let mut parser = InputParser::new();
3424
3425 let garbage: Vec<u8> = (0..65536).map(|i| (i % 256) as u8).collect();
3427 let _ = parser.parse(&garbage);
3428
3429 let events = parser.parse(b"a");
3431 let _ = events; }
3433
3434 #[test]
3436 fn oversized_csi_transitions_to_ignore() {
3437 let mut parser = InputParser::new();
3438
3439 let mut input = vec![0x1B, b'['];
3441 input.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
3442 input.push(b'm');
3443
3444 let _ = parser.parse(&input);
3445
3446 let events = parser.parse(b"x");
3448 assert_eq!(events.len(), 1);
3449 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('x')));
3450 }
3451
3452 #[test]
3454 fn oversized_osc_transitions_to_ignore() {
3455 let mut parser = InputParser::new();
3456
3457 let mut input = vec![0x1B, b']'];
3459 input.extend(std::iter::repeat_n(b'a', MAX_OSC_LEN + 100));
3460 input.push(0x07); let _ = parser.parse(&input);
3463
3464 let events = parser.parse(b"y");
3466 assert_eq!(events.len(), 1);
3467 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('y')));
3468 }
3469
3470 #[test]
3472 fn rapid_esc_toggle() {
3473 let mut parser = InputParser::new();
3474
3475 let input: Vec<u8> = vec![0x1B; 1000];
3477 let _ = parser.parse(&input);
3478
3479 let events = parser.parse(b"k");
3481 assert!(!events.is_empty());
3482 }
3483
3484 #[test]
3486 fn unterminated_paste_recovery() {
3487 let mut parser = InputParser::new();
3488
3489 let mut input = b"\x1b[200~".to_vec();
3491 input.extend(std::iter::repeat_n(b'x', 2048));
3492
3493 let _ = parser.parse(&input);
3494
3495 let events = parser.parse(b"\x1b[201~");
3497 assert!(
3498 !events.is_empty(),
3499 "Parser should emit paste event on terminator"
3500 );
3501 }
3502
3503 #[test]
3505 fn truncated_utf8_lead_bytes() {
3506 let mut parser = InputParser::new();
3507
3508 for lead in [0xC2, 0xE0, 0xF0] {
3510 let _ = parser.parse(&[lead]);
3511 let events = parser.parse(b"a");
3513 let _ = events;
3515 }
3516 }
3517
3518 #[test]
3520 fn null_bytes_interleaved() {
3521 let mut parser = InputParser::new();
3522
3523 let input = b"\x00A\x00\x1b[A\x00B\x00";
3524 let events = parser.parse(input);
3525 assert!(
3527 events.len() >= 2,
3528 "Expected at least 2 events, got {}",
3529 events.len()
3530 );
3531 }
3532
3533 fn osc52_sequence() -> impl Strategy<Value = Vec<u8>> {
3537 let selector = prop_oneof![Just(b'c'), Just(b'p'), Just(b's')];
3538 let payload = prop::collection::vec(
3540 prop_oneof![
3541 0x41u8..=0x5A, 0x61u8..=0x7A, 0x30u8..=0x39, Just(b'+'),
3545 Just(b'/'),
3546 Just(b'='),
3547 ],
3548 0..=128,
3549 );
3550 let terminator = prop_oneof![
3551 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
3554 (selector, payload, terminator).prop_map(|(sel, pay, term)| {
3555 let mut buf = vec![0x1B, b']', b'5', b'2', b';', sel, b';'];
3556 buf.extend_from_slice(&pay);
3557 buf.extend_from_slice(&term);
3558 buf
3559 })
3560 }
3561
3562 fn sgr_mouse_sequence() -> impl Strategy<Value = Vec<u8>> {
3564 let button_code = 0u16..128;
3565 let x = 1u16..300;
3566 let y = 1u16..100;
3567 let final_byte = prop_oneof![Just(b'M'), Just(b'm')];
3568 (button_code, x, y, final_byte)
3569 .prop_map(|(btn, x, y, fb)| format!("\x1b[<{btn};{x};{y}{}", fb as char).into_bytes())
3570 }
3571
3572 fn kitty_keyboard_sequence() -> impl Strategy<Value = Vec<u8>> {
3574 let keycode = prop_oneof![
3575 0x20u32..0x7F, 0x57344u32..0x57400, 0x100u32..0x200, ];
3579 let modifier = 1u32..16;
3580 let kind = prop_oneof![Just(1u32), Just(2u32), Just(3u32)]; (keycode, prop::option::of(modifier), prop::option::of(kind)).prop_map(
3582 |(kc, mods, kind)| match (mods, kind) {
3583 (Some(m), Some(k)) => format!("\x1b[{kc};{m}:{k}u").into_bytes(),
3584 (Some(m), None) => format!("\x1b[{kc};{m}u").into_bytes(),
3585 _ => format!("\x1b[{kc}u").into_bytes(),
3586 },
3587 )
3588 }
3589
3590 proptest! {
3591 #[test]
3595 fn osc52_never_panics(seq in osc52_sequence()) {
3596 let mut parser = InputParser::new();
3597 let events = parser.parse(&seq);
3598 for event in &events {
3600 if let Event::Clipboard(c) = event {
3601 prop_assert!(!c.content.is_empty() || c.content.is_empty(),
3602 "Clipboard event must have a content field");
3603 }
3604 }
3605 }
3606
3607 #[test]
3609 fn osc52_corrupt_base64_safe(payload in arb_byte_vec(128)) {
3610 let mut parser = InputParser::new();
3611 let mut input = b"\x1b]52;c;".to_vec();
3612 input.extend_from_slice(&payload);
3613 input.push(0x07); let _ = parser.parse(&input);
3615 }
3616
3617 #[test]
3621 fn sgr_mouse_never_panics(seq in sgr_mouse_sequence()) {
3622 let mut parser = InputParser::new();
3623 let events = parser.parse(&seq);
3624 for event in &events {
3625 let _ = format!("{event:?}");
3627 }
3628 }
3629
3630 #[test]
3632 fn sgr_mouse_extreme_coords(
3633 btn in 0u16..128,
3634 x in 0u16..=65535,
3635 y in 0u16..=65535,
3636 ) {
3637 let mut parser = InputParser::new();
3638 let input = format!("\x1b[<{btn};{x};{y}M").into_bytes();
3639 let events = parser.parse(&input);
3640 for event in &events {
3641 if let Event::Mouse(m) = event {
3642 prop_assert!(m.x <= x, "Mouse x {} > input x {}", m.x, x);
3643 prop_assert!(m.y <= y, "Mouse y {} > input y {}", m.y, y);
3644 }
3645 }
3646 }
3647
3648 #[test]
3652 fn kitty_keyboard_never_panics(seq in kitty_keyboard_sequence()) {
3653 let mut parser = InputParser::new();
3654 let _ = parser.parse(&seq);
3655 }
3656
3657 #[test]
3661 fn truncated_csi_then_valid(
3662 params in prop::collection::vec(0x30u8..=0x3F, 1..=10),
3663 valid_char in 0x20u8..0x7F,
3664 ) {
3665 let mut parser = InputParser::new();
3666
3667 let mut partial = vec![0x1B, b'['];
3669 partial.extend_from_slice(¶ms);
3670 let _ = parser.parse(&partial);
3671
3672 let events = parser.parse(&[0x1B, b'[', b'A']); let _ = events;
3677
3678 let events = parser.parse(&[valid_char]);
3680 let _ = events;
3681 }
3682
3683 #[test]
3685 fn truncated_osc_then_valid(
3686 content in prop::collection::vec(0x20u8..=0x7E, 1..=32),
3687 ) {
3688 let mut parser = InputParser::new();
3689
3690 let mut partial = vec![0x1B, b']'];
3692 partial.extend_from_slice(&content);
3693 let _ = parser.parse(&partial);
3694
3695 let events = parser.parse(b"\x1bz");
3697 let _ = events;
3698 }
3699
3700 #[test]
3704 fn csi_near_limit_produces_event(
3705 fill_byte in 0x30u8..=0x39, ) {
3707 let mut parser = InputParser::new();
3708
3709 let mut input = vec![0x1B, b'['];
3710 input.extend(std::iter::repeat_n(fill_byte, MAX_CSI_LEN - 1));
3712 input.push(b'm'); let events = parser.parse(&input);
3715 let _ = events;
3718
3719 let events = parser.parse(b"a");
3721 prop_assert!(!events.is_empty(), "Parser stuck after near-limit CSI");
3722 }
3723
3724 #[test]
3726 fn osc_near_limit_processes(
3727 fill_byte in 0x20u8..=0x7E,
3728 ) {
3729 let mut parser = InputParser::new();
3730
3731 let mut input = vec![0x1B, b']'];
3732 input.extend(std::iter::repeat_n(fill_byte, MAX_OSC_LEN - 1));
3733 input.push(0x07); let _ = parser.parse(&input);
3736
3737 let events = parser.parse(b"b");
3739 prop_assert!(!events.is_empty(), "Parser stuck after near-limit OSC");
3740 }
3741
3742 #[test]
3746 fn consecutive_pastes_emit_events(count in 2usize..=5) {
3747 let mut parser = InputParser::new();
3748 let mut input = Vec::new();
3749
3750 for i in 0..count {
3751 input.extend_from_slice(b"\x1b[200~");
3752 input.extend_from_slice(format!("paste_{i}").as_bytes());
3753 input.extend_from_slice(b"\x1b[201~");
3754 }
3755
3756 let events = parser.parse(&input);
3757 let paste_events: Vec<_> = events.iter()
3758 .filter(|e| matches!(e, Event::Paste(_)))
3759 .collect();
3760
3761 prop_assert_eq!(paste_events.len(), count,
3762 "Expected {} paste events, got {}", count, paste_events.len());
3763 }
3764
3765 #[test]
3767 fn paste_with_invalid_utf8(content in arb_byte_vec(256)) {
3768 let mut parser = InputParser::new();
3769 let mut input = b"\x1b[200~".to_vec();
3770 input.extend_from_slice(&content);
3771 input.extend_from_slice(b"\x1b[201~");
3772
3773 let events = parser.parse(&input);
3774 for event in &events {
3775 if let Event::Paste(p) = event {
3776 prop_assert!(p.text.is_char_boundary(0), "Paste text is not valid UTF-8");
3778 }
3779 }
3780 }
3781
3782 #[test]
3786 fn recovery_via_esc_reset(garbage in arb_byte_vec(256)) {
3787 let mut parser = InputParser::new();
3788 let _ = parser.parse(&garbage);
3789
3790 let _ = parser.parse(b"\x07\x1b\\\x1b");
3793 let _ = parser.parse(b"\x1b");
3794
3795 let _ = parser.parse(b"z");
3797
3798 let events = parser.parse(b"q");
3800 prop_assert!(!events.is_empty(),
3803 "Parser did not recover after garbage + reset");
3804 }
3805 }
3806}