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 Utf8 {
77 collected: u8,
79 expected: u8,
81 },
82 MouseX10 { collected: u8, buffer: [u8; 3] },
84}
85
86#[derive(Debug)]
96pub struct InputParser {
97 state: ParserState,
99 buffer: Vec<u8>,
101 paste_buffer: Vec<u8>,
103 utf8_buffer: [u8; 4],
105 in_paste: bool,
107 pending_event: Option<Event>,
109 expect_x10_mouse: bool,
118 allow_legacy_mouse: bool,
125}
126
127impl Default for InputParser {
128 fn default() -> Self {
129 Self::new()
130 }
131}
132
133impl InputParser {
134 #[inline]
135 fn event_reserve_hint(input_len: usize) -> usize {
136 input_len.saturating_add(1).min(MAX_EVENT_RESERVE_HINT)
137 }
138
139 #[must_use]
141 pub fn new() -> Self {
142 Self {
143 state: ParserState::Ground,
144 buffer: Vec::with_capacity(64),
145 paste_buffer: Vec::new(),
146 utf8_buffer: [0; 4],
147 in_paste: false,
148 pending_event: None,
149 expect_x10_mouse: false,
150 allow_legacy_mouse: false,
151 }
152 }
153
154 pub fn set_expect_x10_mouse(&mut self, enabled: bool) {
159 self.expect_x10_mouse = enabled;
160 }
161
162 pub fn set_allow_legacy_mouse(&mut self, enabled: bool) {
169 self.allow_legacy_mouse = enabled;
170 }
171
172 #[must_use]
175 pub const fn has_pending_timeout_state(&self) -> bool {
176 matches!(self.state, ParserState::Escape | ParserState::Utf8 { .. })
177 }
178
179 pub fn timeout(&mut self) -> Option<Event> {
184 match self.state {
185 ParserState::Escape => {
186 self.state = ParserState::Ground;
187 Some(Event::Key(KeyEvent::new(KeyCode::Escape)))
188 }
189 ParserState::Utf8 { .. } => {
190 self.state = ParserState::Ground;
192 self.utf8_buffer = [0; 4];
193 Some(Event::Key(KeyEvent::new(KeyCode::Char(
194 std::char::REPLACEMENT_CHARACTER,
195 ))))
196 }
197 _ => None,
198 }
199 }
200
201 pub fn parse(&mut self, input: &[u8]) -> Vec<Event> {
203 let mut events = Vec::with_capacity(Self::event_reserve_hint(input.len()));
204 self.parse_with(input, |event| events.push(event));
205 events
206 }
207
208 pub fn parse_with<F>(&mut self, input: &[u8], mut emit: F)
210 where
211 F: FnMut(Event),
212 {
213 let span = debug_span!("event.normalize", raw_byte_count = input.len());
214 let _guard = span.enter();
215 trace!("raw input bytes: {} bytes", input.len());
216
217 for &byte in input {
218 if let Some(event) = self.process_byte(byte) {
219 debug!(event_type = event.event_type_label(), "normalized event");
220 emit(event);
221 }
222 if let Some(pending) = self.pending_event.take() {
223 debug!(event_type = pending.event_type_label(), "normalized event");
224 emit(pending);
225 }
226 }
227 }
228
229 pub fn parse_into(&mut self, input: &[u8], events: &mut Vec<Event>) {
234 let needed = Self::event_reserve_hint(input.len());
235 let available = events.capacity().saturating_sub(events.len());
236 if available < needed {
237 events.reserve(needed - available);
238 }
239 self.parse_with(input, |event| events.push(event));
240 }
241
242 fn process_byte(&mut self, byte: u8) -> Option<Event> {
244 if self.in_paste {
246 return self.process_paste_byte(byte);
247 }
248
249 match self.state {
250 ParserState::Ground => self.process_ground(byte),
251 ParserState::Escape => self.process_escape(byte),
252 ParserState::Csi => self.process_csi(byte),
253 ParserState::CsiParam => self.process_csi_param(byte),
254 ParserState::CsiIgnore => self.process_csi_ignore(byte),
255 ParserState::Ss3 => self.process_ss3(byte),
256 ParserState::Osc => self.process_osc(byte),
257 ParserState::OscContent => self.process_osc_content(byte),
258 ParserState::OscEscape => self.process_osc_escape(byte),
259 ParserState::OscIgnore => self.process_osc_ignore(byte),
260 ParserState::Utf8 {
261 collected,
262 expected,
263 } => self.process_utf8(byte, collected, expected),
264 ParserState::MouseX10 { .. } => self.process_mouse_x10(byte),
265 }
266 }
267
268 fn process_ground(&mut self, byte: u8) -> Option<Event> {
270 match byte {
271 0x1B => {
273 self.state = ParserState::Escape;
274 None
275 }
276 0x9B => {
278 self.state = ParserState::Csi;
279 self.buffer.clear();
280 None
281 }
282 0x8F => {
284 self.state = ParserState::Ss3;
285 None
286 }
287 0x9D => {
289 self.state = ParserState::Osc;
290 self.buffer.clear();
291 None
292 }
293 0x00 => Some(Event::Key(KeyEvent::new(KeyCode::Null))),
295 0x08 => Some(Event::Key(KeyEvent::new(KeyCode::Backspace))),
297 0x09 => Some(Event::Key(KeyEvent::new(KeyCode::Tab))),
299 0x0D => Some(Event::Key(KeyEvent::new(KeyCode::Enter))),
301 0x01..=0x07 | 0x0A..=0x0C | 0x0E..=0x1A => {
303 let c = (byte + b'a' - 1) as char;
304 Some(Event::Key(
305 KeyEvent::new(KeyCode::Char(c)).with_modifiers(Modifiers::CTRL),
306 ))
307 }
308 0x1C => Some(Event::Key(
310 KeyEvent::new(KeyCode::Char('\\')).with_modifiers(Modifiers::CTRL),
311 )),
312 0x1D => Some(Event::Key(
313 KeyEvent::new(KeyCode::Char(']')).with_modifiers(Modifiers::CTRL),
314 )),
315 0x1E => Some(Event::Key(
316 KeyEvent::new(KeyCode::Char('^')).with_modifiers(Modifiers::CTRL),
317 )),
318 0x1F => Some(Event::Key(
319 KeyEvent::new(KeyCode::Char('_')).with_modifiers(Modifiers::CTRL),
320 )),
321 0x7F => Some(Event::Key(KeyEvent::new(KeyCode::Backspace))),
323 0x20..=0x7E => Some(Event::Key(KeyEvent::new(KeyCode::Char(byte as char)))),
325 0xC2..=0xDF => {
327 self.utf8_buffer[0] = byte;
328 self.state = ParserState::Utf8 {
329 collected: 1,
330 expected: 2,
331 };
332 None
333 }
334 0xE0..=0xEF => {
335 self.utf8_buffer[0] = byte;
336 self.state = ParserState::Utf8 {
337 collected: 1,
338 expected: 3,
339 };
340 None
341 }
342 0xF0..=0xF4 => {
343 self.utf8_buffer[0] = byte;
344 self.state = ParserState::Utf8 {
345 collected: 1,
346 expected: 4,
347 };
348 None
349 }
350 0xC0..=0xC1 | 0xF5..=0xFF => Some(Event::Key(KeyEvent::new(KeyCode::Char(
352 std::char::REPLACEMENT_CHARACTER,
353 )))),
354 _ => None,
356 }
357 }
358
359 fn process_escape(&mut self, byte: u8) -> Option<Event> {
361 match byte {
362 b'[' => {
364 self.state = ParserState::Csi;
365 self.buffer.clear();
366 None
367 }
368 b'O' => {
370 self.state = ParserState::Ss3;
371 None
372 }
373 b']' => {
375 self.state = ParserState::Osc;
376 self.buffer.clear();
377 None
378 }
379 0x1B => {
382 self.state = ParserState::Ground;
383 Some(Event::Key(
384 KeyEvent::new(KeyCode::Escape).with_modifiers(Modifiers::ALT),
385 ))
386 }
387 0x00..=0x1F => {
389 self.state = ParserState::Ground;
390 if let Some(mut event) = self.process_ground(byte) {
393 if let Event::Key(ref mut key) = event {
394 key.modifiers |= Modifiers::ALT;
395 }
396 Some(event)
397 } else {
398 None
399 }
400 }
401 0x20..=0x7E => {
403 self.state = ParserState::Ground;
404 Some(Event::Key(
405 KeyEvent::new(KeyCode::Char(byte as char)).with_modifiers(Modifiers::ALT),
406 ))
407 }
408 0x7F => {
410 self.state = ParserState::Ground;
411 Some(Event::Key(
412 KeyEvent::new(KeyCode::Backspace).with_modifiers(Modifiers::ALT),
413 ))
414 }
415 _ => {
417 self.state = ParserState::Ground;
418 None
419 }
420 }
421 }
422
423 fn process_csi(&mut self, byte: u8) -> Option<Event> {
425 if byte == 0x1B {
427 self.state = ParserState::Escape;
428 self.buffer.clear();
429 return None;
430 }
431
432 self.buffer.push(byte);
433
434 match byte {
435 0x20..=0x3F => {
437 self.state = ParserState::CsiParam;
438 None
439 }
440 0x40..=0x7E => {
442 if self.expect_x10_mouse && byte == b'M' && self.buffer.len() == 1 {
446 self.state = ParserState::MouseX10 {
447 collected: 0,
448 buffer: [0; 3],
449 };
450 self.buffer.clear();
451 return None;
452 }
453
454 self.state = ParserState::Ground;
455 self.parse_csi_sequence()
456 }
457 _ => {
459 self.state = ParserState::Ground;
460 self.buffer.clear();
461 None
462 }
463 }
464 }
465
466 fn process_csi_param(&mut self, byte: u8) -> Option<Event> {
468 if byte == 0x1B {
470 self.state = ParserState::Escape;
471 self.buffer.clear();
472 return None;
473 }
474
475 if self.buffer.len() >= MAX_CSI_LEN {
477 self.state = ParserState::CsiIgnore;
478 self.buffer.clear();
479 return None;
480 }
481
482 self.buffer.push(byte);
483
484 match byte {
485 0x20..=0x3F => None,
487 0x40..=0x7E => {
489 self.state = ParserState::Ground;
490 self.parse_csi_sequence()
491 }
492 _ => {
494 self.state = ParserState::Ground;
495 self.buffer.clear();
496 self.process_ground(byte)
497 }
498 }
499 }
500
501 fn process_csi_ignore(&mut self, byte: u8) -> Option<Event> {
503 if byte == 0x1B {
505 self.state = ParserState::Escape;
506 return None;
507 }
508
509 if (0x40..=0x7E).contains(&byte) {
511 self.state = ParserState::Ground;
512 None
513 } else if (0x20..=0x3F).contains(&byte) {
514 None
516 } else {
517 self.state = ParserState::Ground;
519 self.process_ground(byte)
520 }
521 }
522
523 fn parse_csi_sequence(&mut self) -> Option<Event> {
525 let seq = std::mem::take(&mut self.buffer);
526 if seq.is_empty() {
527 return None;
528 }
529
530 let final_byte = *seq.last()?;
531 let params = &seq[..seq.len() - 1];
532
533 match (params, final_byte) {
535 ([], b'I') => return Some(Event::Focus(true)),
537 ([], b'O') => return Some(Event::Focus(false)),
538
539 (b"200", b'~') => {
541 self.in_paste = true;
542 self.paste_buffer.clear();
543 self.buffer.clear(); return None;
545 }
546 (b"201", b'~') => {
547 self.in_paste = false;
548 let content = String::from_utf8_lossy(&self.paste_buffer).into_owned();
549 self.paste_buffer.clear();
550 return Some(Event::Paste(PasteEvent::bracketed(content)));
551 }
552
553 _ if params.starts_with(b"<") && (final_byte == b'M' || final_byte == b'm') => {
555 return self.parse_sgr_mouse(params, final_byte);
556 }
557 _ if (self.allow_legacy_mouse || self.expect_x10_mouse) && final_byte == b'M' => {
564 if let Some(event) = self.parse_legacy_mouse(params) {
565 return Some(event);
566 }
567 }
568
569 _ => {}
570 }
571
572 match final_byte {
574 b'A' => Some(Event::Key(self.key_with_modifiers(KeyCode::Up, params))),
575 b'B' => Some(Event::Key(self.key_with_modifiers(KeyCode::Down, params))),
576 b'C' => Some(Event::Key(self.key_with_modifiers(KeyCode::Right, params))),
577 b'D' => Some(Event::Key(self.key_with_modifiers(KeyCode::Left, params))),
578 b'H' => Some(Event::Key(self.key_with_modifiers(KeyCode::Home, params))),
579 b'F' => Some(Event::Key(self.key_with_modifiers(KeyCode::End, params))),
580 b'P' => Some(Event::Key(self.key_with_modifiers(KeyCode::F(1), params))),
581 b'Q' => Some(Event::Key(self.key_with_modifiers(KeyCode::F(2), params))),
582 b'R' => Some(Event::Key(self.key_with_modifiers(KeyCode::F(3), params))),
583 b'S' => Some(Event::Key(self.key_with_modifiers(KeyCode::F(4), params))),
584 b'Z' => Some(Event::Key(
585 self.key_with_modifiers(KeyCode::BackTab, params),
586 )),
587 b'~' => self.parse_csi_tilde(params),
588 b'u' => self.parse_kitty_keyboard(params),
589 _ => None,
590 }
591 }
592
593 fn parse_csi_tilde(&self, params: &[u8]) -> Option<Event> {
595 let num = self.parse_first_param(params)?;
596 let mods = self.parse_modifier_param(params);
597
598 let code = match num {
599 1 => KeyCode::Home,
600 2 => KeyCode::Insert,
601 3 => KeyCode::Delete,
602 4 => KeyCode::End,
603 5 => KeyCode::PageUp,
604 6 => KeyCode::PageDown,
605 15 => KeyCode::F(5),
606 17 => KeyCode::F(6),
607 18 => KeyCode::F(7),
608 19 => KeyCode::F(8),
609 20 => KeyCode::F(9),
610 21 => KeyCode::F(10),
611 23 => KeyCode::F(11),
612 24 => KeyCode::F(12),
613 _ => return None,
614 };
615
616 Some(Event::Key(KeyEvent::new(code).with_modifiers(mods)))
617 }
618
619 fn parse_first_param(&self, params: &[u8]) -> Option<u32> {
621 let s = std::str::from_utf8(params).ok()?;
622 let first = s.split(';').next()?;
623 first.parse().ok()
624 }
625
626 fn parse_modifier_param(&self, params: &[u8]) -> Modifiers {
628 let s = match std::str::from_utf8(params) {
629 Ok(s) => s,
630 Err(_) => return Modifiers::NONE,
631 };
632
633 let modifier_value: u32 = s
634 .split(';')
635 .nth(1)
636 .and_then(|s| s.parse().ok())
637 .unwrap_or(1);
638
639 Self::modifiers_from_xterm(modifier_value)
640 }
641
642 fn parse_kitty_keyboard(&self, params: &[u8]) -> Option<Event> {
646 let s = std::str::from_utf8(params).ok()?;
647 if s.is_empty() {
648 return None;
649 }
650
651 let mut parts = s.split(';');
652 let key_part = parts.next().unwrap_or("");
653 let key_code_str = key_part.split(':').next().unwrap_or("");
654 let key_code: u32 = key_code_str.parse().ok()?;
655
656 let mod_part = parts.next().unwrap_or("");
657 let (modifiers, kind) = Self::kitty_modifiers_and_kind(mod_part);
658
659 let code = Self::kitty_keycode_to_keycode(key_code)?;
660 Some(Event::Key(
661 KeyEvent::new(code)
662 .with_modifiers(modifiers)
663 .with_kind(kind),
664 ))
665 }
666
667 fn kitty_modifiers_and_kind(mod_part: &str) -> (Modifiers, KeyEventKind) {
668 if mod_part.is_empty() {
669 return (Modifiers::NONE, KeyEventKind::Press);
670 }
671
672 let mut parts = mod_part.split(':');
673 let mod_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
674 let kind_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
675
676 let modifiers = Self::modifiers_from_xterm(mod_value);
677 let kind = match kind_value {
678 2 => KeyEventKind::Repeat,
679 3 => KeyEventKind::Release,
680 _ => KeyEventKind::Press,
681 };
682
683 (modifiers, kind)
684 }
685
686 fn kitty_keycode_to_keycode(key_code: u32) -> Option<KeyCode> {
687 match key_code {
688 9 => Some(KeyCode::Tab),
690 13 => Some(KeyCode::Enter),
691 27 => Some(KeyCode::Escape),
692 8 | 127 => Some(KeyCode::Backspace),
693 57_344 => Some(KeyCode::Escape),
695 57_345 => Some(KeyCode::Enter),
696 57_346 => Some(KeyCode::Tab),
697 57_347 => Some(KeyCode::Backspace),
698 57_348 => Some(KeyCode::Insert),
699 57_349 => Some(KeyCode::Delete),
700 57_350 => Some(KeyCode::Left),
701 57_351 => Some(KeyCode::Right),
702 57_352 => Some(KeyCode::Up),
703 57_353 => Some(KeyCode::Down),
704 57_354 => Some(KeyCode::PageUp),
705 57_355 => Some(KeyCode::PageDown),
706 57_356 => Some(KeyCode::Home),
707 57_357 => Some(KeyCode::End),
708 57_364..=57_387 => {
710 let f_num = key_code - 57_364 + 1;
713 debug_assert!(f_num <= 24, "F-key number {f_num} exceeds F24");
714 Some(KeyCode::F(f_num as u8))
715 }
716 57_358..=57_363 | 57_388..=63_743 => None,
718 _ => char::from_u32(key_code).map(KeyCode::Char),
720 }
721 }
722
723 fn modifiers_from_xterm(value: u32) -> Modifiers {
724 let bits = value.saturating_sub(1);
727 let mut mods = Modifiers::NONE;
728 if bits & 1 != 0 {
729 mods |= Modifiers::SHIFT;
730 }
731 if bits & 2 != 0 {
732 mods |= Modifiers::ALT;
733 }
734 if bits & 4 != 0 {
735 mods |= Modifiers::CTRL;
736 }
737 if bits & 8 != 0 {
738 mods |= Modifiers::SUPER;
739 }
740 mods
741 }
742
743 fn key_with_modifiers(&self, code: KeyCode, params: &[u8]) -> KeyEvent {
745 KeyEvent::new(code).with_modifiers(self.parse_modifier_param(params))
746 }
747
748 fn parse_sgr_mouse(&self, params: &[u8], final_byte: u8) -> Option<Event> {
750 let params = ¶ms[1..];
753 let s = std::str::from_utf8(params).ok()?;
754 let mut parts = s.split(';');
755
756 let button_code_u32 = Self::parse_u32_prefix(parts.next()?)?;
759 let button_code = button_code_u32.min(u16::MAX as u32) as u16;
760 let x_raw = Self::parse_i32_prefix(parts.next()?)?;
761 let y_raw = Self::parse_i32_prefix(parts.next()?)?;
762
763 let (button, mods) = self.decode_mouse_button(button_code);
765
766 let kind = if final_byte == b'M' {
767 if button_code & 64 != 0 {
768 match button_code & 3 {
771 0 => MouseEventKind::ScrollUp,
772 1 => MouseEventKind::ScrollDown,
773 2 => MouseEventKind::ScrollLeft,
774 _ => MouseEventKind::ScrollRight,
775 }
776 } else if button_code & 32 != 0 {
777 if button_code & 3 == 3 {
780 MouseEventKind::Moved
781 } else {
782 MouseEventKind::Drag(button)
783 }
784 } else if (button_code & 3) == 3 {
785 MouseEventKind::Up(MouseButton::Left)
788 } else {
789 MouseEventKind::Down(button)
790 }
791 } else {
792 MouseEventKind::Up(button)
793 };
794
795 Some(Event::Mouse(MouseEvent {
796 kind,
797 x: Self::normalize_sgr_coord(x_raw),
798 y: Self::normalize_sgr_coord(y_raw),
799 modifiers: mods,
800 }))
801 }
802
803 #[inline]
804 fn parse_u32_prefix(token: &str) -> Option<u32> {
805 let bytes = token.as_bytes();
806 let digits = bytes.iter().take_while(|b| b.is_ascii_digit()).count();
807 if digits == 0 {
808 return None;
809 }
810 token[..digits].parse().ok()
811 }
812
813 #[inline]
814 fn parse_i32_prefix(token: &str) -> Option<i32> {
815 let bytes = token.as_bytes();
816 if bytes.is_empty() {
817 return None;
818 }
819 let start = if bytes[0] == b'-' || bytes[0] == b'+' {
820 1
821 } else {
822 0
823 };
824 let digits = bytes[start..]
825 .iter()
826 .take_while(|b| b.is_ascii_digit())
827 .count();
828 if digits == 0 {
829 return None;
830 }
831 token[..start + digits].parse().ok()
832 }
833
834 #[inline]
835 fn normalize_sgr_coord(raw: i32) -> u16 {
836 if raw <= 1 {
837 return 0;
838 }
839 let zero_indexed = raw - 1;
840 zero_indexed.min(i32::from(u16::MAX)) as u16
841 }
842
843 fn parse_legacy_mouse(&self, params: &[u8]) -> Option<Event> {
848 if params.is_empty() || params.starts_with(b"<") {
849 return None;
850 }
851
852 let s = std::str::from_utf8(params).ok()?;
853 let mut parts = s.split(';');
854 let button_code: u16 = parts.next()?.parse().ok()?;
855 let x: u16 = parts.next()?.parse().ok()?;
856 let y: u16 = parts.next()?.parse().ok()?;
857 if parts.next().is_some() {
859 return None;
860 }
861
862 let (button, mods) = self.decode_mouse_button(button_code);
863 let kind = if button_code & 64 != 0 {
864 match button_code & 3 {
866 0 => MouseEventKind::ScrollUp,
867 1 => MouseEventKind::ScrollDown,
868 2 => MouseEventKind::ScrollLeft,
869 _ => MouseEventKind::ScrollRight,
870 }
871 } else if button_code & 32 != 0 {
872 if button_code & 3 == 3 {
874 MouseEventKind::Moved
875 } else {
876 MouseEventKind::Drag(button)
877 }
878 } else if (button_code & 3) == 3 {
879 MouseEventKind::Up(MouseButton::Left)
881 } else {
882 MouseEventKind::Down(button)
883 };
884
885 Some(Event::Mouse(MouseEvent {
886 kind,
887 x: x.saturating_sub(1),
888 y: y.saturating_sub(1),
889 modifiers: mods,
890 }))
891 }
892
893 fn decode_mouse_button(&self, code: u16) -> (MouseButton, Modifiers) {
895 let button = match code & 0b11 {
896 0 => MouseButton::Left,
897 1 => MouseButton::Middle,
898 2 => MouseButton::Right,
899 _ => MouseButton::Left,
900 };
901
902 let mut mods = Modifiers::NONE;
903 if code & 4 != 0 {
904 mods |= Modifiers::SHIFT;
905 }
906 if code & 8 != 0 {
907 mods |= Modifiers::ALT;
908 }
909 if code & 16 != 0 {
910 mods |= Modifiers::CTRL;
911 }
912
913 (button, mods)
914 }
915
916 fn process_ss3(&mut self, byte: u8) -> Option<Event> {
918 if byte == 0x1B {
920 self.state = ParserState::Escape;
921 return None;
922 }
923
924 self.state = ParserState::Ground;
925
926 let code = match byte {
927 b'P' => KeyCode::F(1),
928 b'Q' => KeyCode::F(2),
929 b'R' => KeyCode::F(3),
930 b'S' => KeyCode::F(4),
931 b'A' => KeyCode::Up,
932 b'B' => KeyCode::Down,
933 b'C' => KeyCode::Right,
934 b'D' => KeyCode::Left,
935 b'H' => KeyCode::Home,
936 b'F' => KeyCode::End,
937 _ => return None,
938 };
939
940 Some(Event::Key(KeyEvent::new(code)))
941 }
942
943 fn process_osc(&mut self, byte: u8) -> Option<Event> {
945 if byte == 0x1B {
947 self.state = ParserState::OscEscape;
948 return None;
949 }
950
951 self.buffer.push(byte);
952
953 match byte {
954 0x07 => {
956 self.state = ParserState::Ground;
957 self.parse_osc_sequence()
958 }
959 _ => {
961 self.state = ParserState::OscContent;
962 None
963 }
964 }
965 }
966
967 fn process_osc_content(&mut self, byte: u8) -> Option<Event> {
969 if byte == 0x1B {
971 self.state = ParserState::OscEscape;
972 return None;
973 }
974
975 if byte < 0x20 && byte != 0x07 {
977 self.state = ParserState::Ground;
978 self.buffer.clear();
979 return self.process_ground(byte);
980 }
981
982 if self.buffer.len() >= MAX_OSC_LEN {
984 self.state = ParserState::OscIgnore;
985 self.buffer.clear();
986 return None;
987 }
988
989 match byte {
990 0x07 => {
992 self.state = ParserState::Ground;
993 self.parse_osc_sequence()
994 }
995 _ => {
997 self.buffer.push(byte);
998 None
999 }
1000 }
1001 }
1002
1003 fn process_osc_escape(&mut self, byte: u8) -> Option<Event> {
1005 if byte == b'\\' {
1006 self.state = ParserState::Ground;
1008 self.parse_osc_sequence()
1009 } else if byte == 0x1B {
1010 self.state = ParserState::Escape;
1012 self.buffer.clear();
1013 None
1014 } else {
1015 self.buffer.clear();
1021 self.state = ParserState::Escape;
1022 self.process_escape(byte)
1023 }
1024 }
1025
1026 fn process_osc_ignore(&mut self, byte: u8) -> Option<Event> {
1028 match byte {
1029 0x07 => {
1031 self.state = ParserState::Ground;
1032 None
1033 }
1034 0x1B => {
1036 self.state = ParserState::OscEscape;
1037 None
1038 }
1039 _ if byte < 0x20 => {
1041 self.state = ParserState::Ground;
1042 self.process_ground(byte)
1043 }
1044 _ => None,
1046 }
1047 }
1048
1049 fn parse_osc_sequence(&mut self) -> Option<Event> {
1051 let seq = std::mem::take(&mut self.buffer);
1052
1053 if seq.starts_with(b"52;") {
1055 return self.parse_osc52_clipboard(&seq);
1056 }
1057
1058 None
1060 }
1061
1062 fn parse_osc52_clipboard(&self, seq: &[u8]) -> Option<Event> {
1064 let content = &seq[3..]; if content.is_empty() {
1067 return None;
1068 }
1069
1070 let source = ClipboardSource::Osc52;
1073
1074 let base64_start = content.iter().position(|&b| b == b';').map(|i| i + 1)?;
1076 let base64_data = &content[base64_start..];
1077
1078 let decoded = self.decode_base64(base64_data)?;
1080
1081 Some(Event::Clipboard(ClipboardEvent::new(
1082 String::from_utf8_lossy(&decoded).into_owned(),
1083 source,
1084 )))
1085 }
1086
1087 fn decode_base64(&self, input: &[u8]) -> Option<Vec<u8>> {
1089 const DECODE_TABLE: [i8; 256] = {
1090 let mut table = [-1i8; 256];
1091 let mut i = 0u8;
1092 while i < 26 {
1093 table[(b'A' + i) as usize] = i as i8;
1094 table[(b'a' + i) as usize] = (i + 26) as i8;
1095 i += 1;
1096 }
1097 let mut i = 0u8;
1098 while i < 10 {
1099 table[(b'0' + i) as usize] = (i + 52) as i8;
1100 i += 1;
1101 }
1102 table[b'+' as usize] = 62;
1103 table[b'/' as usize] = 63;
1104 table
1105 };
1106
1107 let mut output = Vec::with_capacity(input.len() * 3 / 4);
1108 let mut buffer = 0u32;
1109 let mut bits = 0u8;
1110
1111 for &byte in input {
1112 if byte == b'=' {
1113 break;
1114 }
1115 let value = DECODE_TABLE[byte as usize];
1116 if value < 0 {
1117 continue; }
1119 buffer = (buffer << 6) | (value as u32);
1120 bits += 6;
1121 if bits >= 8 {
1122 bits -= 8;
1123 output.push((buffer >> bits) as u8);
1124 buffer &= (1 << bits) - 1;
1125 }
1126 }
1127
1128 Some(output)
1129 }
1130
1131 fn process_utf8(&mut self, byte: u8, collected: u8, expected: u8) -> Option<Event> {
1133 if (byte & 0xC0) != 0x80 {
1135 self.state = ParserState::Ground;
1138
1139 self.pending_event = self.process_ground(byte);
1141
1142 return Some(Event::Key(KeyEvent::new(KeyCode::Char(
1143 std::char::REPLACEMENT_CHARACTER,
1144 ))));
1145 }
1146
1147 self.utf8_buffer[collected as usize] = byte;
1148 let new_collected = collected + 1;
1149
1150 if new_collected == expected {
1151 self.state = ParserState::Ground;
1153 match std::str::from_utf8(&self.utf8_buffer[..expected as usize]) {
1154 Ok(s) => {
1155 let c = s.chars().next()?;
1156 Some(Event::Key(KeyEvent::new(KeyCode::Char(c))))
1157 }
1158 Err(_) => Some(Event::Key(KeyEvent::new(KeyCode::Char(
1159 std::char::REPLACEMENT_CHARACTER,
1160 )))),
1161 }
1162 } else {
1163 self.state = ParserState::Utf8 {
1165 collected: new_collected,
1166 expected,
1167 };
1168 None
1169 }
1170 }
1171
1172 fn process_mouse_x10(&mut self, byte: u8) -> Option<Event> {
1174 if let ParserState::MouseX10 {
1175 ref mut collected,
1176 ref mut buffer,
1177 } = self.state
1178 {
1179 buffer[*collected as usize] = byte;
1180 *collected += 1;
1181
1182 if *collected == 3 {
1183 let buf = *buffer;
1185 self.state = ParserState::Ground;
1186
1187 if buf[0] < 32 || buf[1] < 33 || buf[2] < 33 {
1191 return None;
1192 }
1193 let cb = buf[0].saturating_sub(32) as u16;
1194 let cx = buf[1].saturating_sub(33) as u16; let cy = buf[2].saturating_sub(33) as u16;
1196
1197 let (button, mods) = self.decode_mouse_button(cb);
1198
1199 let kind = if (cb & 3) == 3 {
1205 MouseEventKind::Up(MouseButton::Left)
1207 } else if cb & 64 != 0 {
1208 match cb & 3 {
1210 0 => MouseEventKind::ScrollUp,
1211 1 => MouseEventKind::ScrollDown,
1212 _ => MouseEventKind::ScrollUp,
1214 }
1215 } else {
1216 MouseEventKind::Down(button)
1218 };
1219
1220 return Some(Event::Mouse(MouseEvent {
1221 kind,
1222 x: cx,
1223 y: cy,
1224 modifiers: mods,
1225 }));
1226 }
1227 }
1228 None
1229 }
1230
1231 fn process_paste_byte(&mut self, byte: u8) -> Option<Event> {
1233 const END_SEQ: &[u8] = b"\x1b[201~";
1234
1235 if self.paste_buffer.len() < MAX_PASTE_LEN {
1241 self.paste_buffer.push(byte);
1242
1243 if self.paste_buffer.ends_with(END_SEQ) {
1245 self.in_paste = false;
1246 let content_len = self.paste_buffer.len() - END_SEQ.len();
1248 let content =
1249 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
1250 self.paste_buffer.clear();
1251 return Some(Event::Paste(PasteEvent::bracketed(content)));
1252 }
1253 } else {
1254 self.buffer.push(byte);
1259 if self.buffer.len() > END_SEQ.len() {
1260 self.buffer.remove(0);
1261 }
1262
1263 let mut last_bytes = [0u8; 6];
1270 let tail_len = self.buffer.len();
1271 let paste_len = self.paste_buffer.len();
1272
1273 if tail_len + paste_len >= 6 {
1275 for i in 0..tail_len {
1277 last_bytes[6 - tail_len + i] = self.buffer[i];
1278 }
1279 let remaining = 6 - tail_len;
1281 if remaining > 0 {
1282 let start = paste_len - remaining;
1283 last_bytes[..remaining]
1284 .copy_from_slice(&self.paste_buffer[start..(remaining + start)]);
1285 }
1286
1287 if last_bytes == END_SEQ {
1288 self.in_paste = false;
1289
1290 let content_len = paste_len - remaining;
1295 let content =
1296 String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
1297
1298 self.paste_buffer.clear();
1299 self.buffer.clear();
1300
1301 return Some(Event::Paste(PasteEvent::bracketed(content)));
1302 }
1303 }
1304 }
1305
1306 None
1307 }
1308}
1309
1310#[cfg(test)]
1311mod tests {
1312 use super::*;
1313
1314 #[test]
1315 fn csi_ignore_handles_final_bytes() {
1316 let mut parser = InputParser::new();
1317
1318 let mut seq = vec![0x1B, b'['];
1321 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100)); seq.push(b'@'); let events = parser.parse(&seq);
1325 assert_eq!(events.len(), 0);
1326
1327 let events = parser.parse(b"a");
1330 assert_eq!(events.len(), 1, "Subsequent char 'a' was swallowed");
1331 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1332 }
1333
1334 #[test]
1335 fn ascii_characters_parsed() {
1336 let mut parser = InputParser::new();
1337
1338 let events = parser.parse(b"abc");
1339 assert_eq!(events.len(), 3);
1340 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1341 assert!(matches!(events[1], Event::Key(k) if k.code == KeyCode::Char('b')));
1342 assert!(matches!(events[2], Event::Key(k) if k.code == KeyCode::Char('c')));
1343 }
1344
1345 #[test]
1346 fn control_characters() {
1347 let mut parser = InputParser::new();
1348
1349 let events = parser.parse(&[0x01]);
1351 assert_eq!(events.len(), 1);
1352 assert!(matches!(
1353 events[0],
1354 Event::Key(k) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::CTRL)
1355 ));
1356
1357 let events = parser.parse(&[0x7F]);
1359 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace));
1360 }
1361
1362 #[test]
1363 fn arrow_keys() {
1364 let mut parser = InputParser::new();
1365
1366 assert!(matches!(
1367 parser.parse(b"\x1b[A").first(),
1368 Some(Event::Key(k)) if k.code == KeyCode::Up
1369 ));
1370 assert!(matches!(
1371 parser.parse(b"\x1b[B").first(),
1372 Some(Event::Key(k)) if k.code == KeyCode::Down
1373 ));
1374 assert!(matches!(
1375 parser.parse(b"\x1b[C").first(),
1376 Some(Event::Key(k)) if k.code == KeyCode::Right
1377 ));
1378 assert!(matches!(
1379 parser.parse(b"\x1b[D").first(),
1380 Some(Event::Key(k)) if k.code == KeyCode::Left
1381 ));
1382 }
1383
1384 #[test]
1385 fn c1_csi_arrow_keys() {
1386 let mut parser = InputParser::new();
1387
1388 assert!(matches!(
1389 parser.parse(&[0x9B, b'A']).first(),
1390 Some(Event::Key(k)) if k.code == KeyCode::Up
1391 ));
1392 assert!(matches!(
1393 parser.parse(&[0x9B, b'B']).first(),
1394 Some(Event::Key(k)) if k.code == KeyCode::Down
1395 ));
1396 }
1397
1398 #[test]
1399 fn c1_csi_mouse_sgr_protocol() {
1400 let mut parser = InputParser::new();
1401
1402 let events = parser.parse(&[0x9B, b'<', b'0', b';', b'1', b'0', b';', b'2', b'0', b'M']);
1403 assert!(matches!(
1404 events.first(),
1405 Some(Event::Mouse(m)) if m.x == 9 && m.y == 19
1406 ));
1407 }
1408
1409 #[test]
1410 fn function_keys_ss3() {
1411 let mut parser = InputParser::new();
1412
1413 assert!(matches!(
1414 parser.parse(b"\x1bOP").first(),
1415 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1416 ));
1417 assert!(matches!(
1418 parser.parse(b"\x1bOQ").first(),
1419 Some(Event::Key(k)) if k.code == KeyCode::F(2)
1420 ));
1421 assert!(matches!(
1422 parser.parse(b"\x1bOR").first(),
1423 Some(Event::Key(k)) if k.code == KeyCode::F(3)
1424 ));
1425 assert!(matches!(
1426 parser.parse(b"\x1bOS").first(),
1427 Some(Event::Key(k)) if k.code == KeyCode::F(4)
1428 ));
1429 }
1430
1431 #[test]
1432 fn function_keys_csi() {
1433 let mut parser = InputParser::new();
1434
1435 assert!(matches!(
1436 parser.parse(b"\x1b[15~").first(),
1437 Some(Event::Key(k)) if k.code == KeyCode::F(5)
1438 ));
1439 assert!(matches!(
1440 parser.parse(b"\x1b[17~").first(),
1441 Some(Event::Key(k)) if k.code == KeyCode::F(6)
1442 ));
1443 }
1444
1445 #[test]
1446 fn modifiers_in_csi() {
1447 let mut parser = InputParser::new();
1448
1449 let events = parser.parse(b"\x1b[1;2A");
1451 assert!(matches!(
1452 events.first(),
1453 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::SHIFT)
1454 ));
1455
1456 let events = parser.parse(b"\x1b[1;5A");
1458 assert!(matches!(
1459 events.first(),
1460 Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::CTRL)
1461 ));
1462 }
1463
1464 #[test]
1465 fn modifiers_in_csi_alt_ctrl() {
1466 let mut parser = InputParser::new();
1467
1468 let events = parser.parse(b"\x1b[1;7A");
1470 assert!(matches!(
1471 events.first(),
1472 Some(Event::Key(k))
1473 if k.code == KeyCode::Up
1474 && k.modifiers.contains(Modifiers::ALT)
1475 && k.modifiers.contains(Modifiers::CTRL)
1476 ));
1477 }
1478
1479 #[test]
1480 fn kitty_keyboard_basic_char() {
1481 let mut parser = InputParser::new();
1482
1483 let events = parser.parse(b"\x1b[97u");
1484 assert!(matches!(
1485 events.first(),
1486 Some(Event::Key(k))
1487 if k.code == KeyCode::Char('a')
1488 && k.modifiers == Modifiers::NONE
1489 && k.kind == KeyEventKind::Press
1490 ));
1491 }
1492
1493 #[test]
1494 fn kitty_keyboard_with_modifiers_and_kind() {
1495 let mut parser = InputParser::new();
1496
1497 let events = parser.parse(b"\x1b[97;5:2u");
1499 assert!(matches!(
1500 events.first(),
1501 Some(Event::Key(k))
1502 if k.code == KeyCode::Char('a')
1503 && k.modifiers.contains(Modifiers::CTRL)
1504 && k.kind == KeyEventKind::Repeat
1505 ));
1506 }
1507
1508 #[test]
1509 fn kitty_keyboard_function_key() {
1510 let mut parser = InputParser::new();
1511
1512 let events = parser.parse(b"\x1b[57364;1u");
1513 assert!(matches!(
1514 events.first(),
1515 Some(Event::Key(k)) if k.code == KeyCode::F(1)
1516 ));
1517 }
1518
1519 #[test]
1520 fn alt_key_escapes() {
1521 let mut parser = InputParser::new();
1522
1523 let events = parser.parse(b"\x1ba");
1524 assert!(matches!(
1525 events.first(),
1526 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::ALT)
1527 ));
1528 }
1529
1530 #[test]
1531 fn alt_backspace() {
1532 let mut parser = InputParser::new();
1533
1534 let events = parser.parse(b"\x1b\x7f");
1535 assert!(matches!(
1536 events.first(),
1537 Some(Event::Key(k))
1538 if k.code == KeyCode::Backspace && k.modifiers.contains(Modifiers::ALT)
1539 ));
1540 }
1541
1542 #[test]
1543 fn escape_escape_resets_state() {
1544 let mut parser = InputParser::new();
1545
1546 let events = parser.parse(b"\x1b\x1b");
1547 assert!(matches!(
1548 events.first(),
1549 Some(Event::Key(k)) if k.code == KeyCode::Escape && k.modifiers.contains(Modifiers::ALT)
1550 ));
1551
1552 let events = parser.parse(b"a");
1553 assert!(matches!(
1554 events.first(),
1555 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers == Modifiers::NONE
1556 ));
1557 }
1558
1559 #[test]
1560 fn focus_events() {
1561 let mut parser = InputParser::new();
1562
1563 assert!(matches!(
1564 parser.parse(b"\x1b[I").first(),
1565 Some(Event::Focus(true))
1566 ));
1567 assert!(matches!(
1568 parser.parse(b"\x1b[O").first(),
1569 Some(Event::Focus(false))
1570 ));
1571 }
1572
1573 #[test]
1574 fn bracketed_paste() {
1575 let mut parser = InputParser::new();
1576
1577 let events = parser.parse(b"\x1b[200~hello world\x1b[201~");
1579 assert_eq!(events.len(), 1);
1580 assert!(matches!(
1581 &events[0],
1582 Event::Paste(p) if p.text == "hello world"
1583 ));
1584 }
1585
1586 #[test]
1587 fn mouse_sgr_protocol() {
1588 let mut parser = InputParser::new();
1589
1590 let events = parser.parse(b"\x1b[<0;10;20M");
1592 assert!(matches!(
1593 events.first(),
1594 Some(Event::Mouse(m)) if m.x == 9 && m.y == 19 ));
1596 }
1597
1598 #[test]
1599 fn mouse_sgr_protocol_with_subparams() {
1600 let mut parser = InputParser::new();
1601
1602 let events = parser.parse(b"\x1b[<0:0;10:0;20:0M");
1604 assert!(matches!(
1605 events.first(),
1606 Some(Event::Mouse(m))
1607 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1608 && m.x == 9
1609 && m.y == 19
1610 ));
1611 }
1612
1613 #[test]
1614 fn mouse_sgr_protocol_large_coords_clamped() {
1615 let mut parser = InputParser::new();
1616
1617 let events = parser.parse(b"\x1b[<0;70000;80000M");
1619 assert!(matches!(
1620 events.first(),
1621 Some(Event::Mouse(m))
1622 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1623 && m.x == u16::MAX
1624 && m.y == u16::MAX
1625 ));
1626 }
1627
1628 #[test]
1629 fn mouse_sgr_protocol_negative_coords_clamped() {
1630 let mut parser = InputParser::new();
1631
1632 let events = parser.parse(b"\x1b[<0;-12;-3M");
1635 assert!(matches!(
1636 events.first(),
1637 Some(Event::Mouse(m))
1638 if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1639 && m.x == 0
1640 && m.y == 0
1641 ));
1642 }
1643
1644 #[test]
1645 fn mouse_sgr_modifiers() {
1646 let mut parser = InputParser::new();
1647
1648 let events = parser.parse(b"\x1b[<28;3;4M");
1650 assert!(matches!(
1651 events.first(),
1652 Some(Event::Mouse(m))
1653 if m.modifiers.contains(Modifiers::SHIFT)
1654 && m.modifiers.contains(Modifiers::ALT)
1655 && m.modifiers.contains(Modifiers::CTRL)
1656 ));
1657 }
1658
1659 #[test]
1660 fn mouse_sgr_scroll_up() {
1661 let mut parser = InputParser::new();
1662
1663 let events = parser.parse(b"\x1b[<64;5;5M");
1665 assert!(matches!(
1666 events.first(),
1667 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollUp)
1668 ));
1669 }
1670
1671 #[test]
1672 fn mouse_sgr_scroll_down() {
1673 let mut parser = InputParser::new();
1674
1675 let events = parser.parse(b"\x1b[<65;5;5M");
1677 assert!(matches!(
1678 events.first(),
1679 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollDown)
1680 ));
1681 }
1682
1683 #[test]
1684 fn mouse_sgr_scroll_left() {
1685 let mut parser = InputParser::new();
1686
1687 let events = parser.parse(b"\x1b[<66;5;5M");
1689 assert!(matches!(
1690 events.first(),
1691 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollLeft)
1692 ));
1693 }
1694
1695 #[test]
1696 fn mouse_sgr_scroll_right() {
1697 let mut parser = InputParser::new();
1698
1699 let events = parser.parse(b"\x1b[<67;5;5M");
1701 assert!(matches!(
1702 events.first(),
1703 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollRight)
1704 ));
1705 }
1706
1707 #[test]
1708 fn mouse_sgr_drag_left() {
1709 let mut parser = InputParser::new();
1710
1711 let events = parser.parse(b"\x1b[<32;10;20M");
1713 assert!(matches!(
1714 events.first(),
1715 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Drag(MouseButton::Left))
1716 ));
1717 }
1718
1719 #[test]
1720 fn utf8_characters() {
1721 let mut parser = InputParser::new();
1722
1723 let events = parser.parse(&[0xC3, 0xA9]);
1725 assert!(matches!(
1726 events.first(),
1727 Some(Event::Key(k)) if k.code == KeyCode::Char('é')
1728 ));
1729 }
1730
1731 #[test]
1732 fn invalid_utf8_emits_replacement_then_reprocesses_byte() {
1733 let mut parser = InputParser::new();
1734
1735 let events = parser.parse(&[0xE2, 0x28]);
1737 assert_eq!(events.len(), 2);
1738 assert!(matches!(
1739 events[0],
1740 Event::Key(k) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)
1741 ));
1742 assert!(matches!(
1743 events[1],
1744 Event::Key(k) if k.code == KeyCode::Char('(')
1745 ));
1746 }
1747
1748 #[test]
1749 fn dos_protection_csi() {
1750 let mut parser = InputParser::new();
1751
1752 let mut seq = vec![0x1B, b'['];
1754 seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
1755 seq.push(b'A');
1756
1757 let events = parser.parse(&seq);
1760 assert_eq!(
1761 events.len(),
1762 0,
1763 "Oversized CSI sequence should produce no events"
1764 );
1765
1766 let events = parser.parse(b"\x1b[A");
1769 assert!(matches!(
1770 events.first(),
1771 Some(Event::Key(k)) if k.code == KeyCode::Up
1772 ));
1773 }
1774
1775 #[test]
1776 fn incomplete_csi_sequence_emits_no_event() {
1777 let mut parser = InputParser::new();
1778 let events = parser.parse(b"\x1b[");
1779 assert!(events.is_empty());
1780 }
1781
1782 #[test]
1783 fn dos_protection_paste() {
1784 let mut parser = InputParser::new();
1785
1786 parser.parse(b"\x1b[200~");
1788
1789 let content = vec![b'x'; MAX_PASTE_LEN - 100]; parser.parse(&content);
1792
1793 let events = parser.parse(b"\x1b[201~");
1795
1796 assert!(matches!(
1798 events.first(),
1799 Some(Event::Paste(p)) if p.text.len() <= MAX_PASTE_LEN
1800 ));
1801 }
1802
1803 #[test]
1804 fn dos_protection_paste_overflow_terminator() {
1805 let mut parser = InputParser::new();
1806
1807 parser.parse(b"\x1b[200~");
1809
1810 let overflow = 100;
1814 let content = vec![b'a'; MAX_PASTE_LEN + overflow];
1815 parser.parse(&content);
1816
1817 let events = parser.parse(b"\x1b[201~");
1820
1821 assert_eq!(events.len(), 1, "Should emit paste event");
1822 match &events[0] {
1823 Event::Paste(p) => {
1824 assert_eq!(
1827 p.text.len(),
1828 MAX_PASTE_LEN,
1829 "Paste should be capped at MAX_PASTE_LEN bytes"
1830 );
1831 assert!(p.text.chars().all(|c| c == 'a'));
1833 }
1834 _ => unreachable!("Expected Paste event"),
1835 }
1836
1837 let events = parser.parse(b"b");
1839 assert_eq!(events.len(), 1);
1840 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('b')));
1841 }
1842
1843 #[test]
1844 fn no_panic_on_invalid_input() {
1845 let mut parser = InputParser::new();
1846
1847 let garbage = [0xFF, 0xFE, 0x00, 0x1B, 0x1B, 0x1B, b'[', 0xFF, b']', 0x00];
1849
1850 let _ = parser.parse(&garbage);
1852 }
1853
1854 #[test]
1855 fn dos_protection_paste_boundary() {
1856 let mut parser = InputParser::new();
1857 parser.parse(b"\x1b[200~");
1859
1860 let content = vec![b'x'; MAX_PASTE_LEN];
1862 parser.parse(&content);
1863
1864 let events = parser.parse(b"\x1b[201~");
1867
1868 assert!(
1869 !events.is_empty(),
1870 "Parser trapped in paste mode after hitting limit"
1871 );
1872 assert!(matches!(events[0], Event::Paste(_)));
1873 }
1874
1875 #[test]
1878 fn csi_tilde_home() {
1879 let mut parser = InputParser::new();
1880 let events = parser.parse(b"\x1b[1~");
1881 assert!(matches!(
1882 events.first(),
1883 Some(Event::Key(k)) if k.code == KeyCode::Home
1884 ));
1885 }
1886
1887 #[test]
1888 fn csi_tilde_insert() {
1889 let mut parser = InputParser::new();
1890 let events = parser.parse(b"\x1b[2~");
1891 assert!(matches!(
1892 events.first(),
1893 Some(Event::Key(k)) if k.code == KeyCode::Insert
1894 ));
1895 }
1896
1897 #[test]
1898 fn csi_tilde_delete() {
1899 let mut parser = InputParser::new();
1900 let events = parser.parse(b"\x1b[3~");
1901 assert!(matches!(
1902 events.first(),
1903 Some(Event::Key(k)) if k.code == KeyCode::Delete
1904 ));
1905 }
1906
1907 #[test]
1908 fn csi_tilde_end() {
1909 let mut parser = InputParser::new();
1910 let events = parser.parse(b"\x1b[4~");
1911 assert!(matches!(
1912 events.first(),
1913 Some(Event::Key(k)) if k.code == KeyCode::End
1914 ));
1915 }
1916
1917 #[test]
1918 fn csi_tilde_page_up() {
1919 let mut parser = InputParser::new();
1920 let events = parser.parse(b"\x1b[5~");
1921 assert!(matches!(
1922 events.first(),
1923 Some(Event::Key(k)) if k.code == KeyCode::PageUp
1924 ));
1925 }
1926
1927 #[test]
1928 fn csi_tilde_page_down() {
1929 let mut parser = InputParser::new();
1930 let events = parser.parse(b"\x1b[6~");
1931 assert!(matches!(
1932 events.first(),
1933 Some(Event::Key(k)) if k.code == KeyCode::PageDown
1934 ));
1935 }
1936
1937 #[test]
1940 fn csi_home_and_end() {
1941 let mut parser = InputParser::new();
1942 assert!(matches!(
1943 parser.parse(b"\x1b[H").first(),
1944 Some(Event::Key(k)) if k.code == KeyCode::Home
1945 ));
1946 assert!(matches!(
1947 parser.parse(b"\x1b[F").first(),
1948 Some(Event::Key(k)) if k.code == KeyCode::End
1949 ));
1950 }
1951
1952 #[test]
1955 fn ss3_home_and_end() {
1956 let mut parser = InputParser::new();
1957 assert!(matches!(
1958 parser.parse(b"\x1bOH").first(),
1959 Some(Event::Key(k)) if k.code == KeyCode::Home
1960 ));
1961 assert!(matches!(
1962 parser.parse(b"\x1bOF").first(),
1963 Some(Event::Key(k)) if k.code == KeyCode::End
1964 ));
1965 }
1966
1967 #[test]
1970 fn backtab_csi_z() {
1971 let mut parser = InputParser::new();
1972 let events = parser.parse(b"\x1b[Z");
1973 assert!(matches!(
1974 events.first(),
1975 Some(Event::Key(k)) if k.code == KeyCode::BackTab
1976 ));
1977 }
1978
1979 #[test]
1982 fn function_keys_f7_to_f12() {
1983 let mut parser = InputParser::new();
1984 assert!(matches!(
1985 parser.parse(b"\x1b[18~").first(),
1986 Some(Event::Key(k)) if k.code == KeyCode::F(7)
1987 ));
1988 assert!(matches!(
1989 parser.parse(b"\x1b[19~").first(),
1990 Some(Event::Key(k)) if k.code == KeyCode::F(8)
1991 ));
1992 assert!(matches!(
1993 parser.parse(b"\x1b[20~").first(),
1994 Some(Event::Key(k)) if k.code == KeyCode::F(9)
1995 ));
1996 assert!(matches!(
1997 parser.parse(b"\x1b[21~").first(),
1998 Some(Event::Key(k)) if k.code == KeyCode::F(10)
1999 ));
2000 assert!(matches!(
2001 parser.parse(b"\x1b[23~").first(),
2002 Some(Event::Key(k)) if k.code == KeyCode::F(11)
2003 ));
2004 assert!(matches!(
2005 parser.parse(b"\x1b[24~").first(),
2006 Some(Event::Key(k)) if k.code == KeyCode::F(12)
2007 ));
2008 }
2009
2010 #[test]
2013 fn ctrl_home_and_alt_end() {
2014 let mut parser = InputParser::new();
2015
2016 let events = parser.parse(b"\x1b[1;5H");
2018 assert!(matches!(
2019 events.first(),
2020 Some(Event::Key(k)) if k.code == KeyCode::Home && k.modifiers.contains(Modifiers::CTRL)
2021 ));
2022
2023 let events = parser.parse(b"\x1b[1;3F");
2025 assert!(matches!(
2026 events.first(),
2027 Some(Event::Key(k)) if k.code == KeyCode::End && k.modifiers.contains(Modifiers::ALT)
2028 ));
2029 }
2030
2031 #[test]
2032 fn shift_ctrl_arrow() {
2033 let mut parser = InputParser::new();
2034
2035 let events = parser.parse(b"\x1b[1;6C");
2037 assert!(matches!(
2038 events.first(),
2039 Some(Event::Key(k)) if k.code == KeyCode::Right
2040 && k.modifiers.contains(Modifiers::SHIFT)
2041 && k.modifiers.contains(Modifiers::CTRL)
2042 ));
2043 }
2044
2045 #[test]
2046 fn modifiers_on_tilde_keys() {
2047 let mut parser = InputParser::new();
2048
2049 let events = parser.parse(b"\x1b[3;5~");
2051 assert!(matches!(
2052 events.first(),
2053 Some(Event::Key(k)) if k.code == KeyCode::Delete && k.modifiers.contains(Modifiers::CTRL)
2054 ));
2055
2056 let events = parser.parse(b"\x1b[5;2~");
2058 assert!(matches!(
2059 events.first(),
2060 Some(Event::Key(k)) if k.code == KeyCode::PageUp && k.modifiers.contains(Modifiers::SHIFT)
2061 ));
2062 }
2063
2064 #[test]
2067 fn mouse_sgr_right_click() {
2068 let mut parser = InputParser::new();
2069 let events = parser.parse(b"\x1b[<2;15;10M");
2071 assert!(matches!(
2072 events.first(),
2073 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Right))
2074 && m.x == 14 && m.y == 9
2075 ));
2076 }
2077
2078 #[test]
2079 fn mouse_sgr_middle_click() {
2080 let mut parser = InputParser::new();
2081 let events = parser.parse(b"\x1b[<1;5;5M");
2083 assert!(matches!(
2084 events.first(),
2085 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Middle))
2086 ));
2087 }
2088
2089 #[test]
2090 fn mouse_sgr_button_release() {
2091 let mut parser = InputParser::new();
2092 let events = parser.parse(b"\x1b[<0;10;20m");
2094 assert!(matches!(
2095 events.first(),
2096 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Up(MouseButton::Left))
2097 ));
2098 }
2099
2100 #[test]
2101 fn mouse_sgr_button_release_uppercase_m_compat() {
2102 let mut parser = InputParser::new();
2103 let events = parser.parse(b"\x1b[<3;10;20M");
2106 assert!(matches!(
2107 events.first(),
2108 Some(Event::Mouse(m))
2109 if matches!(m.kind, MouseEventKind::Up(MouseButton::Left))
2110 && m.x == 9
2111 && m.y == 19
2112 ));
2113 }
2114
2115 #[test]
2116 fn mouse_sgr_moved() {
2117 let mut parser = InputParser::new();
2118 let events = parser.parse(b"\x1b[<35;10;20M");
2120 assert!(matches!(
2121 events.first(),
2122 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Moved)
2123 ));
2124 }
2125
2126 #[test]
2127 fn mouse_sgr_with_modifiers() {
2128 let mut parser = InputParser::new();
2129 let events = parser.parse(b"\x1b[<4;5;5M");
2131 assert!(matches!(
2132 events.first(),
2133 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2134 && m.modifiers.contains(Modifiers::SHIFT)
2135 ));
2136
2137 let events = parser.parse(b"\x1b[<16;5;5M");
2139 assert!(matches!(
2140 events.first(),
2141 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2142 && m.modifiers.contains(Modifiers::CTRL)
2143 ));
2144
2145 let events = parser.parse(b"\x1b[<8;5;5M");
2147 assert!(matches!(
2148 events.first(),
2149 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2150 && m.modifiers.contains(Modifiers::ALT)
2151 ));
2152 }
2153
2154 #[test]
2155 fn mouse_legacy_1015_when_enabled() {
2156 let mut parser = InputParser::new();
2157 parser.set_expect_x10_mouse(true);
2158
2159 let events = parser.parse(b"\x1b[0;10;20M");
2160 assert!(matches!(
2161 events.first(),
2162 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2163 && m.x == 9 && m.y == 19
2164 ));
2165 }
2166
2167 #[test]
2168 fn mouse_legacy_1015_with_fallback_enabled() {
2169 let mut parser = InputParser::new();
2170 parser.set_allow_legacy_mouse(true);
2171
2172 let events = parser.parse(b"\x1b[0;10;20M");
2173 assert!(matches!(
2174 events.first(),
2175 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2176 && m.x == 9 && m.y == 19
2177 ));
2178 }
2179
2180 #[test]
2181 fn mouse_legacy_1015_ignored_when_disabled() {
2182 let mut parser = InputParser::new();
2183 let events = parser.parse(b"\x1b[0;10;20M");
2184 assert!(
2185 events.is_empty(),
2186 "legacy mouse should require explicit opt-in"
2187 );
2188 }
2189
2190 #[test]
2191 fn mouse_x10_when_enabled() {
2192 let mut parser = InputParser::new();
2193 parser.set_expect_x10_mouse(true);
2194
2195 let events = parser.parse(&[0x1B, b'[', b'M', 32, 42, 52]);
2196 assert!(matches!(
2197 events.first(),
2198 Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
2199 && m.x == 9 && m.y == 19
2200 ));
2201 }
2202
2203 #[test]
2204 fn mouse_x10_malformed_packet_ignored() {
2205 let mut parser = InputParser::new();
2206 parser.set_expect_x10_mouse(true);
2207
2208 let events = parser.parse(&[0x1B, b'[', b'M', 31, 0, 10]);
2210 assert!(
2211 events.iter().all(|event| !matches!(event, Event::Mouse(_))),
2212 "malformed X10 payload must not emit mouse events"
2213 );
2214 }
2215
2216 #[test]
2219 fn kitty_keyboard_release_event() {
2220 let mut parser = InputParser::new();
2221 let events = parser.parse(b"\x1b[97;1:3u");
2223 assert!(matches!(
2224 events.first(),
2225 Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.kind == KeyEventKind::Release
2226 ));
2227
2228 let events = parser.parse(b"\x1b[65;5:3u");
2230 assert!(matches!(
2231 events.first(),
2232 Some(Event::Key(k))
2233 if k.code == KeyCode::Char('A')
2234 && k.modifiers.contains(Modifiers::CTRL)
2235 && k.kind == KeyEventKind::Release
2236 ));
2237 }
2238
2239 #[test]
2240 fn kitty_keyboard_special_keys() {
2241 let mut parser = InputParser::new();
2242
2243 assert!(matches!(
2245 parser.parse(b"\x1b[57344u").first(),
2246 Some(Event::Key(k)) if k.code == KeyCode::Escape
2247 ));
2248
2249 assert!(matches!(
2251 parser.parse(b"\x1b[57345u").first(),
2252 Some(Event::Key(k)) if k.code == KeyCode::Enter
2253 ));
2254
2255 assert!(matches!(
2257 parser.parse(b"\x1b[57346u").first(),
2258 Some(Event::Key(k)) if k.code == KeyCode::Tab
2259 ));
2260
2261 assert!(matches!(
2263 parser.parse(b"\x1b[57347u").first(),
2264 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2265 ));
2266
2267 assert!(matches!(
2269 parser.parse(b"\x1b[57348u").first(),
2270 Some(Event::Key(k)) if k.code == KeyCode::Insert
2271 ));
2272
2273 assert!(matches!(
2275 parser.parse(b"\x1b[57349u").first(),
2276 Some(Event::Key(k)) if k.code == KeyCode::Delete
2277 ));
2278 }
2279
2280 #[test]
2281 fn kitty_keyboard_navigation_keys() {
2282 let mut parser = InputParser::new();
2283
2284 assert!(matches!(
2286 parser.parse(b"\x1b[57350u").first(),
2287 Some(Event::Key(k)) if k.code == KeyCode::Left
2288 ));
2289 assert!(matches!(
2291 parser.parse(b"\x1b[57351u").first(),
2292 Some(Event::Key(k)) if k.code == KeyCode::Right
2293 ));
2294 assert!(matches!(
2296 parser.parse(b"\x1b[57352u").first(),
2297 Some(Event::Key(k)) if k.code == KeyCode::Up
2298 ));
2299 assert!(matches!(
2301 parser.parse(b"\x1b[57353u").first(),
2302 Some(Event::Key(k)) if k.code == KeyCode::Down
2303 ));
2304 assert!(matches!(
2306 parser.parse(b"\x1b[57354u").first(),
2307 Some(Event::Key(k)) if k.code == KeyCode::PageUp
2308 ));
2309 assert!(matches!(
2311 parser.parse(b"\x1b[57355u").first(),
2312 Some(Event::Key(k)) if k.code == KeyCode::PageDown
2313 ));
2314 assert!(matches!(
2316 parser.parse(b"\x1b[57356u").first(),
2317 Some(Event::Key(k)) if k.code == KeyCode::Home
2318 ));
2319 assert!(matches!(
2321 parser.parse(b"\x1b[57357u").first(),
2322 Some(Event::Key(k)) if k.code == KeyCode::End
2323 ));
2324 }
2325
2326 #[test]
2327 fn kitty_keyboard_f_keys() {
2328 let mut parser = InputParser::new();
2329 assert!(matches!(
2331 parser.parse(b"\x1b[57364u").first(),
2332 Some(Event::Key(k)) if k.code == KeyCode::F(1)
2333 ));
2334 assert!(matches!(
2336 parser.parse(b"\x1b[57375u").first(),
2337 Some(Event::Key(k)) if k.code == KeyCode::F(12)
2338 ));
2339 assert!(matches!(
2341 parser.parse(b"\x1b[57387u").first(),
2342 Some(Event::Key(k)) if k.code == KeyCode::F(24)
2343 ));
2344 }
2345
2346 #[test]
2347 fn kitty_keyboard_ascii_as_standard() {
2348 let mut parser = InputParser::new();
2349 assert!(matches!(
2351 parser.parse(b"\x1b[9u").first(),
2352 Some(Event::Key(k)) if k.code == KeyCode::Tab
2353 ));
2354 assert!(matches!(
2355 parser.parse(b"\x1b[13u").first(),
2356 Some(Event::Key(k)) if k.code == KeyCode::Enter
2357 ));
2358 assert!(matches!(
2359 parser.parse(b"\x1b[27u").first(),
2360 Some(Event::Key(k)) if k.code == KeyCode::Escape
2361 ));
2362 assert!(matches!(
2363 parser.parse(b"\x1b[127u").first(),
2364 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2365 ));
2366 assert!(matches!(
2368 parser.parse(b"\x1b[8u").first(),
2369 Some(Event::Key(k)) if k.code == KeyCode::Backspace
2370 ));
2371 }
2372
2373 #[test]
2376 fn osc52_clipboard_bel_terminated() {
2377 let mut parser = InputParser::new();
2378 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x07");
2381 assert!(matches!(
2382 events.first(),
2383 Some(Event::Clipboard(c)) if c.content == "hello" && c.source == ClipboardSource::Osc52
2384 ));
2385 }
2386
2387 #[test]
2388 fn osc52_clipboard_st_terminated() {
2389 let mut parser = InputParser::new();
2390 let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x1b\\");
2392 assert!(matches!(
2393 events.first(),
2394 Some(Event::Clipboard(c)) if c.content == "hello"
2395 ));
2396 }
2397
2398 #[test]
2399 fn osc52_clipboard_primary_selection() {
2400 let mut parser = InputParser::new();
2401 let events = parser.parse(b"\x1b]52;p;YWJj\x07");
2404 assert!(matches!(
2405 events.first(),
2406 Some(Event::Clipboard(c)) if c.content == "abc"
2407 ));
2408 }
2409
2410 #[test]
2413 fn ctrl_space_is_null() {
2414 let mut parser = InputParser::new();
2415 let events = parser.parse(&[0x00]);
2416 assert!(matches!(
2417 events.first(),
2418 Some(Event::Key(k)) if k.code == KeyCode::Null
2419 ));
2420 }
2421
2422 #[test]
2423 fn all_ctrl_letter_keys() {
2424 let mut parser = InputParser::new();
2425 for byte in 0x01..=0x1Au8 {
2427 let events = parser.parse(&[byte]);
2428 assert_eq!(
2429 events.len(),
2430 1,
2431 "Ctrl+{} should produce one event",
2432 (byte + b'a' - 1) as char
2433 );
2434 match byte {
2435 0x08 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace)),
2436 0x09 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Tab)),
2437 0x0D => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Enter)),
2438 _ => {
2439 let expected_char = (byte + b'a' - 1) as char;
2440 match &events[0] {
2441 Event::Key(k) => {
2442 assert_eq!(
2443 k.code,
2444 KeyCode::Char(expected_char),
2445 "Byte 0x{byte:02X} should produce Ctrl+{expected_char}"
2446 );
2447 assert!(
2448 k.modifiers.contains(Modifiers::CTRL),
2449 "Byte 0x{byte:02X} should have Ctrl modifier"
2450 );
2451 }
2452 other => {
2453 panic!("Byte 0x{byte:02X}: expected Key event, got {other:?}");
2454 }
2455 }
2456 }
2457 }
2458 }
2459 }
2460
2461 #[test]
2464 fn utf8_3byte_cjk() {
2465 let mut parser = InputParser::new();
2466 let events = parser.parse(&[0xE4, 0xB8, 0xAD]);
2468 assert!(matches!(
2469 events.first(),
2470 Some(Event::Key(k)) if k.code == KeyCode::Char('中')
2471 ));
2472 }
2473
2474 #[test]
2475 fn utf8_4byte_emoji() {
2476 let mut parser = InputParser::new();
2477 let events = parser.parse(&[0xF0, 0x9F, 0xA6, 0x80]);
2479 assert!(matches!(
2480 events.first(),
2481 Some(Event::Key(k)) if k.code == KeyCode::Char('🦀')
2482 ));
2483 }
2484
2485 #[test]
2488 fn empty_input_returns_no_events() {
2489 let mut parser = InputParser::new();
2490 let events = parser.parse(b"");
2491 assert!(events.is_empty());
2492 }
2493
2494 #[test]
2497 fn unknown_csi_tilde_ignored() {
2498 let mut parser = InputParser::new();
2499 let events = parser.parse(b"\x1b[99~");
2501 assert!(events.is_empty());
2502
2503 let events = parser.parse(b"a");
2505 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('a')));
2506 }
2507
2508 #[test]
2511 fn alt_special_chars() {
2512 let mut parser = InputParser::new();
2513
2514 let events = parser.parse(b"\x1b ");
2516 assert!(matches!(
2517 events.first(),
2518 Some(Event::Key(k)) if k.code == KeyCode::Char(' ') && k.modifiers.contains(Modifiers::ALT)
2519 ));
2520
2521 let events = parser.parse(b"\x1b5");
2523 assert!(matches!(
2524 events.first(),
2525 Some(Event::Key(k)) if k.code == KeyCode::Char('5') && k.modifiers.contains(Modifiers::ALT)
2526 ));
2527
2528 let events = parser.parse(b"\x1b}");
2530 assert!(matches!(
2531 events.first(),
2532 Some(Event::Key(k)) if k.code == KeyCode::Char('}') && k.modifiers.contains(Modifiers::ALT)
2533 ));
2534 }
2535
2536 #[test]
2537 fn alt_ctrl_key_combinations() {
2538 let mut parser = InputParser::new();
2539
2540 let events = parser.parse(&[0x1B, 0x01]);
2542 assert_eq!(events.len(), 1);
2543 match &events[0] {
2544 Event::Key(k) => {
2545 assert_eq!(k.code, KeyCode::Char('a'));
2546 assert!(k.modifiers.contains(Modifiers::ALT));
2547 assert!(k.modifiers.contains(Modifiers::CTRL));
2548 }
2549 _ => panic!("Expected Key event"),
2550 }
2551
2552 let events = parser.parse(&[0x1B, 0x08]);
2556 assert_eq!(events.len(), 1);
2557 match &events[0] {
2558 Event::Key(k) => {
2559 assert_eq!(k.code, KeyCode::Backspace);
2560 assert!(k.modifiers.contains(Modifiers::ALT));
2561 }
2562 _ => panic!("Expected Key event"),
2563 }
2564 }
2565
2566 #[test]
2569 fn ss3_arrow_keys() {
2570 let mut parser = InputParser::new();
2571 assert!(matches!(
2572 parser.parse(b"\x1bOA").first(),
2573 Some(Event::Key(k)) if k.code == KeyCode::Up
2574 ));
2575 assert!(matches!(
2576 parser.parse(b"\x1bOB").first(),
2577 Some(Event::Key(k)) if k.code == KeyCode::Down
2578 ));
2579 assert!(matches!(
2580 parser.parse(b"\x1bOC").first(),
2581 Some(Event::Key(k)) if k.code == KeyCode::Right
2582 ));
2583 assert!(matches!(
2584 parser.parse(b"\x1bOD").first(),
2585 Some(Event::Key(k)) if k.code == KeyCode::Left
2586 ));
2587 }
2588
2589 #[test]
2592 fn xterm_modifier_encoding() {
2593 assert_eq!(InputParser::modifiers_from_xterm(1), Modifiers::NONE);
2595 assert_eq!(InputParser::modifiers_from_xterm(2), Modifiers::SHIFT);
2596 assert_eq!(InputParser::modifiers_from_xterm(3), Modifiers::ALT);
2597 assert_eq!(
2598 InputParser::modifiers_from_xterm(4),
2599 Modifiers::SHIFT | Modifiers::ALT
2600 );
2601 assert_eq!(InputParser::modifiers_from_xterm(5), Modifiers::CTRL);
2602 assert_eq!(
2603 InputParser::modifiers_from_xterm(6),
2604 Modifiers::SHIFT | Modifiers::CTRL
2605 );
2606 assert_eq!(InputParser::modifiers_from_xterm(9), Modifiers::SUPER);
2607 }
2608
2609 #[test]
2612 fn ss3_interrupted_by_esc() {
2613 let mut parser = InputParser::new();
2614 let events = parser.parse(b"\x1bO\x1b[A");
2616 assert!(matches!(
2618 events.first(),
2619 Some(Event::Key(k)) if k.code == KeyCode::Up
2620 ));
2621 }
2622
2623 #[test]
2626 fn kitty_keyboard_reserved_keycode_ignored() {
2627 let mut parser = InputParser::new();
2628 let events = parser.parse(b"\x1b[57360u");
2630 assert!(events.is_empty());
2631
2632 let events = parser.parse(b"x");
2634 assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('x')));
2635 }
2636 #[test]
2637 fn utf8_invalid_sequence_emits_replacement() {
2638 let mut parser = InputParser::new();
2639
2640 let events = parser.parse(&[0xE0, 0x41]);
2644 assert_eq!(events.len(), 2);
2645
2646 match &events[0] {
2647 Event::Key(k) => assert_eq!(k.code, KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2648 _ => panic!("Expected replacement character"),
2649 }
2650
2651 match &events[1] {
2652 Event::Key(k) => assert_eq!(k.code, KeyCode::Char('A')),
2653 _ => panic!("Expected character 'A'"),
2654 }
2655 }
2656
2657 #[test]
2658 fn utf8_invalid_lead_emits_replacement() {
2659 let mut parser = InputParser::new();
2660
2661 let events = parser.parse(&[0xC0, b'a']);
2663 assert!(
2664 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2665 "Expected replacement for invalid lead"
2666 );
2667 assert!(
2668 events
2669 .iter()
2670 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('a'))),
2671 "Expected subsequent ASCII to be preserved"
2672 );
2673
2674 let events = parser.parse(&[0xF5, b'b']);
2676 assert!(
2677 matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2678 "Expected replacement for out-of-range lead"
2679 );
2680 assert!(
2681 events
2682 .iter()
2683 .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('b'))),
2684 "Expected subsequent ASCII to be preserved"
2685 );
2686 }
2687}
2688
2689#[cfg(test)]
2690mod proptest_fuzz {
2691 use super::*;
2692 use proptest::prelude::*;
2693
2694 fn arb_byte() -> impl Strategy<Value = u8> {
2698 any::<u8>()
2699 }
2700
2701 fn arb_byte_vec(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
2702 prop::collection::vec(arb_byte(), 0..=max_len)
2703 }
2704
2705 fn csi_sequence() -> impl Strategy<Value = Vec<u8>> {
2707 let params = prop::collection::vec(0x30u8..=0x3F, 0..=20);
2708 let final_byte = 0x40u8..=0x7E;
2709 (params, final_byte).prop_map(|(p, f)| {
2710 let mut buf = vec![0x1B, b'['];
2711 buf.extend_from_slice(&p);
2712 buf.push(f);
2713 buf
2714 })
2715 }
2716
2717 fn osc_sequence() -> impl Strategy<Value = Vec<u8>> {
2719 let content = prop::collection::vec(0x20u8..=0x7E, 0..=64);
2720 let terminator = prop_oneof![
2721 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
2724 (content, terminator).prop_map(|(c, t)| {
2725 let mut buf = vec![0x1B, b']'];
2726 buf.extend_from_slice(&c);
2727 buf.extend_from_slice(&t);
2728 buf
2729 })
2730 }
2731
2732 fn ss3_sequence() -> impl Strategy<Value = Vec<u8>> {
2734 (0x40u8..=0x7E).prop_map(|f| vec![0x1B, b'O', f])
2735 }
2736
2737 fn paste_sequence() -> impl Strategy<Value = Vec<u8>> {
2739 prop::collection::vec(0x20u8..=0x7E, 0..=128).prop_map(|content| {
2740 let mut buf = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
2741 buf.extend_from_slice(&content);
2742 buf.extend_from_slice(b"\x1b[201~");
2743 buf
2744 })
2745 }
2746
2747 fn mixed_adversarial() -> impl Strategy<Value = Vec<u8>> {
2749 let fragment = prop_oneof![
2750 csi_sequence(),
2751 osc_sequence(),
2752 ss3_sequence(),
2753 paste_sequence(),
2754 arb_byte_vec(16), Just(vec![0x1B]), Just(vec![0x1B, b'[']), Just(vec![0x1B, b']']), prop::collection::vec(0x80u8..=0xFF, 1..=4), ];
2760 prop::collection::vec(fragment, 1..=8)
2761 .prop_map(|frags| frags.into_iter().flatten().collect())
2762 }
2763
2764 proptest! {
2767 #[test]
2769 fn random_bytes_never_panic(input in arb_byte_vec(512)) {
2770 let mut parser = InputParser::new();
2771 let _ = parser.parse(&input);
2772 }
2773
2774 #[test]
2776 fn parser_recovers_after_garbage(input in arb_byte_vec(256)) {
2777 let mut parser = InputParser::new();
2778 let _ = parser.parse(&input);
2779
2780 let events = parser.parse(b"z");
2782 let _ = events;
2785 }
2786
2787 #[test]
2789 fn mixed_sequences_never_panic(input in mixed_adversarial()) {
2790 let mut parser = InputParser::new();
2791 let _ = parser.parse(&input);
2792 }
2793
2794 #[test]
2796 fn events_are_well_formed(input in arb_byte_vec(256)) {
2797 let mut parser = InputParser::new();
2798 let events = parser.parse(&input);
2799 for event in &events {
2800 let _ = format!("{event:?}");
2802 }
2803 }
2804
2805 #[test]
2807 fn csi_event_count_bounded(seq in csi_sequence()) {
2808 let mut parser = InputParser::new();
2809 let events = parser.parse(&seq);
2810 prop_assert!(events.len() <= seq.len(),
2811 "Got {} events from {} bytes", events.len(), seq.len());
2812 }
2813
2814 #[test]
2816 fn osc_event_count_bounded(seq in osc_sequence()) {
2817 let mut parser = InputParser::new();
2818 let events = parser.parse(&seq);
2819 prop_assert!(events.len() <= seq.len(),
2820 "Got {} events from {} bytes", events.len(), seq.len());
2821 }
2822
2823 #[test]
2825 fn paste_content_bounded(content in prop::collection::vec(arb_byte(), 0..=2048)) {
2826 let mut parser = InputParser::new();
2827 let mut input = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
2828 input.extend_from_slice(&content);
2829 input.extend_from_slice(b"\x1b[201~");
2830
2831 let events = parser.parse(&input);
2832 for event in &events {
2833 if let Event::Paste(p) = event {
2834 prop_assert!(p.text.len() <= MAX_PASTE_LEN,
2835 "Paste text {} exceeds limit {}", p.text.len(), MAX_PASTE_LEN);
2836 }
2837 }
2838 }
2839
2840 #[test]
2842 fn incremental_matches_bulk(input in arb_byte_vec(128)) {
2843 let mut bulk_parser = InputParser::new();
2844 let bulk_events = bulk_parser.parse(&input);
2845
2846 let mut incr_parser = InputParser::new();
2847 let mut incr_events = Vec::new();
2848 for byte in &input {
2849 incr_events.extend(incr_parser.parse(std::slice::from_ref(byte)));
2850 }
2851
2852 let bulk_dbg: Vec<String> = bulk_events.iter().map(|e| format!("{e:?}")).collect();
2853 let incr_dbg: Vec<String> = incr_events.iter().map(|e| format!("{e:?}")).collect();
2854 prop_assert_eq!(bulk_dbg, incr_dbg,
2855 "Bulk vs incremental mismatch for input {:?}", input);
2856 }
2857
2858 #[test]
2861 fn deterministic_output(input in arb_byte_vec(128)) {
2862 let mut parser1 = InputParser::new();
2863 let events1 = parser1.parse(&input);
2864
2865 let mut parser2 = InputParser::new();
2866 let events2 = parser2.parse(&input);
2867
2868 let dbg1: Vec<String> = events1.iter().map(|e| format!("{e:?}")).collect();
2869 let dbg2: Vec<String> = events2.iter().map(|e| format!("{e:?}")).collect();
2870 prop_assert_eq!(dbg1, dbg2);
2871 }
2872 }
2873
2874 #[test]
2878 fn no_quadratic_blowup() {
2879 let mut parser = InputParser::new();
2880
2881 let garbage: Vec<u8> = (0..65536).map(|i| (i % 256) as u8).collect();
2883 let _ = parser.parse(&garbage);
2884
2885 let events = parser.parse(b"a");
2887 let _ = events; }
2889
2890 #[test]
2892 fn oversized_csi_transitions_to_ignore() {
2893 let mut parser = InputParser::new();
2894
2895 let mut input = vec![0x1B, b'['];
2897 input.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
2898 input.push(b'm');
2899
2900 let _ = parser.parse(&input);
2901
2902 let events = parser.parse(b"x");
2904 assert_eq!(events.len(), 1);
2905 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('x')));
2906 }
2907
2908 #[test]
2910 fn oversized_osc_transitions_to_ignore() {
2911 let mut parser = InputParser::new();
2912
2913 let mut input = vec![0x1B, b']'];
2915 input.extend(std::iter::repeat_n(b'a', MAX_OSC_LEN + 100));
2916 input.push(0x07); let _ = parser.parse(&input);
2919
2920 let events = parser.parse(b"y");
2922 assert_eq!(events.len(), 1);
2923 assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('y')));
2924 }
2925
2926 #[test]
2928 fn rapid_esc_toggle() {
2929 let mut parser = InputParser::new();
2930
2931 let input: Vec<u8> = vec![0x1B; 1000];
2933 let _ = parser.parse(&input);
2934
2935 let events = parser.parse(b"k");
2937 assert!(!events.is_empty());
2938 }
2939
2940 #[test]
2942 fn unterminated_paste_recovery() {
2943 let mut parser = InputParser::new();
2944
2945 let mut input = b"\x1b[200~".to_vec();
2947 input.extend(std::iter::repeat_n(b'x', 2048));
2948
2949 let _ = parser.parse(&input);
2950
2951 let events = parser.parse(b"\x1b[201~");
2953 assert!(
2954 !events.is_empty(),
2955 "Parser should emit paste event on terminator"
2956 );
2957 }
2958
2959 #[test]
2961 fn truncated_utf8_lead_bytes() {
2962 let mut parser = InputParser::new();
2963
2964 for lead in [0xC2, 0xE0, 0xF0] {
2966 let _ = parser.parse(&[lead]);
2967 let events = parser.parse(b"a");
2969 let _ = events;
2971 }
2972 }
2973
2974 #[test]
2976 fn null_bytes_interleaved() {
2977 let mut parser = InputParser::new();
2978
2979 let input = b"\x00A\x00\x1b[A\x00B\x00";
2980 let events = parser.parse(input);
2981 assert!(
2983 events.len() >= 2,
2984 "Expected at least 2 events, got {}",
2985 events.len()
2986 );
2987 }
2988
2989 fn osc52_sequence() -> impl Strategy<Value = Vec<u8>> {
2993 let selector = prop_oneof![Just(b'c'), Just(b'p'), Just(b's')];
2994 let payload = prop::collection::vec(
2996 prop_oneof![
2997 0x41u8..=0x5A, 0x61u8..=0x7A, 0x30u8..=0x39, Just(b'+'),
3001 Just(b'/'),
3002 Just(b'='),
3003 ],
3004 0..=128,
3005 );
3006 let terminator = prop_oneof![
3007 Just(vec![0x1B, b'\\']), Just(vec![0x07]), ];
3010 (selector, payload, terminator).prop_map(|(sel, pay, term)| {
3011 let mut buf = vec![0x1B, b']', b'5', b'2', b';', sel, b';'];
3012 buf.extend_from_slice(&pay);
3013 buf.extend_from_slice(&term);
3014 buf
3015 })
3016 }
3017
3018 fn sgr_mouse_sequence() -> impl Strategy<Value = Vec<u8>> {
3020 let button_code = 0u16..128;
3021 let x = 1u16..300;
3022 let y = 1u16..100;
3023 let final_byte = prop_oneof![Just(b'M'), Just(b'm')];
3024 (button_code, x, y, final_byte)
3025 .prop_map(|(btn, x, y, fb)| format!("\x1b[<{btn};{x};{y}{}", fb as char).into_bytes())
3026 }
3027
3028 fn kitty_keyboard_sequence() -> impl Strategy<Value = Vec<u8>> {
3030 let keycode = prop_oneof![
3031 0x20u32..0x7F, 0x57344u32..0x57400, 0x100u32..0x200, ];
3035 let modifier = 1u32..16;
3036 let kind = prop_oneof![Just(1u32), Just(2u32), Just(3u32)]; (keycode, prop::option::of(modifier), prop::option::of(kind)).prop_map(
3038 |(kc, mods, kind)| match (mods, kind) {
3039 (Some(m), Some(k)) => format!("\x1b[{kc};{m}:{k}u").into_bytes(),
3040 (Some(m), None) => format!("\x1b[{kc};{m}u").into_bytes(),
3041 _ => format!("\x1b[{kc}u").into_bytes(),
3042 },
3043 )
3044 }
3045
3046 proptest! {
3047 #[test]
3051 fn osc52_never_panics(seq in osc52_sequence()) {
3052 let mut parser = InputParser::new();
3053 let events = parser.parse(&seq);
3054 for event in &events {
3056 if let Event::Clipboard(c) = event {
3057 prop_assert!(!c.content.is_empty() || c.content.is_empty(),
3058 "Clipboard event must have a content field");
3059 }
3060 }
3061 }
3062
3063 #[test]
3065 fn osc52_corrupt_base64_safe(payload in arb_byte_vec(128)) {
3066 let mut parser = InputParser::new();
3067 let mut input = b"\x1b]52;c;".to_vec();
3068 input.extend_from_slice(&payload);
3069 input.push(0x07); let _ = parser.parse(&input);
3071 }
3072
3073 #[test]
3077 fn sgr_mouse_never_panics(seq in sgr_mouse_sequence()) {
3078 let mut parser = InputParser::new();
3079 let events = parser.parse(&seq);
3080 for event in &events {
3081 let _ = format!("{event:?}");
3083 }
3084 }
3085
3086 #[test]
3088 fn sgr_mouse_extreme_coords(
3089 btn in 0u16..128,
3090 x in 0u16..=65535,
3091 y in 0u16..=65535,
3092 ) {
3093 let mut parser = InputParser::new();
3094 let input = format!("\x1b[<{btn};{x};{y}M").into_bytes();
3095 let events = parser.parse(&input);
3096 for event in &events {
3097 if let Event::Mouse(m) = event {
3098 prop_assert!(m.x <= x, "Mouse x {} > input x {}", m.x, x);
3099 prop_assert!(m.y <= y, "Mouse y {} > input y {}", m.y, y);
3100 }
3101 }
3102 }
3103
3104 #[test]
3108 fn kitty_keyboard_never_panics(seq in kitty_keyboard_sequence()) {
3109 let mut parser = InputParser::new();
3110 let _ = parser.parse(&seq);
3111 }
3112
3113 #[test]
3117 fn truncated_csi_then_valid(
3118 params in prop::collection::vec(0x30u8..=0x3F, 1..=10),
3119 valid_char in 0x20u8..0x7F,
3120 ) {
3121 let mut parser = InputParser::new();
3122
3123 let mut partial = vec![0x1B, b'['];
3125 partial.extend_from_slice(¶ms);
3126 let _ = parser.parse(&partial);
3127
3128 let events = parser.parse(&[0x1B, b'[', b'A']); let _ = events;
3133
3134 let events = parser.parse(&[valid_char]);
3136 let _ = events;
3137 }
3138
3139 #[test]
3141 fn truncated_osc_then_valid(
3142 content in prop::collection::vec(0x20u8..=0x7E, 1..=32),
3143 ) {
3144 let mut parser = InputParser::new();
3145
3146 let mut partial = vec![0x1B, b']'];
3148 partial.extend_from_slice(&content);
3149 let _ = parser.parse(&partial);
3150
3151 let events = parser.parse(b"\x1bz");
3153 let _ = events;
3154 }
3155
3156 #[test]
3160 fn csi_near_limit_produces_event(
3161 fill_byte in 0x30u8..=0x39, ) {
3163 let mut parser = InputParser::new();
3164
3165 let mut input = vec![0x1B, b'['];
3166 input.extend(std::iter::repeat_n(fill_byte, MAX_CSI_LEN - 1));
3168 input.push(b'm'); let events = parser.parse(&input);
3171 let _ = events;
3174
3175 let events = parser.parse(b"a");
3177 prop_assert!(!events.is_empty(), "Parser stuck after near-limit CSI");
3178 }
3179
3180 #[test]
3182 fn osc_near_limit_processes(
3183 fill_byte in 0x20u8..=0x7E,
3184 ) {
3185 let mut parser = InputParser::new();
3186
3187 let mut input = vec![0x1B, b']'];
3188 input.extend(std::iter::repeat_n(fill_byte, MAX_OSC_LEN - 1));
3189 input.push(0x07); let _ = parser.parse(&input);
3192
3193 let events = parser.parse(b"b");
3195 prop_assert!(!events.is_empty(), "Parser stuck after near-limit OSC");
3196 }
3197
3198 #[test]
3202 fn consecutive_pastes_emit_events(count in 2usize..=5) {
3203 let mut parser = InputParser::new();
3204 let mut input = Vec::new();
3205
3206 for i in 0..count {
3207 input.extend_from_slice(b"\x1b[200~");
3208 input.extend_from_slice(format!("paste_{i}").as_bytes());
3209 input.extend_from_slice(b"\x1b[201~");
3210 }
3211
3212 let events = parser.parse(&input);
3213 let paste_events: Vec<_> = events.iter()
3214 .filter(|e| matches!(e, Event::Paste(_)))
3215 .collect();
3216
3217 prop_assert_eq!(paste_events.len(), count,
3218 "Expected {} paste events, got {}", count, paste_events.len());
3219 }
3220
3221 #[test]
3223 fn paste_with_invalid_utf8(content in arb_byte_vec(256)) {
3224 let mut parser = InputParser::new();
3225 let mut input = b"\x1b[200~".to_vec();
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.is_char_boundary(0), "Paste text is not valid UTF-8");
3234 }
3235 }
3236 }
3237
3238 #[test]
3242 fn recovery_via_esc_reset(garbage in arb_byte_vec(256)) {
3243 let mut parser = InputParser::new();
3244 let _ = parser.parse(&garbage);
3245
3246 let _ = parser.parse(b"\x07\x1b\\\x1b");
3249 let _ = parser.parse(b"\x1b");
3250
3251 let _ = parser.parse(b"z");
3253
3254 let events = parser.parse(b"q");
3256 prop_assert!(!events.is_empty(),
3259 "Parser did not recover after garbage + reset");
3260 }
3261 }
3262}