Skip to main content

ftui_core/
input_parser.rs

1#![forbid(unsafe_code)]
2
3//! Input parser state machine.
4//!
5//! Decodes terminal input bytes into [`crate::event::Event`] values with DoS protection.
6//!
7//! # Design
8//!
9//! The parser is a state machine that handles:
10//! - ASCII characters and control codes
11//! - UTF-8 multi-byte sequences
12//! - CSI (Control Sequence Introducer) sequences
13//! - SS3 (Single Shift 3) sequences
14//! - OSC (Operating System Command) sequences
15//! - Bracketed paste mode
16//! - Mouse events (SGR protocol)
17//! - Focus events
18//!
19//! # DoS Protection
20//!
21//! The parser enforces length limits on all sequence types to prevent memory exhaustion:
22//! - CSI sequences: 256 bytes max
23//! - OSC sequences: 4KB max
24//! - Paste content: 1MB max
25
26use crate::event::{
27    ClipboardEvent, ClipboardSource, Event, KeyCode, KeyEvent, KeyEventKind, Modifiers,
28    MouseButton, MouseEvent, MouseEventKind, PasteEvent,
29};
30
31// Import tracing macros (no-op when tracing feature is disabled).
32#[cfg(feature = "tracing")]
33use crate::logging::{debug, debug_span, trace};
34#[cfg(not(feature = "tracing"))]
35use crate::{debug, debug_span, trace};
36
37/// DoS protection: maximum CSI sequence length.
38const MAX_CSI_LEN: usize = 256;
39
40/// DoS protection: maximum OSC sequence length.
41const MAX_OSC_LEN: usize = 4096;
42
43/// DoS protection: maximum paste content length.
44const MAX_PASTE_LEN: usize = 1024 * 1024; // 1MB
45/// Upper bound for event vector preallocation hints.
46///
47/// Keep this bounded so callers passing very large slices do not cause
48/// disproportionate reserve spikes.
49const MAX_EVENT_RESERVE_HINT: usize = 8 * 1024 + 1;
50
51/// Parser state machine states.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
53enum ParserState {
54    /// Normal character input.
55    #[default]
56    Ground,
57    /// After ESC (0x1B).
58    Escape,
59    /// After ESC [ (CSI introducer).
60    Csi,
61    /// Collecting CSI parameters.
62    CsiParam,
63    /// Ignoring oversized CSI sequence.
64    CsiIgnore,
65    /// After ESC O (SS3 introducer).
66    Ss3,
67    /// After ESC ] (OSC introducer).
68    Osc,
69    /// Collecting OSC content.
70    OscContent,
71    /// After ESC inside OSC (for ESC \ terminator).
72    OscEscape,
73    /// Ignoring oversized OSC sequence.
74    OscIgnore,
75    /// Collecting UTF-8 multi-byte sequence.
76    Utf8 {
77        /// Bytes collected so far.
78        collected: u8,
79        /// Total bytes expected.
80        expected: u8,
81    },
82}
83
84/// Terminal input parser with DoS protection.
85///
86/// Parse terminal input bytes into events:
87///
88/// ```ignore
89/// let mut parser = InputParser::new();
90/// let events = parser.parse(b"\x1b[A"); // Up arrow
91/// assert_eq!(events.len(), 1);
92/// ```
93#[derive(Debug)]
94pub struct InputParser {
95    /// Current parser state.
96    state: ParserState,
97    /// Buffer for accumulating sequence bytes.
98    buffer: Vec<u8>,
99    /// Buffer for collecting paste content.
100    paste_buffer: Vec<u8>,
101    /// UTF-8 bytes collected so far.
102    utf8_buffer: [u8; 4],
103    /// Whether we're in bracketed paste mode.
104    in_paste: bool,
105    /// Event queued for the next iteration (allows emitting 2 events per byte).
106    pending_event: Option<Event>,
107}
108
109impl Default for InputParser {
110    fn default() -> Self {
111        Self::new()
112    }
113}
114
115impl InputParser {
116    #[inline]
117    fn event_reserve_hint(input_len: usize) -> usize {
118        input_len.saturating_add(1).min(MAX_EVENT_RESERVE_HINT)
119    }
120
121    /// Create a new input parser.
122    #[must_use]
123    pub fn new() -> Self {
124        Self {
125            state: ParserState::Ground,
126            buffer: Vec::with_capacity(64),
127            paste_buffer: Vec::new(),
128            utf8_buffer: [0; 4],
129            in_paste: false,
130            pending_event: None,
131        }
132    }
133
134    /// Parse input bytes and return any completed events.
135    pub fn parse(&mut self, input: &[u8]) -> Vec<Event> {
136        let mut events = Vec::with_capacity(Self::event_reserve_hint(input.len()));
137        self.parse_with(input, |event| events.push(event));
138        events
139    }
140
141    /// Parse input bytes and emit each completed event through `emit`.
142    pub fn parse_with<F>(&mut self, input: &[u8], mut emit: F)
143    where
144        F: FnMut(Event),
145    {
146        let span = debug_span!("event.normalize", raw_byte_count = input.len());
147        let _guard = span.enter();
148        trace!("raw input bytes: {} bytes", input.len());
149
150        for &byte in input {
151            if let Some(event) = self.process_byte(byte) {
152                debug!(event_type = event.event_type_label(), "normalized event");
153                emit(event);
154            }
155            if let Some(pending) = self.pending_event.take() {
156                debug!(event_type = pending.event_type_label(), "normalized event");
157                emit(pending);
158            }
159        }
160    }
161
162    /// Parse input bytes and append completed events to `events`.
163    ///
164    /// This variant lets callers reuse a scratch buffer across parses to avoid
165    /// repeated allocations on hot input paths.
166    pub fn parse_into(&mut self, input: &[u8], events: &mut Vec<Event>) {
167        let needed = Self::event_reserve_hint(input.len());
168        let available = events.capacity().saturating_sub(events.len());
169        if available < needed {
170            events.reserve(needed - available);
171        }
172        self.parse_with(input, |event| events.push(event));
173    }
174
175    /// Process a single byte and optionally return an event.
176    fn process_byte(&mut self, byte: u8) -> Option<Event> {
177        // In paste mode, collect bytes until end sequence
178        if self.in_paste {
179            return self.process_paste_byte(byte);
180        }
181
182        match self.state {
183            ParserState::Ground => self.process_ground(byte),
184            ParserState::Escape => self.process_escape(byte),
185            ParserState::Csi => self.process_csi(byte),
186            ParserState::CsiParam => self.process_csi_param(byte),
187            ParserState::CsiIgnore => self.process_csi_ignore(byte),
188            ParserState::Ss3 => self.process_ss3(byte),
189            ParserState::Osc => self.process_osc(byte),
190            ParserState::OscContent => self.process_osc_content(byte),
191            ParserState::OscEscape => self.process_osc_escape(byte),
192            ParserState::OscIgnore => self.process_osc_ignore(byte),
193            ParserState::Utf8 {
194                collected,
195                expected,
196            } => self.process_utf8(byte, collected, expected),
197        }
198    }
199
200    /// Process byte in ground state.
201    fn process_ground(&mut self, byte: u8) -> Option<Event> {
202        match byte {
203            // ESC - start escape sequence
204            0x1B => {
205                self.state = ParserState::Escape;
206                None
207            }
208            // NUL - Ctrl+Space or Ctrl+@
209            0x00 => Some(Event::Key(KeyEvent::new(KeyCode::Null))),
210            // Backspace alternate (Ctrl+H)
211            0x08 => Some(Event::Key(KeyEvent::new(KeyCode::Backspace))),
212            // Tab (Ctrl+I) - check before generic Ctrl range
213            0x09 => Some(Event::Key(KeyEvent::new(KeyCode::Tab))),
214            // Enter (Ctrl+M) - check before generic Ctrl range
215            0x0D => Some(Event::Key(KeyEvent::new(KeyCode::Enter))),
216            // Other Ctrl+A through Ctrl+Z (0x01-0x1A excluding Tab and Enter)
217            0x01..=0x07 | 0x0A..=0x0C | 0x0E..=0x1A => {
218                let c = (byte + b'a' - 1) as char;
219                Some(Event::Key(
220                    KeyEvent::new(KeyCode::Char(c)).with_modifiers(Modifiers::CTRL),
221                ))
222            }
223            // Backspace (DEL)
224            0x7F => Some(Event::Key(KeyEvent::new(KeyCode::Backspace))),
225            // Printable ASCII
226            0x20..=0x7E => Some(Event::Key(KeyEvent::new(KeyCode::Char(byte as char)))),
227            // UTF-8 lead bytes (valid ranges only)
228            0xC2..=0xDF => {
229                self.utf8_buffer[0] = byte;
230                self.state = ParserState::Utf8 {
231                    collected: 1,
232                    expected: 2,
233                };
234                None
235            }
236            0xE0..=0xEF => {
237                self.utf8_buffer[0] = byte;
238                self.state = ParserState::Utf8 {
239                    collected: 1,
240                    expected: 3,
241                };
242                None
243            }
244            0xF0..=0xF4 => {
245                self.utf8_buffer[0] = byte;
246                self.state = ParserState::Utf8 {
247                    collected: 1,
248                    expected: 4,
249                };
250                None
251            }
252            // Invalid UTF-8 lead bytes (overlong or out of range)
253            0xC0..=0xC1 | 0xF5..=0xFF => Some(Event::Key(KeyEvent::new(KeyCode::Char(
254                std::char::REPLACEMENT_CHARACTER,
255            )))),
256            // Invalid or ignored bytes
257            _ => None,
258        }
259    }
260
261    /// Process byte after ESC.
262    fn process_escape(&mut self, byte: u8) -> Option<Event> {
263        match byte {
264            // CSI introducer
265            b'[' => {
266                self.state = ParserState::Csi;
267                self.buffer.clear();
268                None
269            }
270            // SS3 introducer
271            b'O' => {
272                self.state = ParserState::Ss3;
273                None
274            }
275            // OSC introducer
276            b']' => {
277                self.state = ParserState::Osc;
278                self.buffer.clear();
279                None
280            }
281            // Another ESC - emit Alt+Escape and reset to ground
282            // (or treat as start of new sequence - but ESC ESC is usually Alt+ESC)
283            0x1B => {
284                self.state = ParserState::Ground;
285                Some(Event::Key(
286                    KeyEvent::new(KeyCode::Escape).with_modifiers(Modifiers::ALT),
287                ))
288            }
289            // Alt+letter or Alt+char
290            0x20..=0x7E => {
291                self.state = ParserState::Ground;
292                Some(Event::Key(
293                    KeyEvent::new(KeyCode::Char(byte as char)).with_modifiers(Modifiers::ALT),
294                ))
295            }
296            // Alt+Backspace (DEL)
297            0x7F => {
298                self.state = ParserState::Ground;
299                Some(Event::Key(
300                    KeyEvent::new(KeyCode::Backspace).with_modifiers(Modifiers::ALT),
301                ))
302            }
303            // Invalid - return to ground
304            _ => {
305                self.state = ParserState::Ground;
306                None
307            }
308        }
309    }
310
311    /// Process byte at start of CSI sequence.
312    fn process_csi(&mut self, byte: u8) -> Option<Event> {
313        // Robustness: ESC restarts sequence
314        if byte == 0x1B {
315            self.state = ParserState::Escape;
316            self.buffer.clear();
317            return None;
318        }
319
320        self.buffer.push(byte);
321
322        match byte {
323            // Parameter bytes (0x30-0x3F) and Intermediate bytes (0x20-0x2F)
324            0x20..=0x3F => {
325                self.state = ParserState::CsiParam;
326                None
327            }
328            // Final byte (0x40-0x7E) - parse and return
329            0x40..=0x7E => {
330                self.state = ParserState::Ground;
331                self.parse_csi_sequence()
332            }
333            // Invalid (0x00-0x1F, 0x7F-0xFF)
334            _ => {
335                self.state = ParserState::Ground;
336                self.buffer.clear();
337                None
338            }
339        }
340    }
341
342    /// Process byte while collecting CSI parameters.
343    fn process_csi_param(&mut self, byte: u8) -> Option<Event> {
344        // Robustness: ESC restarts sequence
345        if byte == 0x1B {
346            self.state = ParserState::Escape;
347            self.buffer.clear();
348            return None;
349        }
350
351        // DoS protection
352        if self.buffer.len() >= MAX_CSI_LEN {
353            self.state = ParserState::CsiIgnore;
354            self.buffer.clear();
355            return None;
356        }
357
358        self.buffer.push(byte);
359
360        match byte {
361            // Continue collecting parameters/intermediates
362            0x20..=0x3F => None,
363            // Final byte - parse and return
364            0x40..=0x7E => {
365                self.state = ParserState::Ground;
366                self.parse_csi_sequence()
367            }
368            // Invalid
369            _ => {
370                self.state = ParserState::Ground;
371                self.buffer.clear();
372                None
373            }
374        }
375    }
376
377    /// Ignore bytes until end of CSI sequence.
378    fn process_csi_ignore(&mut self, byte: u8) -> Option<Event> {
379        // Robustness: ESC restarts sequence
380        if byte == 0x1B {
381            self.state = ParserState::Escape;
382            return None;
383        }
384
385        // Final byte (0x40-0x7E) - return to ground
386        // Intermediate bytes outside this range are ignored
387        if (0x40..=0x7E).contains(&byte) {
388            self.state = ParserState::Ground;
389        } else if !(0x20..=0x7E).contains(&byte) {
390            // Invalid character (e.g. newline) - abort sequence
391            self.state = ParserState::Ground;
392        }
393        None
394    }
395
396    /// Parse a complete CSI sequence from the buffer.
397    fn parse_csi_sequence(&mut self) -> Option<Event> {
398        let seq = std::mem::take(&mut self.buffer);
399        if seq.is_empty() {
400            return None;
401        }
402
403        let final_byte = *seq.last()?;
404        let params = &seq[..seq.len() - 1];
405
406        // Check for special sequences first
407        match (params, final_byte) {
408            // Focus events
409            ([], b'I') => return Some(Event::Focus(true)),
410            ([], b'O') => return Some(Event::Focus(false)),
411
412            // Bracketed paste
413            (b"200", b'~') => {
414                self.in_paste = true;
415                self.paste_buffer.clear();
416                self.buffer.clear(); // Ensure tail buffer is clean
417                return None;
418            }
419            (b"201", b'~') => {
420                self.in_paste = false;
421                let content = String::from_utf8_lossy(&self.paste_buffer).into_owned();
422                self.paste_buffer.clear();
423                return Some(Event::Paste(PasteEvent::bracketed(content)));
424            }
425
426            // SGR mouse protocol
427            _ if params.starts_with(b"<") && (final_byte == b'M' || final_byte == b'm') => {
428                return self.parse_sgr_mouse(params, final_byte);
429            }
430
431            _ => {}
432        }
433
434        // Arrow keys and other CSI sequences
435        match final_byte {
436            b'A' => Some(Event::Key(self.key_with_modifiers(KeyCode::Up, params))),
437            b'B' => Some(Event::Key(self.key_with_modifiers(KeyCode::Down, params))),
438            b'C' => Some(Event::Key(self.key_with_modifiers(KeyCode::Right, params))),
439            b'D' => Some(Event::Key(self.key_with_modifiers(KeyCode::Left, params))),
440            b'H' => Some(Event::Key(self.key_with_modifiers(KeyCode::Home, params))),
441            b'F' => Some(Event::Key(self.key_with_modifiers(KeyCode::End, params))),
442            b'Z' => Some(Event::Key(
443                self.key_with_modifiers(KeyCode::BackTab, params),
444            )),
445            b'~' => self.parse_csi_tilde(params),
446            b'u' => self.parse_kitty_keyboard(params),
447            _ => None,
448        }
449    }
450
451    /// Parse CSI sequences ending in ~.
452    fn parse_csi_tilde(&self, params: &[u8]) -> Option<Event> {
453        let num = self.parse_first_param(params)?;
454        let mods = self.parse_modifier_param(params);
455
456        let code = match num {
457            1 => KeyCode::Home,
458            2 => KeyCode::Insert,
459            3 => KeyCode::Delete,
460            4 => KeyCode::End,
461            5 => KeyCode::PageUp,
462            6 => KeyCode::PageDown,
463            15 => KeyCode::F(5),
464            17 => KeyCode::F(6),
465            18 => KeyCode::F(7),
466            19 => KeyCode::F(8),
467            20 => KeyCode::F(9),
468            21 => KeyCode::F(10),
469            23 => KeyCode::F(11),
470            24 => KeyCode::F(12),
471            _ => return None,
472        };
473
474        Some(Event::Key(KeyEvent::new(code).with_modifiers(mods)))
475    }
476
477    /// Parse the first numeric parameter from CSI params.
478    fn parse_first_param(&self, params: &[u8]) -> Option<u32> {
479        let s = std::str::from_utf8(params).ok()?;
480        let first = s.split(';').next()?;
481        first.parse().ok()
482    }
483
484    /// Parse modifier parameter (second param in CSI sequences).
485    fn parse_modifier_param(&self, params: &[u8]) -> Modifiers {
486        let s = match std::str::from_utf8(params) {
487            Ok(s) => s,
488            Err(_) => return Modifiers::NONE,
489        };
490
491        let modifier_value: u32 = s
492            .split(';')
493            .nth(1)
494            .and_then(|s| s.parse().ok())
495            .unwrap_or(1);
496
497        Self::modifiers_from_xterm(modifier_value)
498    }
499
500    /// Parse Kitty keyboard protocol CSI u sequences.
501    ///
502    /// Format: `CSI unicode-key-code:alt-keys ; modifiers:event-type ; text-as-codepoints u`
503    fn parse_kitty_keyboard(&self, params: &[u8]) -> Option<Event> {
504        let s = std::str::from_utf8(params).ok()?;
505        if s.is_empty() {
506            return None;
507        }
508
509        let mut parts = s.split(';');
510        let key_part = parts.next().unwrap_or("");
511        let key_code_str = key_part.split(':').next().unwrap_or("");
512        let key_code: u32 = key_code_str.parse().ok()?;
513
514        let mod_part = parts.next().unwrap_or("");
515        let (modifiers, kind) = Self::kitty_modifiers_and_kind(mod_part);
516
517        let code = Self::kitty_keycode_to_keycode(key_code)?;
518        Some(Event::Key(
519            KeyEvent::new(code)
520                .with_modifiers(modifiers)
521                .with_kind(kind),
522        ))
523    }
524
525    fn kitty_modifiers_and_kind(mod_part: &str) -> (Modifiers, KeyEventKind) {
526        if mod_part.is_empty() {
527            return (Modifiers::NONE, KeyEventKind::Press);
528        }
529
530        let mut parts = mod_part.split(':');
531        let mod_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
532        let kind_value: u32 = parts.next().and_then(|v| v.parse().ok()).unwrap_or(1);
533
534        let modifiers = Self::modifiers_from_xterm(mod_value);
535        let kind = match kind_value {
536            2 => KeyEventKind::Repeat,
537            3 => KeyEventKind::Release,
538            _ => KeyEventKind::Press,
539        };
540
541        (modifiers, kind)
542    }
543
544    fn kitty_keycode_to_keycode(key_code: u32) -> Option<KeyCode> {
545        match key_code {
546            // Standard ASCII keys
547            9 => Some(KeyCode::Tab),
548            13 => Some(KeyCode::Enter),
549            27 => Some(KeyCode::Escape),
550            8 | 127 => Some(KeyCode::Backspace),
551            // Kitty keyboard protocol extended keys (CSI u)
552            57_344 => Some(KeyCode::Escape),
553            57_345 => Some(KeyCode::Enter),
554            57_346 => Some(KeyCode::Tab),
555            57_347 => Some(KeyCode::Backspace),
556            57_348 => Some(KeyCode::Insert),
557            57_349 => Some(KeyCode::Delete),
558            57_350 => Some(KeyCode::Left),
559            57_351 => Some(KeyCode::Right),
560            57_352 => Some(KeyCode::Up),
561            57_353 => Some(KeyCode::Down),
562            57_354 => Some(KeyCode::PageUp),
563            57_355 => Some(KeyCode::PageDown),
564            57_356 => Some(KeyCode::Home),
565            57_357 => Some(KeyCode::End),
566            // F1-F24 (57_364-57_387)
567            57_364..=57_387 => {
568                // Safety: range is [57_364, 57_387], so (key_code - 57_364 + 1) is [1, 24]
569                // which fits in u8. We use debug_assert to catch any future range changes.
570                let f_num = key_code - 57_364 + 1;
571                debug_assert!(f_num <= 24, "F-key number {f_num} exceeds F24");
572                Some(KeyCode::F(f_num as u8))
573            }
574            // Reserved/unhandled Kitty keycodes return None
575            57_358..=57_363 | 57_388..=63_743 => None,
576            // Unicode codepoints
577            _ => char::from_u32(key_code).map(KeyCode::Char),
578        }
579    }
580
581    fn modifiers_from_xterm(value: u32) -> Modifiers {
582        // xterm modifier encoding: value = 1 + modifier_bits
583        // Shift=1, Alt=2, Ctrl=4, Super=8
584        let bits = value.saturating_sub(1);
585        let mut mods = Modifiers::NONE;
586        if bits & 1 != 0 {
587            mods |= Modifiers::SHIFT;
588        }
589        if bits & 2 != 0 {
590            mods |= Modifiers::ALT;
591        }
592        if bits & 4 != 0 {
593            mods |= Modifiers::CTRL;
594        }
595        if bits & 8 != 0 {
596            mods |= Modifiers::SUPER;
597        }
598        mods
599    }
600
601    /// Create a key event with modifiers from CSI params.
602    fn key_with_modifiers(&self, code: KeyCode, params: &[u8]) -> KeyEvent {
603        KeyEvent::new(code).with_modifiers(self.parse_modifier_param(params))
604    }
605
606    /// Parse SGR mouse protocol events.
607    fn parse_sgr_mouse(&self, params: &[u8], final_byte: u8) -> Option<Event> {
608        // Format: CSI < button ; x ; y M|m
609        // Skip the leading '<'
610        let params = &params[1..];
611        let s = std::str::from_utf8(params).ok()?;
612        let mut parts = s.split(';');
613
614        let button_code: u16 = parts.next()?.parse().ok()?;
615        let x: u16 = parts.next()?.parse().ok()?;
616        let y: u16 = parts.next()?.parse().ok()?;
617
618        // Decode button and modifiers
619        let (button, mods) = self.decode_mouse_button(button_code);
620
621        let kind = if final_byte == b'M' {
622            if button_code & 64 != 0 {
623                // Scroll event: bit 6 (64) is set
624                // bits 0-1 determine direction: 0=up, 1=down, 2=left, 3=right
625                match button_code & 3 {
626                    0 => MouseEventKind::ScrollUp,
627                    1 => MouseEventKind::ScrollDown,
628                    2 => MouseEventKind::ScrollLeft,
629                    _ => MouseEventKind::ScrollRight,
630                }
631            } else if button_code & 32 != 0 {
632                // Motion event (bit 5 set)
633                // bits 0-1: 0=left, 1=middle, 2=right, 3=no button (moved)
634                if button_code & 3 == 3 {
635                    MouseEventKind::Moved
636                } else {
637                    MouseEventKind::Drag(button)
638                }
639            } else {
640                MouseEventKind::Down(button)
641            }
642        } else {
643            MouseEventKind::Up(button)
644        };
645
646        Some(Event::Mouse(MouseEvent {
647            kind,
648            x: x.saturating_sub(1), // Convert to 0-indexed
649            y: y.saturating_sub(1),
650            modifiers: mods,
651        }))
652    }
653
654    /// Decode mouse button code to button and modifiers.
655    fn decode_mouse_button(&self, code: u16) -> (MouseButton, Modifiers) {
656        let button = match code & 0b11 {
657            0 => MouseButton::Left,
658            1 => MouseButton::Middle,
659            2 => MouseButton::Right,
660            _ => MouseButton::Left,
661        };
662
663        let mut mods = Modifiers::NONE;
664        if code & 4 != 0 {
665            mods |= Modifiers::SHIFT;
666        }
667        if code & 8 != 0 {
668            mods |= Modifiers::ALT;
669        }
670        if code & 16 != 0 {
671            mods |= Modifiers::CTRL;
672        }
673
674        (button, mods)
675    }
676
677    /// Process SS3 (ESC O) sequences.
678    fn process_ss3(&mut self, byte: u8) -> Option<Event> {
679        // Robustness: ESC restarts sequence
680        if byte == 0x1B {
681            self.state = ParserState::Escape;
682            return None;
683        }
684
685        self.state = ParserState::Ground;
686
687        let code = match byte {
688            b'P' => KeyCode::F(1),
689            b'Q' => KeyCode::F(2),
690            b'R' => KeyCode::F(3),
691            b'S' => KeyCode::F(4),
692            b'A' => KeyCode::Up,
693            b'B' => KeyCode::Down,
694            b'C' => KeyCode::Right,
695            b'D' => KeyCode::Left,
696            b'H' => KeyCode::Home,
697            b'F' => KeyCode::End,
698            _ => return None,
699        };
700
701        Some(Event::Key(KeyEvent::new(code)))
702    }
703
704    /// Process OSC start.
705    fn process_osc(&mut self, byte: u8) -> Option<Event> {
706        // Handle ESC as potential ST terminator (ESC \) - don't add to buffer
707        if byte == 0x1B {
708            self.state = ParserState::OscEscape;
709            return None;
710        }
711
712        self.buffer.push(byte);
713
714        match byte {
715            // BEL terminates immediately
716            0x07 => {
717                self.state = ParserState::Ground;
718                self.parse_osc_sequence()
719            }
720            // Continue collecting
721            _ => {
722                self.state = ParserState::OscContent;
723                None
724            }
725        }
726    }
727
728    /// Process OSC content.
729    fn process_osc_content(&mut self, byte: u8) -> Option<Event> {
730        // Handle ESC (0x1B) as potential terminator or reset
731        if byte == 0x1B {
732            self.state = ParserState::OscEscape;
733            return None;
734        }
735
736        // Robustness: Abort on control characters (except BEL) to prevent swallowing logs
737        if byte < 0x20 && byte != 0x07 {
738            self.state = ParserState::Ground;
739            self.buffer.clear();
740            return None;
741        }
742
743        // DoS protection
744        if self.buffer.len() >= MAX_OSC_LEN {
745            self.state = ParserState::OscIgnore;
746            self.buffer.clear();
747            return None;
748        }
749
750        match byte {
751            // BEL terminates
752            0x07 => {
753                self.state = ParserState::Ground;
754                self.parse_osc_sequence()
755            }
756            // Continue collecting
757            _ => {
758                self.buffer.push(byte);
759                None
760            }
761        }
762    }
763
764    /// Process ESC inside OSC (checking for ST terminator).
765    fn process_osc_escape(&mut self, byte: u8) -> Option<Event> {
766        if byte == b'\\' {
767            // ST (String Terminator) found
768            self.state = ParserState::Ground;
769            self.parse_osc_sequence()
770        } else if byte == 0x1B {
771            // ESC ESC - treat second ESC as start of new sequence (restart)
772            self.state = ParserState::Escape;
773            self.buffer.clear();
774            None
775        } else {
776            // ESC followed by something else.
777            // Strict ANSI would say the OSC is cancelled by the ESC.
778            // We treat this as a restart of parsing at the *current* byte,
779            // effectively interpreting the previous ESC as a cancel.
780
781            self.buffer.clear();
782            self.state = ParserState::Escape;
783            self.process_escape(byte)
784        }
785    }
786
787    /// Ignore bytes until end of OSC sequence.
788    fn process_osc_ignore(&mut self, byte: u8) -> Option<Event> {
789        match byte {
790            // BEL terminates
791            0x07 => {
792                self.state = ParserState::Ground;
793                None
794            }
795            // ESC might start terminator or new sequence
796            0x1B => {
797                self.state = ParserState::OscEscape;
798                None
799            }
800            // Abort on control characters to prevent swallowing logs
801            _ if byte < 0x20 => {
802                self.state = ParserState::Ground;
803                None
804            }
805            // Continue ignoring
806            _ => None,
807        }
808    }
809
810    /// Parse a complete OSC sequence.
811    fn parse_osc_sequence(&mut self) -> Option<Event> {
812        let seq = std::mem::take(&mut self.buffer);
813
814        // OSC 52 clipboard response: OSC 52 ; c ; <base64> BEL/ST
815        if seq.starts_with(b"52;") {
816            return self.parse_osc52_clipboard(&seq);
817        }
818
819        // Other OSC sequences (e.g., OSC 8 hyperlinks) are not parsed as events
820        None
821    }
822
823    /// Parse OSC 52 clipboard response.
824    fn parse_osc52_clipboard(&self, seq: &[u8]) -> Option<Event> {
825        // Format: 52;c;<base64> or 52;p;<base64>
826        let content = &seq[3..]; // Skip "52;"
827        if content.is_empty() {
828            return None;
829        }
830
831        // OSC 52 uses clipboard selectors: c=clipboard, p=primary, s=secondary
832        // We map all to Osc52 source type since that's how we received it
833        let source = ClipboardSource::Osc52;
834
835        // Skip "c;" prefix
836        let base64_start = content.iter().position(|&b| b == b';').map(|i| i + 1)?;
837        let base64_data = &content[base64_start..];
838
839        // Decode base64 (simple implementation)
840        let decoded = self.decode_base64(base64_data)?;
841
842        Some(Event::Clipboard(ClipboardEvent::new(
843            String::from_utf8_lossy(&decoded).into_owned(),
844            source,
845        )))
846    }
847
848    /// Simple base64 decoder.
849    fn decode_base64(&self, input: &[u8]) -> Option<Vec<u8>> {
850        const DECODE_TABLE: [i8; 256] = {
851            let mut table = [-1i8; 256];
852            let mut i = 0u8;
853            while i < 26 {
854                table[(b'A' + i) as usize] = i as i8;
855                table[(b'a' + i) as usize] = (i + 26) as i8;
856                i += 1;
857            }
858            let mut i = 0u8;
859            while i < 10 {
860                table[(b'0' + i) as usize] = (i + 52) as i8;
861                i += 1;
862            }
863            table[b'+' as usize] = 62;
864            table[b'/' as usize] = 63;
865            table
866        };
867
868        let mut output = Vec::with_capacity(input.len() * 3 / 4);
869        let mut buffer = 0u32;
870        let mut bits = 0u8;
871
872        for &byte in input {
873            if byte == b'=' {
874                break;
875            }
876            let value = DECODE_TABLE[byte as usize];
877            if value < 0 {
878                continue; // Skip whitespace/invalid
879            }
880            buffer = (buffer << 6) | (value as u32);
881            bits += 6;
882            if bits >= 8 {
883                bits -= 8;
884                output.push((buffer >> bits) as u8);
885                buffer &= (1 << bits) - 1;
886            }
887        }
888
889        Some(output)
890    }
891
892    /// Process UTF-8 continuation bytes.
893    fn process_utf8(&mut self, byte: u8, collected: u8, expected: u8) -> Option<Event> {
894        // Check for valid continuation byte
895        if (byte & 0xC0) != 0x80 {
896            // Invalid - return to ground and re-process the unexpected byte.
897            // Also emit a replacement character for the invalid sequence we just aborted.
898            self.state = ParserState::Ground;
899
900            // Queue the replacement event for the next iteration of the parse loop
901            self.pending_event = self.process_ground(byte);
902
903            return Some(Event::Key(KeyEvent::new(KeyCode::Char(
904                std::char::REPLACEMENT_CHARACTER,
905            ))));
906        }
907
908        self.utf8_buffer[collected as usize] = byte;
909        let new_collected = collected + 1;
910
911        if new_collected == expected {
912            // Complete - decode and emit
913            self.state = ParserState::Ground;
914            let s = std::str::from_utf8(&self.utf8_buffer[..expected as usize]).ok()?;
915            let c = s.chars().next()?;
916            Some(Event::Key(KeyEvent::new(KeyCode::Char(c))))
917        } else {
918            // Need more bytes
919            self.state = ParserState::Utf8 {
920                collected: new_collected,
921                expected,
922            };
923            None
924        }
925    }
926
927    /// Process bytes while in paste mode.
928    fn process_paste_byte(&mut self, byte: u8) -> Option<Event> {
929        const END_SEQ: &[u8] = b"\x1b[201~";
930
931        // Logic:
932        // 1. If we have room in paste_buffer, push it.
933        // 2. If we are full, push to self.buffer (used as a tail tracker) to detect END_SEQ.
934        // 3. Always check if the effective stream ends with END_SEQ.
935
936        if self.paste_buffer.len() < MAX_PASTE_LEN {
937            self.paste_buffer.push(byte);
938
939            // Check for end sequence in paste_buffer
940            if self.paste_buffer.ends_with(END_SEQ) {
941                self.in_paste = false;
942                // Remove the end sequence from content
943                let content_len = self.paste_buffer.len() - END_SEQ.len();
944                let content =
945                    String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
946                self.paste_buffer.clear();
947                return Some(Event::Paste(PasteEvent::bracketed(content)));
948            }
949        } else {
950            // Buffer is full. DoS protection active.
951            // We stop collecting content, but we MUST track the end sequence.
952            // Use self.buffer as a sliding window for the tail.
953
954            self.buffer.push(byte);
955            if self.buffer.len() > END_SEQ.len() {
956                self.buffer.remove(0);
957            }
958
959            // Check if we found the end sequence.
960            // The sequence might be split between paste_buffer and buffer.
961            // We only need to check the last 6 bytes.
962            // Since `buffer` contains the most recent bytes (up to 6), and `paste_buffer` is full...
963
964            // Construct a view of the last 6 bytes
965            let mut last_bytes = [0u8; 6];
966            let tail_len = self.buffer.len();
967            let paste_len = self.paste_buffer.len();
968
969            if tail_len + paste_len >= 6 {
970                // Fill from buffer (reverse order)
971                for i in 0..tail_len {
972                    last_bytes[6 - tail_len + i] = self.buffer[i];
973                }
974                // Fill remaining from paste_buffer
975                let remaining = 6 - tail_len;
976                if remaining > 0 {
977                    let start = paste_len - remaining;
978                    last_bytes[..remaining]
979                        .copy_from_slice(&self.paste_buffer[start..(remaining + start)]);
980                }
981
982                if last_bytes == END_SEQ {
983                    self.in_paste = false;
984
985                    // We found the end sequence.
986                    // The content is `paste_buffer` MINUS the part of END_SEQ that was in it.
987                    // `remaining` bytes of END_SEQ were in paste_buffer.
988
989                    let content_len = paste_len - remaining;
990                    let content =
991                        String::from_utf8_lossy(&self.paste_buffer[..content_len]).into_owned();
992
993                    self.paste_buffer.clear();
994                    self.buffer.clear();
995
996                    return Some(Event::Paste(PasteEvent::bracketed(content)));
997                }
998            }
999        }
1000
1001        None
1002    }
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007    use super::*;
1008
1009    #[test]
1010    fn csi_ignore_handles_final_bytes() {
1011        let mut parser = InputParser::new();
1012
1013        // Create a very long CSI sequence terminated by '@' (0x40)
1014        // 0x40 is a valid Final Byte (ECMA-48), but our parser currently only checks A-Za-z~
1015        let mut seq = vec![0x1B, b'['];
1016        seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100)); // Trigger CsiIgnore
1017        seq.push(b'@'); // Final byte
1018
1019        let events = parser.parse(&seq);
1020        assert_eq!(events.len(), 0);
1021
1022        // Feed 'a'. If '@' was correctly treated as final byte, 'a' should be parsed as 'a'.
1023        // If '@' was ignored (stayed in CsiIgnore), 'a' terminates the sequence and is swallowed.
1024        let events = parser.parse(b"a");
1025        assert_eq!(events.len(), 1, "Subsequent char 'a' was swallowed");
1026        assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1027    }
1028
1029    #[test]
1030    fn ascii_characters_parsed() {
1031        let mut parser = InputParser::new();
1032
1033        let events = parser.parse(b"abc");
1034        assert_eq!(events.len(), 3);
1035        assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('a')));
1036        assert!(matches!(events[1], Event::Key(k) if k.code == KeyCode::Char('b')));
1037        assert!(matches!(events[2], Event::Key(k) if k.code == KeyCode::Char('c')));
1038    }
1039
1040    #[test]
1041    fn control_characters() {
1042        let mut parser = InputParser::new();
1043
1044        // Ctrl+A
1045        let events = parser.parse(&[0x01]);
1046        assert_eq!(events.len(), 1);
1047        assert!(matches!(
1048            events[0],
1049            Event::Key(k) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::CTRL)
1050        ));
1051
1052        // Backspace
1053        let events = parser.parse(&[0x7F]);
1054        assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace));
1055    }
1056
1057    #[test]
1058    fn arrow_keys() {
1059        let mut parser = InputParser::new();
1060
1061        assert!(matches!(
1062            parser.parse(b"\x1b[A").first(),
1063            Some(Event::Key(k)) if k.code == KeyCode::Up
1064        ));
1065        assert!(matches!(
1066            parser.parse(b"\x1b[B").first(),
1067            Some(Event::Key(k)) if k.code == KeyCode::Down
1068        ));
1069        assert!(matches!(
1070            parser.parse(b"\x1b[C").first(),
1071            Some(Event::Key(k)) if k.code == KeyCode::Right
1072        ));
1073        assert!(matches!(
1074            parser.parse(b"\x1b[D").first(),
1075            Some(Event::Key(k)) if k.code == KeyCode::Left
1076        ));
1077    }
1078
1079    #[test]
1080    fn function_keys_ss3() {
1081        let mut parser = InputParser::new();
1082
1083        assert!(matches!(
1084            parser.parse(b"\x1bOP").first(),
1085            Some(Event::Key(k)) if k.code == KeyCode::F(1)
1086        ));
1087        assert!(matches!(
1088            parser.parse(b"\x1bOQ").first(),
1089            Some(Event::Key(k)) if k.code == KeyCode::F(2)
1090        ));
1091        assert!(matches!(
1092            parser.parse(b"\x1bOR").first(),
1093            Some(Event::Key(k)) if k.code == KeyCode::F(3)
1094        ));
1095        assert!(matches!(
1096            parser.parse(b"\x1bOS").first(),
1097            Some(Event::Key(k)) if k.code == KeyCode::F(4)
1098        ));
1099    }
1100
1101    #[test]
1102    fn function_keys_csi() {
1103        let mut parser = InputParser::new();
1104
1105        assert!(matches!(
1106            parser.parse(b"\x1b[15~").first(),
1107            Some(Event::Key(k)) if k.code == KeyCode::F(5)
1108        ));
1109        assert!(matches!(
1110            parser.parse(b"\x1b[17~").first(),
1111            Some(Event::Key(k)) if k.code == KeyCode::F(6)
1112        ));
1113    }
1114
1115    #[test]
1116    fn modifiers_in_csi() {
1117        let mut parser = InputParser::new();
1118
1119        // Shift+Up: CSI 1;2 A
1120        let events = parser.parse(b"\x1b[1;2A");
1121        assert!(matches!(
1122            events.first(),
1123            Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::SHIFT)
1124        ));
1125
1126        // Ctrl+Up: CSI 1;5 A
1127        let events = parser.parse(b"\x1b[1;5A");
1128        assert!(matches!(
1129            events.first(),
1130            Some(Event::Key(k)) if k.code == KeyCode::Up && k.modifiers.contains(Modifiers::CTRL)
1131        ));
1132    }
1133
1134    #[test]
1135    fn modifiers_in_csi_alt_ctrl() {
1136        let mut parser = InputParser::new();
1137
1138        // Alt+Ctrl+Up: CSI 1;7 A (1 + ALT(2) + CTRL(4) = 7)
1139        let events = parser.parse(b"\x1b[1;7A");
1140        assert!(matches!(
1141            events.first(),
1142            Some(Event::Key(k))
1143                if k.code == KeyCode::Up
1144                    && k.modifiers.contains(Modifiers::ALT)
1145                    && k.modifiers.contains(Modifiers::CTRL)
1146        ));
1147    }
1148
1149    #[test]
1150    fn kitty_keyboard_basic_char() {
1151        let mut parser = InputParser::new();
1152
1153        let events = parser.parse(b"\x1b[97u");
1154        assert!(matches!(
1155            events.first(),
1156            Some(Event::Key(k))
1157                if k.code == KeyCode::Char('a')
1158                    && k.modifiers == Modifiers::NONE
1159                    && k.kind == KeyEventKind::Press
1160        ));
1161    }
1162
1163    #[test]
1164    fn kitty_keyboard_with_modifiers_and_kind() {
1165        let mut parser = InputParser::new();
1166
1167        // Ctrl+repeat for 'a' (modifiers=5, event_type=2)
1168        let events = parser.parse(b"\x1b[97;5:2u");
1169        assert!(matches!(
1170            events.first(),
1171            Some(Event::Key(k))
1172                if k.code == KeyCode::Char('a')
1173                    && k.modifiers.contains(Modifiers::CTRL)
1174                    && k.kind == KeyEventKind::Repeat
1175        ));
1176    }
1177
1178    #[test]
1179    fn kitty_keyboard_function_key() {
1180        let mut parser = InputParser::new();
1181
1182        let events = parser.parse(b"\x1b[57364;1u");
1183        assert!(matches!(
1184            events.first(),
1185            Some(Event::Key(k)) if k.code == KeyCode::F(1)
1186        ));
1187    }
1188
1189    #[test]
1190    fn alt_key_escapes() {
1191        let mut parser = InputParser::new();
1192
1193        let events = parser.parse(b"\x1ba");
1194        assert!(matches!(
1195            events.first(),
1196            Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers.contains(Modifiers::ALT)
1197        ));
1198    }
1199
1200    #[test]
1201    fn alt_backspace() {
1202        let mut parser = InputParser::new();
1203
1204        let events = parser.parse(b"\x1b\x7f");
1205        assert!(matches!(
1206            events.first(),
1207            Some(Event::Key(k))
1208                if k.code == KeyCode::Backspace && k.modifiers.contains(Modifiers::ALT)
1209        ));
1210    }
1211
1212    #[test]
1213    fn escape_escape_resets_state() {
1214        let mut parser = InputParser::new();
1215
1216        let events = parser.parse(b"\x1b\x1b");
1217        assert!(matches!(
1218            events.first(),
1219            Some(Event::Key(k)) if k.code == KeyCode::Escape && k.modifiers.contains(Modifiers::ALT)
1220        ));
1221
1222        let events = parser.parse(b"a");
1223        assert!(matches!(
1224            events.first(),
1225            Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.modifiers == Modifiers::NONE
1226        ));
1227    }
1228
1229    #[test]
1230    fn focus_events() {
1231        let mut parser = InputParser::new();
1232
1233        assert!(matches!(
1234            parser.parse(b"\x1b[I").first(),
1235            Some(Event::Focus(true))
1236        ));
1237        assert!(matches!(
1238            parser.parse(b"\x1b[O").first(),
1239            Some(Event::Focus(false))
1240        ));
1241    }
1242
1243    #[test]
1244    fn bracketed_paste() {
1245        let mut parser = InputParser::new();
1246
1247        // Start paste mode, paste content, end paste mode
1248        let events = parser.parse(b"\x1b[200~hello world\x1b[201~");
1249        assert_eq!(events.len(), 1);
1250        assert!(matches!(
1251            &events[0],
1252            Event::Paste(p) if p.text == "hello world"
1253        ));
1254    }
1255
1256    #[test]
1257    fn mouse_sgr_protocol() {
1258        let mut parser = InputParser::new();
1259
1260        // Left click at (10, 20)
1261        let events = parser.parse(b"\x1b[<0;10;20M");
1262        assert!(matches!(
1263            events.first(),
1264            Some(Event::Mouse(m)) if m.x == 9 && m.y == 19 // 0-indexed
1265        ));
1266    }
1267
1268    #[test]
1269    fn mouse_sgr_modifiers() {
1270        let mut parser = InputParser::new();
1271
1272        // Shift+Alt+Ctrl + left button (0 + 4 + 8 + 16 = 28)
1273        let events = parser.parse(b"\x1b[<28;3;4M");
1274        assert!(matches!(
1275            events.first(),
1276            Some(Event::Mouse(m))
1277                if m.modifiers.contains(Modifiers::SHIFT)
1278                    && m.modifiers.contains(Modifiers::ALT)
1279                    && m.modifiers.contains(Modifiers::CTRL)
1280        ));
1281    }
1282
1283    #[test]
1284    fn mouse_sgr_scroll_up() {
1285        let mut parser = InputParser::new();
1286
1287        // Scroll up: button code 64
1288        let events = parser.parse(b"\x1b[<64;5;5M");
1289        assert!(matches!(
1290            events.first(),
1291            Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollUp)
1292        ));
1293    }
1294
1295    #[test]
1296    fn mouse_sgr_scroll_down() {
1297        let mut parser = InputParser::new();
1298
1299        // Scroll down: button code 65
1300        let events = parser.parse(b"\x1b[<65;5;5M");
1301        assert!(matches!(
1302            events.first(),
1303            Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollDown)
1304        ));
1305    }
1306
1307    #[test]
1308    fn mouse_sgr_scroll_left() {
1309        let mut parser = InputParser::new();
1310
1311        // Scroll left: button code 66
1312        let events = parser.parse(b"\x1b[<66;5;5M");
1313        assert!(matches!(
1314            events.first(),
1315            Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollLeft)
1316        ));
1317    }
1318
1319    #[test]
1320    fn mouse_sgr_scroll_right() {
1321        let mut parser = InputParser::new();
1322
1323        // Scroll right: button code 67
1324        let events = parser.parse(b"\x1b[<67;5;5M");
1325        assert!(matches!(
1326            events.first(),
1327            Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::ScrollRight)
1328        ));
1329    }
1330
1331    #[test]
1332    fn mouse_sgr_drag_left() {
1333        let mut parser = InputParser::new();
1334
1335        // Drag with left button: button code 32
1336        let events = parser.parse(b"\x1b[<32;10;20M");
1337        assert!(matches!(
1338            events.first(),
1339            Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Drag(MouseButton::Left))
1340        ));
1341    }
1342
1343    #[test]
1344    fn utf8_characters() {
1345        let mut parser = InputParser::new();
1346
1347        // é (U+00E9) = 0xC3 0xA9
1348        let events = parser.parse(&[0xC3, 0xA9]);
1349        assert!(matches!(
1350            events.first(),
1351            Some(Event::Key(k)) if k.code == KeyCode::Char('é')
1352        ));
1353    }
1354
1355    #[test]
1356    fn invalid_utf8_emits_replacement_then_reprocesses_byte() {
1357        let mut parser = InputParser::new();
1358
1359        // 0xE2 expects a 3-byte sequence, 0x28 is invalid continuation.
1360        let events = parser.parse(&[0xE2, 0x28]);
1361        assert_eq!(events.len(), 2);
1362        assert!(matches!(
1363            events[0],
1364            Event::Key(k) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)
1365        ));
1366        assert!(matches!(
1367            events[1],
1368            Event::Key(k) if k.code == KeyCode::Char('(')
1369        ));
1370    }
1371
1372    #[test]
1373    fn dos_protection_csi() {
1374        let mut parser = InputParser::new();
1375
1376        // Create a very long CSI sequence
1377        let mut seq = vec![0x1B, b'['];
1378        seq.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
1379        seq.push(b'A');
1380
1381        // DoS protection kicks in and switches to CsiIgnore
1382        // Excess bytes should be ignored, NOT leaked as characters
1383        let events = parser.parse(&seq);
1384        assert_eq!(
1385            events.len(),
1386            0,
1387            "Oversized CSI sequence should produce no events"
1388        );
1389
1390        // The key invariant: parser should be back in ground state and functional
1391        // Verify by parsing a normal sequence after the attack
1392        let events = parser.parse(b"\x1b[A");
1393        assert!(matches!(
1394            events.first(),
1395            Some(Event::Key(k)) if k.code == KeyCode::Up
1396        ));
1397    }
1398
1399    #[test]
1400    fn incomplete_csi_sequence_emits_no_event() {
1401        let mut parser = InputParser::new();
1402        let events = parser.parse(b"\x1b[");
1403        assert!(events.is_empty());
1404    }
1405
1406    #[test]
1407    fn dos_protection_paste() {
1408        let mut parser = InputParser::new();
1409
1410        // Start paste mode
1411        parser.parse(b"\x1b[200~");
1412
1413        // Paste content up to the limit
1414        let content = vec![b'x'; MAX_PASTE_LEN - 100]; // Leave room for end sequence
1415        parser.parse(&content);
1416
1417        // End paste mode
1418        let events = parser.parse(b"\x1b[201~");
1419
1420        // Should have collected content up to limit
1421        assert!(matches!(
1422            events.first(),
1423            Some(Event::Paste(p)) if p.text.len() <= MAX_PASTE_LEN
1424        ));
1425    }
1426
1427    #[test]
1428    fn dos_protection_paste_overflow_terminator() {
1429        let mut parser = InputParser::new();
1430
1431        // Start paste mode
1432        parser.parse(b"\x1b[200~");
1433
1434        // Overflow the buffer by pushing more than MAX_PASTE_LEN bytes.
1435        // DoS protection stops collecting content once buffer is full,
1436        // but continues tracking the end sequence to properly exit paste mode.
1437        let overflow = 100;
1438        let content = vec![b'a'; MAX_PASTE_LEN + overflow];
1439        parser.parse(&content);
1440
1441        // Send terminator - parser MUST detect it and exit paste mode.
1442        // Even though the buffer overflowed, the terminator detection still works.
1443        let events = parser.parse(b"\x1b[201~");
1444
1445        assert_eq!(events.len(), 1, "Should emit paste event");
1446        match &events[0] {
1447            Event::Paste(p) => {
1448                // Content is capped at MAX_PASTE_LEN due to DoS protection.
1449                // Overflow bytes are discarded but terminator is still detected.
1450                assert_eq!(
1451                    p.text.len(),
1452                    MAX_PASTE_LEN,
1453                    "Paste should be capped at MAX_PASTE_LEN bytes"
1454                );
1455                // The content should be all 'a' since we filled with 'a'
1456                assert!(p.text.chars().all(|c| c == 'a'));
1457            }
1458            _ => unreachable!("Expected Paste event"),
1459        }
1460
1461        // Verify we are back in ground state by parsing a key
1462        let events = parser.parse(b"b");
1463        assert_eq!(events.len(), 1);
1464        assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('b')));
1465    }
1466
1467    #[test]
1468    fn no_panic_on_invalid_input() {
1469        let mut parser = InputParser::new();
1470
1471        // Random bytes that might trip up the parser
1472        let garbage = [0xFF, 0xFE, 0x00, 0x1B, 0x1B, 0x1B, b'[', 0xFF, b']', 0x00];
1473
1474        // Should not panic
1475        let _ = parser.parse(&garbage);
1476    }
1477
1478    #[test]
1479    fn dos_protection_paste_boundary() {
1480        let mut parser = InputParser::new();
1481        // Start paste mode
1482        parser.parse(b"\x1b[200~");
1483
1484        // Fill buffer exactly to limit
1485        let content = vec![b'x'; MAX_PASTE_LEN];
1486        parser.parse(&content);
1487
1488        // Send end sequence
1489        // Current bug: This will be dropped because buffer is full, trapping parser
1490        let events = parser.parse(b"\x1b[201~");
1491
1492        assert!(
1493            !events.is_empty(),
1494            "Parser trapped in paste mode after hitting limit"
1495        );
1496        assert!(matches!(events[0], Event::Paste(_)));
1497    }
1498
1499    // ── Navigation keys via CSI ~ sequences ──────────────────────────
1500
1501    #[test]
1502    fn csi_tilde_home() {
1503        let mut parser = InputParser::new();
1504        let events = parser.parse(b"\x1b[1~");
1505        assert!(matches!(
1506            events.first(),
1507            Some(Event::Key(k)) if k.code == KeyCode::Home
1508        ));
1509    }
1510
1511    #[test]
1512    fn csi_tilde_insert() {
1513        let mut parser = InputParser::new();
1514        let events = parser.parse(b"\x1b[2~");
1515        assert!(matches!(
1516            events.first(),
1517            Some(Event::Key(k)) if k.code == KeyCode::Insert
1518        ));
1519    }
1520
1521    #[test]
1522    fn csi_tilde_delete() {
1523        let mut parser = InputParser::new();
1524        let events = parser.parse(b"\x1b[3~");
1525        assert!(matches!(
1526            events.first(),
1527            Some(Event::Key(k)) if k.code == KeyCode::Delete
1528        ));
1529    }
1530
1531    #[test]
1532    fn csi_tilde_end() {
1533        let mut parser = InputParser::new();
1534        let events = parser.parse(b"\x1b[4~");
1535        assert!(matches!(
1536            events.first(),
1537            Some(Event::Key(k)) if k.code == KeyCode::End
1538        ));
1539    }
1540
1541    #[test]
1542    fn csi_tilde_page_up() {
1543        let mut parser = InputParser::new();
1544        let events = parser.parse(b"\x1b[5~");
1545        assert!(matches!(
1546            events.first(),
1547            Some(Event::Key(k)) if k.code == KeyCode::PageUp
1548        ));
1549    }
1550
1551    #[test]
1552    fn csi_tilde_page_down() {
1553        let mut parser = InputParser::new();
1554        let events = parser.parse(b"\x1b[6~");
1555        assert!(matches!(
1556            events.first(),
1557            Some(Event::Key(k)) if k.code == KeyCode::PageDown
1558        ));
1559    }
1560
1561    // ── Navigation keys via CSI H/F (xterm-style) ───────────────────
1562
1563    #[test]
1564    fn csi_home_and_end() {
1565        let mut parser = InputParser::new();
1566        assert!(matches!(
1567            parser.parse(b"\x1b[H").first(),
1568            Some(Event::Key(k)) if k.code == KeyCode::Home
1569        ));
1570        assert!(matches!(
1571            parser.parse(b"\x1b[F").first(),
1572            Some(Event::Key(k)) if k.code == KeyCode::End
1573        ));
1574    }
1575
1576    // ── SS3 Home/End ─────────────────────────────────────────────────
1577
1578    #[test]
1579    fn ss3_home_and_end() {
1580        let mut parser = InputParser::new();
1581        assert!(matches!(
1582            parser.parse(b"\x1bOH").first(),
1583            Some(Event::Key(k)) if k.code == KeyCode::Home
1584        ));
1585        assert!(matches!(
1586            parser.parse(b"\x1bOF").first(),
1587            Some(Event::Key(k)) if k.code == KeyCode::End
1588        ));
1589    }
1590
1591    // ── BackTab (Shift+Tab via CSI Z) ────────────────────────────────
1592
1593    #[test]
1594    fn backtab_csi_z() {
1595        let mut parser = InputParser::new();
1596        let events = parser.parse(b"\x1b[Z");
1597        assert!(matches!(
1598            events.first(),
1599            Some(Event::Key(k)) if k.code == KeyCode::BackTab
1600        ));
1601    }
1602
1603    // ── F7-F12 keys via CSI tilde ────────────────────────────────────
1604
1605    #[test]
1606    fn function_keys_f7_to_f12() {
1607        let mut parser = InputParser::new();
1608        assert!(matches!(
1609            parser.parse(b"\x1b[18~").first(),
1610            Some(Event::Key(k)) if k.code == KeyCode::F(7)
1611        ));
1612        assert!(matches!(
1613            parser.parse(b"\x1b[19~").first(),
1614            Some(Event::Key(k)) if k.code == KeyCode::F(8)
1615        ));
1616        assert!(matches!(
1617            parser.parse(b"\x1b[20~").first(),
1618            Some(Event::Key(k)) if k.code == KeyCode::F(9)
1619        ));
1620        assert!(matches!(
1621            parser.parse(b"\x1b[21~").first(),
1622            Some(Event::Key(k)) if k.code == KeyCode::F(10)
1623        ));
1624        assert!(matches!(
1625            parser.parse(b"\x1b[23~").first(),
1626            Some(Event::Key(k)) if k.code == KeyCode::F(11)
1627        ));
1628        assert!(matches!(
1629            parser.parse(b"\x1b[24~").first(),
1630            Some(Event::Key(k)) if k.code == KeyCode::F(12)
1631        ));
1632    }
1633
1634    // ── Modifier combinations on navigation keys ─────────────────────
1635
1636    #[test]
1637    fn ctrl_home_and_alt_end() {
1638        let mut parser = InputParser::new();
1639
1640        // Ctrl+Home: CSI 1;5 H
1641        let events = parser.parse(b"\x1b[1;5H");
1642        assert!(matches!(
1643            events.first(),
1644            Some(Event::Key(k)) if k.code == KeyCode::Home && k.modifiers.contains(Modifiers::CTRL)
1645        ));
1646
1647        // Alt+End: CSI 1;3 F
1648        let events = parser.parse(b"\x1b[1;3F");
1649        assert!(matches!(
1650            events.first(),
1651            Some(Event::Key(k)) if k.code == KeyCode::End && k.modifiers.contains(Modifiers::ALT)
1652        ));
1653    }
1654
1655    #[test]
1656    fn shift_ctrl_arrow() {
1657        let mut parser = InputParser::new();
1658
1659        // Shift+Ctrl+Right: CSI 1;6 C (modifier value 6 = 1 + Shift|Ctrl = 1 + 5)
1660        let events = parser.parse(b"\x1b[1;6C");
1661        assert!(matches!(
1662            events.first(),
1663            Some(Event::Key(k)) if k.code == KeyCode::Right
1664                && k.modifiers.contains(Modifiers::SHIFT)
1665                && k.modifiers.contains(Modifiers::CTRL)
1666        ));
1667    }
1668
1669    #[test]
1670    fn modifiers_on_tilde_keys() {
1671        let mut parser = InputParser::new();
1672
1673        // Ctrl+Delete: CSI 3;5 ~
1674        let events = parser.parse(b"\x1b[3;5~");
1675        assert!(matches!(
1676            events.first(),
1677            Some(Event::Key(k)) if k.code == KeyCode::Delete && k.modifiers.contains(Modifiers::CTRL)
1678        ));
1679
1680        // Shift+PageUp: CSI 5;2 ~
1681        let events = parser.parse(b"\x1b[5;2~");
1682        assert!(matches!(
1683            events.first(),
1684            Some(Event::Key(k)) if k.code == KeyCode::PageUp && k.modifiers.contains(Modifiers::SHIFT)
1685        ));
1686    }
1687
1688    // ── Mouse right/middle click and release ─────────────────────────
1689
1690    #[test]
1691    fn mouse_sgr_right_click() {
1692        let mut parser = InputParser::new();
1693        // Right click: button code 2
1694        let events = parser.parse(b"\x1b[<2;15;10M");
1695        assert!(matches!(
1696            events.first(),
1697            Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Right))
1698                && m.x == 14 && m.y == 9
1699        ));
1700    }
1701
1702    #[test]
1703    fn mouse_sgr_middle_click() {
1704        let mut parser = InputParser::new();
1705        // Middle click: button code 1
1706        let events = parser.parse(b"\x1b[<1;5;5M");
1707        assert!(matches!(
1708            events.first(),
1709            Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Middle))
1710        ));
1711    }
1712
1713    #[test]
1714    fn mouse_sgr_button_release() {
1715        let mut parser = InputParser::new();
1716        // Left button release: final byte 'm'
1717        let events = parser.parse(b"\x1b[<0;10;20m");
1718        assert!(matches!(
1719            events.first(),
1720            Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Up(MouseButton::Left))
1721        ));
1722    }
1723
1724    #[test]
1725    fn mouse_sgr_moved() {
1726        let mut parser = InputParser::new();
1727        // Mouse move (no button): button code 35 (32 | 3, bit 5 set + bits 0-1 = 3)
1728        let events = parser.parse(b"\x1b[<35;10;20M");
1729        assert!(matches!(
1730            events.first(),
1731            Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Moved)
1732        ));
1733    }
1734
1735    #[test]
1736    fn mouse_sgr_with_modifiers() {
1737        let mut parser = InputParser::new();
1738        // Shift+Left click: button_code bit 2 set (shift) = 4
1739        let events = parser.parse(b"\x1b[<4;5;5M");
1740        assert!(matches!(
1741            events.first(),
1742            Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1743                && m.modifiers.contains(Modifiers::SHIFT)
1744        ));
1745
1746        // Ctrl+Left click: button_code bit 4 set (ctrl) = 16
1747        let events = parser.parse(b"\x1b[<16;5;5M");
1748        assert!(matches!(
1749            events.first(),
1750            Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1751                && m.modifiers.contains(Modifiers::CTRL)
1752        ));
1753
1754        // Alt+Left click: button_code bit 3 set (alt) = 8
1755        let events = parser.parse(b"\x1b[<8;5;5M");
1756        assert!(matches!(
1757            events.first(),
1758            Some(Event::Mouse(m)) if matches!(m.kind, MouseEventKind::Down(MouseButton::Left))
1759                && m.modifiers.contains(Modifiers::ALT)
1760        ));
1761    }
1762
1763    // ── Kitty keyboard release events and special keys ───────────────
1764
1765    #[test]
1766    fn kitty_keyboard_release_event() {
1767        let mut parser = InputParser::new();
1768        // Release event: kind=3
1769        let events = parser.parse(b"\x1b[97;1:3u");
1770        assert!(matches!(
1771            events.first(),
1772            Some(Event::Key(k)) if k.code == KeyCode::Char('a') && k.kind == KeyEventKind::Release
1773        ));
1774
1775        // Ctrl+release for 'A' (modifiers=5, event_type=3)
1776        let events = parser.parse(b"\x1b[65;5:3u");
1777        assert!(matches!(
1778            events.first(),
1779            Some(Event::Key(k))
1780                if k.code == KeyCode::Char('A')
1781                    && k.modifiers.contains(Modifiers::CTRL)
1782                    && k.kind == KeyEventKind::Release
1783        ));
1784    }
1785
1786    #[test]
1787    fn kitty_keyboard_special_keys() {
1788        let mut parser = InputParser::new();
1789
1790        // Escape: 57344
1791        assert!(matches!(
1792            parser.parse(b"\x1b[57344u").first(),
1793            Some(Event::Key(k)) if k.code == KeyCode::Escape
1794        ));
1795
1796        // Enter: 57345
1797        assert!(matches!(
1798            parser.parse(b"\x1b[57345u").first(),
1799            Some(Event::Key(k)) if k.code == KeyCode::Enter
1800        ));
1801
1802        // Tab: 57346
1803        assert!(matches!(
1804            parser.parse(b"\x1b[57346u").first(),
1805            Some(Event::Key(k)) if k.code == KeyCode::Tab
1806        ));
1807
1808        // Backspace: 57347
1809        assert!(matches!(
1810            parser.parse(b"\x1b[57347u").first(),
1811            Some(Event::Key(k)) if k.code == KeyCode::Backspace
1812        ));
1813
1814        // Insert: 57348
1815        assert!(matches!(
1816            parser.parse(b"\x1b[57348u").first(),
1817            Some(Event::Key(k)) if k.code == KeyCode::Insert
1818        ));
1819
1820        // Delete: 57349
1821        assert!(matches!(
1822            parser.parse(b"\x1b[57349u").first(),
1823            Some(Event::Key(k)) if k.code == KeyCode::Delete
1824        ));
1825    }
1826
1827    #[test]
1828    fn kitty_keyboard_navigation_keys() {
1829        let mut parser = InputParser::new();
1830
1831        // Left: 57350
1832        assert!(matches!(
1833            parser.parse(b"\x1b[57350u").first(),
1834            Some(Event::Key(k)) if k.code == KeyCode::Left
1835        ));
1836        // Right: 57351
1837        assert!(matches!(
1838            parser.parse(b"\x1b[57351u").first(),
1839            Some(Event::Key(k)) if k.code == KeyCode::Right
1840        ));
1841        // Up: 57352
1842        assert!(matches!(
1843            parser.parse(b"\x1b[57352u").first(),
1844            Some(Event::Key(k)) if k.code == KeyCode::Up
1845        ));
1846        // Down: 57353
1847        assert!(matches!(
1848            parser.parse(b"\x1b[57353u").first(),
1849            Some(Event::Key(k)) if k.code == KeyCode::Down
1850        ));
1851        // PageUp: 57354
1852        assert!(matches!(
1853            parser.parse(b"\x1b[57354u").first(),
1854            Some(Event::Key(k)) if k.code == KeyCode::PageUp
1855        ));
1856        // PageDown: 57355
1857        assert!(matches!(
1858            parser.parse(b"\x1b[57355u").first(),
1859            Some(Event::Key(k)) if k.code == KeyCode::PageDown
1860        ));
1861        // Home: 57356
1862        assert!(matches!(
1863            parser.parse(b"\x1b[57356u").first(),
1864            Some(Event::Key(k)) if k.code == KeyCode::Home
1865        ));
1866        // End: 57357
1867        assert!(matches!(
1868            parser.parse(b"\x1b[57357u").first(),
1869            Some(Event::Key(k)) if k.code == KeyCode::End
1870        ));
1871    }
1872
1873    #[test]
1874    fn kitty_keyboard_f_keys() {
1875        let mut parser = InputParser::new();
1876        // F1: 57364
1877        assert!(matches!(
1878            parser.parse(b"\x1b[57364u").first(),
1879            Some(Event::Key(k)) if k.code == KeyCode::F(1)
1880        ));
1881        // F12: 57375
1882        assert!(matches!(
1883            parser.parse(b"\x1b[57375u").first(),
1884            Some(Event::Key(k)) if k.code == KeyCode::F(12)
1885        ));
1886        // F24: 57387
1887        assert!(matches!(
1888            parser.parse(b"\x1b[57387u").first(),
1889            Some(Event::Key(k)) if k.code == KeyCode::F(24)
1890        ));
1891    }
1892
1893    #[test]
1894    fn kitty_keyboard_ascii_as_standard() {
1895        let mut parser = InputParser::new();
1896        // Tab (9), Enter (13), Escape (27), Backspace (127)
1897        assert!(matches!(
1898            parser.parse(b"\x1b[9u").first(),
1899            Some(Event::Key(k)) if k.code == KeyCode::Tab
1900        ));
1901        assert!(matches!(
1902            parser.parse(b"\x1b[13u").first(),
1903            Some(Event::Key(k)) if k.code == KeyCode::Enter
1904        ));
1905        assert!(matches!(
1906            parser.parse(b"\x1b[27u").first(),
1907            Some(Event::Key(k)) if k.code == KeyCode::Escape
1908        ));
1909        assert!(matches!(
1910            parser.parse(b"\x1b[127u").first(),
1911            Some(Event::Key(k)) if k.code == KeyCode::Backspace
1912        ));
1913        // Backspace alternate: 8
1914        assert!(matches!(
1915            parser.parse(b"\x1b[8u").first(),
1916            Some(Event::Key(k)) if k.code == KeyCode::Backspace
1917        ));
1918    }
1919
1920    // ── OSC 52 clipboard ─────────────────────────────────────────────
1921
1922    #[test]
1923    fn osc52_clipboard_bel_terminated() {
1924        let mut parser = InputParser::new();
1925        // OSC 52;c;<base64 "hello"> BEL
1926        // "hello" in base64 is "aGVsbG8="
1927        let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x07");
1928        assert!(matches!(
1929            events.first(),
1930            Some(Event::Clipboard(c)) if c.content == "hello" && c.source == ClipboardSource::Osc52
1931        ));
1932    }
1933
1934    #[test]
1935    fn osc52_clipboard_st_terminated() {
1936        let mut parser = InputParser::new();
1937        // OSC 52;c;<base64 "hello"> ESC \
1938        let events = parser.parse(b"\x1b]52;c;aGVsbG8=\x1b\\");
1939        assert!(matches!(
1940            events.first(),
1941            Some(Event::Clipboard(c)) if c.content == "hello"
1942        ));
1943    }
1944
1945    #[test]
1946    fn osc52_clipboard_primary_selection() {
1947        let mut parser = InputParser::new();
1948        // Primary selection: p instead of c
1949        // "abc" in base64 is "YWJj"
1950        let events = parser.parse(b"\x1b]52;p;YWJj\x07");
1951        assert!(matches!(
1952            events.first(),
1953            Some(Event::Clipboard(c)) if c.content == "abc"
1954        ));
1955    }
1956
1957    // ── Control keys ─────────────────────────────────────────────────
1958
1959    #[test]
1960    fn ctrl_space_is_null() {
1961        let mut parser = InputParser::new();
1962        let events = parser.parse(&[0x00]);
1963        assert!(matches!(
1964            events.first(),
1965            Some(Event::Key(k)) if k.code == KeyCode::Null
1966        ));
1967    }
1968
1969    #[test]
1970    fn all_ctrl_letter_keys() {
1971        let mut parser = InputParser::new();
1972        // Ctrl+A (0x01) through Ctrl+Z (0x1A), skipping Backspace (0x08), Tab (0x09), and Enter (0x0D)
1973        for byte in 0x01..=0x1Au8 {
1974            let events = parser.parse(&[byte]);
1975            assert_eq!(
1976                events.len(),
1977                1,
1978                "Ctrl+{} should produce one event",
1979                (byte + b'a' - 1) as char
1980            );
1981            match byte {
1982                0x08 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Backspace)),
1983                0x09 => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Tab)),
1984                0x0D => assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Enter)),
1985                _ => {
1986                    let expected_char = (byte + b'a' - 1) as char;
1987                    match &events[0] {
1988                        Event::Key(k) => {
1989                            assert_eq!(
1990                                k.code,
1991                                KeyCode::Char(expected_char),
1992                                "Byte 0x{byte:02X} should produce Ctrl+{expected_char}"
1993                            );
1994                            assert!(
1995                                k.modifiers.contains(Modifiers::CTRL),
1996                                "Byte 0x{byte:02X} should have Ctrl modifier"
1997                            );
1998                        }
1999                        other => {
2000                            panic!("Byte 0x{byte:02X}: expected Key event, got {other:?}");
2001                        }
2002                    }
2003                }
2004            }
2005        }
2006    }
2007
2008    // ── UTF-8 multi-byte: 3-byte and 4-byte ─────────────────────────
2009
2010    #[test]
2011    fn utf8_3byte_cjk() {
2012        let mut parser = InputParser::new();
2013        // 中 (U+4E2D) = 0xE4 0xB8 0xAD
2014        let events = parser.parse(&[0xE4, 0xB8, 0xAD]);
2015        assert!(matches!(
2016            events.first(),
2017            Some(Event::Key(k)) if k.code == KeyCode::Char('中')
2018        ));
2019    }
2020
2021    #[test]
2022    fn utf8_4byte_emoji() {
2023        let mut parser = InputParser::new();
2024        // 🦀 (U+1F980) = 0xF0 0x9F 0xA6 0x80
2025        let events = parser.parse(&[0xF0, 0x9F, 0xA6, 0x80]);
2026        assert!(matches!(
2027            events.first(),
2028            Some(Event::Key(k)) if k.code == KeyCode::Char('🦀')
2029        ));
2030    }
2031
2032    // ── Empty input ──────────────────────────────────────────────────
2033
2034    #[test]
2035    fn empty_input_returns_no_events() {
2036        let mut parser = InputParser::new();
2037        let events = parser.parse(b"");
2038        assert!(events.is_empty());
2039    }
2040
2041    // ── Unknown CSI tilde values ─────────────────────────────────────
2042
2043    #[test]
2044    fn unknown_csi_tilde_ignored() {
2045        let mut parser = InputParser::new();
2046        // Code 99 is not a known tilde key
2047        let events = parser.parse(b"\x1b[99~");
2048        assert!(events.is_empty());
2049
2050        // Parser should still work
2051        let events = parser.parse(b"a");
2052        assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('a')));
2053    }
2054
2055    // ── Alt+various characters ───────────────────────────────────────
2056
2057    #[test]
2058    fn alt_special_chars() {
2059        let mut parser = InputParser::new();
2060
2061        // Alt+space
2062        let events = parser.parse(b"\x1b ");
2063        assert!(matches!(
2064            events.first(),
2065            Some(Event::Key(k)) if k.code == KeyCode::Char(' ') && k.modifiers.contains(Modifiers::ALT)
2066        ));
2067
2068        // Alt+digit
2069        let events = parser.parse(b"\x1b5");
2070        assert!(matches!(
2071            events.first(),
2072            Some(Event::Key(k)) if k.code == KeyCode::Char('5') && k.modifiers.contains(Modifiers::ALT)
2073        ));
2074
2075        // Alt+bracket
2076        let events = parser.parse(b"\x1b}");
2077        assert!(matches!(
2078            events.first(),
2079            Some(Event::Key(k)) if k.code == KeyCode::Char('}') && k.modifiers.contains(Modifiers::ALT)
2080        ));
2081    }
2082
2083    // ── SS3 arrow keys ───────────────────────────────────────────────
2084
2085    #[test]
2086    fn ss3_arrow_keys() {
2087        let mut parser = InputParser::new();
2088        assert!(matches!(
2089            parser.parse(b"\x1bOA").first(),
2090            Some(Event::Key(k)) if k.code == KeyCode::Up
2091        ));
2092        assert!(matches!(
2093            parser.parse(b"\x1bOB").first(),
2094            Some(Event::Key(k)) if k.code == KeyCode::Down
2095        ));
2096        assert!(matches!(
2097            parser.parse(b"\x1bOC").first(),
2098            Some(Event::Key(k)) if k.code == KeyCode::Right
2099        ));
2100        assert!(matches!(
2101            parser.parse(b"\x1bOD").first(),
2102            Some(Event::Key(k)) if k.code == KeyCode::Left
2103        ));
2104    }
2105
2106    // ── Xterm modifier encoding ──────────────────────────────────────
2107
2108    #[test]
2109    fn xterm_modifier_encoding() {
2110        // Verify modifiers_from_xterm decoding (value = 1 + modifier_bits)
2111        assert_eq!(InputParser::modifiers_from_xterm(1), Modifiers::NONE);
2112        assert_eq!(InputParser::modifiers_from_xterm(2), Modifiers::SHIFT);
2113        assert_eq!(InputParser::modifiers_from_xterm(3), Modifiers::ALT);
2114        assert_eq!(
2115            InputParser::modifiers_from_xterm(4),
2116            Modifiers::SHIFT | Modifiers::ALT
2117        );
2118        assert_eq!(InputParser::modifiers_from_xterm(5), Modifiers::CTRL);
2119        assert_eq!(
2120            InputParser::modifiers_from_xterm(6),
2121            Modifiers::SHIFT | Modifiers::CTRL
2122        );
2123        assert_eq!(InputParser::modifiers_from_xterm(9), Modifiers::SUPER);
2124    }
2125
2126    // ── SS3 interrupted by ESC ───────────────────────────────────────
2127
2128    #[test]
2129    fn ss3_interrupted_by_esc() {
2130        let mut parser = InputParser::new();
2131        // ESC O ESC should restart into Escape state
2132        let events = parser.parse(b"\x1bO\x1b[A");
2133        // Should get Up arrow from the new ESC [ A sequence
2134        assert!(matches!(
2135            events.first(),
2136            Some(Event::Key(k)) if k.code == KeyCode::Up
2137        ));
2138    }
2139
2140    // ── Kitty keyboard: unhandled keycodes ───────────────────────────
2141
2142    #[test]
2143    fn kitty_keyboard_reserved_keycode_ignored() {
2144        let mut parser = InputParser::new();
2145        // Reserved range 57358..=57363 returns None
2146        let events = parser.parse(b"\x1b[57360u");
2147        assert!(events.is_empty());
2148
2149        // Parser still works
2150        let events = parser.parse(b"x");
2151        assert!(matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char('x')));
2152    }
2153    #[test]
2154    fn utf8_invalid_sequence_emits_replacement() {
2155        let mut parser = InputParser::new();
2156
2157        // 0xE0 is a start of 3-byte sequence.
2158        // 0x41 ('A') is not a valid continuation byte.
2159        // Should emit Replacement Character then 'A'.
2160        let events = parser.parse(&[0xE0, 0x41]);
2161        assert_eq!(events.len(), 2);
2162
2163        match &events[0] {
2164            Event::Key(k) => assert_eq!(k.code, KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2165            _ => panic!("Expected replacement character"),
2166        }
2167
2168        match &events[1] {
2169            Event::Key(k) => assert_eq!(k.code, KeyCode::Char('A')),
2170            _ => panic!("Expected character 'A'"),
2171        }
2172    }
2173
2174    #[test]
2175    fn utf8_invalid_lead_emits_replacement() {
2176        let mut parser = InputParser::new();
2177
2178        // 0xC0 is an invalid UTF-8 lead byte (overlong sequence).
2179        let events = parser.parse(&[0xC0, b'a']);
2180        assert!(
2181            matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2182            "Expected replacement for invalid lead"
2183        );
2184        assert!(
2185            events
2186                .iter()
2187                .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('a'))),
2188            "Expected subsequent ASCII to be preserved"
2189        );
2190
2191        // 0xF5 is an out-of-range UTF-8 lead byte.
2192        let events = parser.parse(&[0xF5, b'b']);
2193        assert!(
2194            matches!(events.first(), Some(Event::Key(k)) if k.code == KeyCode::Char(std::char::REPLACEMENT_CHARACTER)),
2195            "Expected replacement for out-of-range lead"
2196        );
2197        assert!(
2198            events
2199                .iter()
2200                .any(|e| matches!(e, Event::Key(k) if k.code == KeyCode::Char('b'))),
2201            "Expected subsequent ASCII to be preserved"
2202        );
2203    }
2204}
2205
2206#[cfg(test)]
2207mod proptest_fuzz {
2208    use super::*;
2209    use proptest::prelude::*;
2210
2211    // ── Strategy helpers ────────────────────────────────────────────────
2212    // Avoid turbofish inside proptest! macro (Rust 2024 edition compat).
2213
2214    fn arb_byte() -> impl Strategy<Value = u8> {
2215        any::<u8>()
2216    }
2217
2218    fn arb_byte_vec(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
2219        prop::collection::vec(arb_byte(), 0..=max_len)
2220    }
2221
2222    /// Generate a well-formed CSI sequence: ESC [ <params> <final byte>.
2223    fn csi_sequence() -> impl Strategy<Value = Vec<u8>> {
2224        let params = prop::collection::vec(0x30u8..=0x3F, 0..=20);
2225        let final_byte = 0x40u8..=0x7E;
2226        (params, final_byte).prop_map(|(p, f)| {
2227            let mut buf = vec![0x1B, b'['];
2228            buf.extend_from_slice(&p);
2229            buf.push(f);
2230            buf
2231        })
2232    }
2233
2234    /// Generate an OSC sequence: ESC ] <content> ST.
2235    fn osc_sequence() -> impl Strategy<Value = Vec<u8>> {
2236        let content = prop::collection::vec(0x20u8..=0x7E, 0..=64);
2237        let terminator = prop_oneof![
2238            Just(vec![0x1B, b'\\']), // ESC backslash
2239            Just(vec![0x07]),        // BEL
2240        ];
2241        (content, terminator).prop_map(|(c, t)| {
2242            let mut buf = vec![0x1B, b']'];
2243            buf.extend_from_slice(&c);
2244            buf.extend_from_slice(&t);
2245            buf
2246        })
2247    }
2248
2249    /// Generate an SS3 sequence: ESC O <final byte>.
2250    fn ss3_sequence() -> impl Strategy<Value = Vec<u8>> {
2251        (0x40u8..=0x7E).prop_map(|f| vec![0x1B, b'O', f])
2252    }
2253
2254    /// Generate a bracketed paste: ESC[200~ <content> ESC[201~.
2255    fn paste_sequence() -> impl Strategy<Value = Vec<u8>> {
2256        prop::collection::vec(0x20u8..=0x7E, 0..=128).prop_map(|content| {
2257            let mut buf = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
2258            buf.extend_from_slice(&content);
2259            buf.extend_from_slice(b"\x1b[201~");
2260            buf
2261        })
2262    }
2263
2264    /// Generate structured adversarial input: mix of valid sequences and random bytes.
2265    fn mixed_adversarial() -> impl Strategy<Value = Vec<u8>> {
2266        let fragment = prop_oneof![
2267            csi_sequence(),
2268            osc_sequence(),
2269            ss3_sequence(),
2270            paste_sequence(),
2271            arb_byte_vec(16),                            // random bytes
2272            Just(vec![0x1B]),                            // bare ESC
2273            Just(vec![0x1B, b'[']),                      // unterminated CSI
2274            Just(vec![0x1B, b']']),                      // unterminated OSC
2275            prop::collection::vec(0x80u8..=0xFF, 1..=4), // high bytes
2276        ];
2277        prop::collection::vec(fragment, 1..=8)
2278            .prop_map(|frags| frags.into_iter().flatten().collect())
2279    }
2280
2281    // ── Property tests ─────────────────────────────────────────────────
2282
2283    proptest! {
2284        /// Random bytes must never panic.
2285        #[test]
2286        fn random_bytes_never_panic(input in arb_byte_vec(512)) {
2287            let mut parser = InputParser::new();
2288            let _ = parser.parse(&input);
2289        }
2290
2291        /// After parsing any input, the parser must be reusable for normal keys.
2292        #[test]
2293        fn parser_recovers_after_garbage(input in arb_byte_vec(256)) {
2294            let mut parser = InputParser::new();
2295            let _ = parser.parse(&input);
2296
2297            // Feed a clean known sequence (letter 'z') after the garbage.
2298            let events = parser.parse(b"z");
2299            // Parser must not panic. We can't assert exact events because
2300            // the parser may still be mid-sequence, but it must not panic.
2301            let _ = events;
2302        }
2303
2304        /// Structured mixed input (valid sequences + garbage) must never panic.
2305        #[test]
2306        fn mixed_sequences_never_panic(input in mixed_adversarial()) {
2307            let mut parser = InputParser::new();
2308            let _ = parser.parse(&input);
2309        }
2310
2311        /// All generated events must be valid (non-panicking Debug).
2312        #[test]
2313        fn events_are_well_formed(input in arb_byte_vec(256)) {
2314            let mut parser = InputParser::new();
2315            let events = parser.parse(&input);
2316            for event in &events {
2317                // Exercise Debug impl — catches inconsistent internal state.
2318                let _ = format!("{event:?}");
2319            }
2320        }
2321
2322        /// CSI sequences never produce more events than bytes fed.
2323        #[test]
2324        fn csi_event_count_bounded(seq in csi_sequence()) {
2325            let mut parser = InputParser::new();
2326            let events = parser.parse(&seq);
2327            prop_assert!(events.len() <= seq.len(),
2328                "Got {} events from {} bytes", events.len(), seq.len());
2329        }
2330
2331        /// OSC sequences never produce more events than bytes fed.
2332        #[test]
2333        fn osc_event_count_bounded(seq in osc_sequence()) {
2334            let mut parser = InputParser::new();
2335            let events = parser.parse(&seq);
2336            prop_assert!(events.len() <= seq.len(),
2337                "Got {} events from {} bytes", events.len(), seq.len());
2338        }
2339
2340        /// Paste content is always bounded by MAX_PASTE_LEN.
2341        #[test]
2342        fn paste_content_bounded(content in prop::collection::vec(arb_byte(), 0..=2048)) {
2343            let mut parser = InputParser::new();
2344            let mut input = vec![0x1B, b'[', b'2', b'0', b'0', b'~'];
2345            input.extend_from_slice(&content);
2346            input.extend_from_slice(b"\x1b[201~");
2347
2348            let events = parser.parse(&input);
2349            for event in &events {
2350                if let Event::Paste(p) = event {
2351                    prop_assert!(p.text.len() <= MAX_PASTE_LEN,
2352                        "Paste text {} exceeds limit {}", p.text.len(), MAX_PASTE_LEN);
2353                }
2354            }
2355        }
2356
2357        /// Feeding input byte-by-byte yields same events as feeding all at once.
2358        #[test]
2359        fn incremental_matches_bulk(input in arb_byte_vec(128)) {
2360            let mut bulk_parser = InputParser::new();
2361            let bulk_events = bulk_parser.parse(&input);
2362
2363            let mut incr_parser = InputParser::new();
2364            let mut incr_events = Vec::new();
2365            for byte in &input {
2366                incr_events.extend(incr_parser.parse(std::slice::from_ref(byte)));
2367            }
2368
2369            let bulk_dbg: Vec<String> = bulk_events.iter().map(|e| format!("{e:?}")).collect();
2370            let incr_dbg: Vec<String> = incr_events.iter().map(|e| format!("{e:?}")).collect();
2371            prop_assert_eq!(bulk_dbg, incr_dbg,
2372                "Bulk vs incremental mismatch for input {:?}", input);
2373        }
2374
2375        /// Repeated parsing of the same input must always produce the same result
2376        /// (parser is deterministic after reset).
2377        #[test]
2378        fn deterministic_output(input in arb_byte_vec(128)) {
2379            let mut parser1 = InputParser::new();
2380            let events1 = parser1.parse(&input);
2381
2382            let mut parser2 = InputParser::new();
2383            let events2 = parser2.parse(&input);
2384
2385            let dbg1: Vec<String> = events1.iter().map(|e| format!("{e:?}")).collect();
2386            let dbg2: Vec<String> = events2.iter().map(|e| format!("{e:?}")).collect();
2387            prop_assert_eq!(dbg1, dbg2);
2388        }
2389    }
2390
2391    // ── Targeted invariant tests (outside proptest! macro) ─────────────
2392
2393    /// After a long garbage run, parser handles a simple key within bounded time.
2394    #[test]
2395    fn no_quadratic_blowup() {
2396        let mut parser = InputParser::new();
2397
2398        // 64KB of random-ish bytes (repeating pattern).
2399        let garbage: Vec<u8> = (0..65536).map(|i| (i % 256) as u8).collect();
2400        let _ = parser.parse(&garbage);
2401
2402        // Follow with a clean key — must not take pathological time.
2403        let events = parser.parse(b"a");
2404        let _ = events; // primarily asserting no hang/panic
2405    }
2406
2407    /// Oversized CSI sequence triggers DoS protection without panic.
2408    #[test]
2409    fn oversized_csi_transitions_to_ignore() {
2410        let mut parser = InputParser::new();
2411
2412        // CSI followed by MAX_CSI_LEN+100 parameter bytes then a final byte.
2413        let mut input = vec![0x1B, b'['];
2414        input.extend(std::iter::repeat_n(b'0', MAX_CSI_LEN + 100));
2415        input.push(b'm');
2416
2417        let _ = parser.parse(&input);
2418
2419        // Parser must still be usable.
2420        let events = parser.parse(b"x");
2421        assert_eq!(events.len(), 1);
2422        assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('x')));
2423    }
2424
2425    /// Oversized OSC sequence triggers DoS protection without panic.
2426    #[test]
2427    fn oversized_osc_transitions_to_ignore() {
2428        let mut parser = InputParser::new();
2429
2430        // OSC followed by MAX_OSC_LEN+100 content bytes then ST.
2431        let mut input = vec![0x1B, b']'];
2432        input.extend(std::iter::repeat_n(b'a', MAX_OSC_LEN + 100));
2433        input.push(0x07); // BEL terminator
2434
2435        let _ = parser.parse(&input);
2436
2437        // Parser must still be usable.
2438        let events = parser.parse(b"y");
2439        assert_eq!(events.len(), 1);
2440        assert!(matches!(events[0], Event::Key(k) if k.code == KeyCode::Char('y')));
2441    }
2442
2443    /// Rapid ESC toggling doesn't corrupt state.
2444    #[test]
2445    fn rapid_esc_toggle() {
2446        let mut parser = InputParser::new();
2447
2448        // 1000 bare ESCs in a row.
2449        let input: Vec<u8> = vec![0x1B; 1000];
2450        let _ = parser.parse(&input);
2451
2452        // Must recover for a normal key.
2453        let events = parser.parse(b"k");
2454        assert!(!events.is_empty());
2455    }
2456
2457    /// Interleaved paste start sequences without end.
2458    #[test]
2459    fn unterminated_paste_recovery() {
2460        let mut parser = InputParser::new();
2461
2462        // Start paste, but never end it — feed lots of data.
2463        let mut input = b"\x1b[200~".to_vec();
2464        input.extend(std::iter::repeat_n(b'x', 2048));
2465
2466        let _ = parser.parse(&input);
2467
2468        // Now end the paste.
2469        let events = parser.parse(b"\x1b[201~");
2470        assert!(
2471            !events.is_empty(),
2472            "Parser should emit paste event on terminator"
2473        );
2474    }
2475
2476    /// UTF-8 boundary: all possible lead bytes followed by truncation.
2477    #[test]
2478    fn truncated_utf8_lead_bytes() {
2479        let mut parser = InputParser::new();
2480
2481        // Two-byte lead (0xC0..0xDF), three-byte (0xE0..0xEF), four-byte (0xF0..0xF7)
2482        for lead in [0xC2, 0xE0, 0xF0] {
2483            let _ = parser.parse(&[lead]);
2484            // Feed a normal ASCII after the truncated lead.
2485            let events = parser.parse(b"a");
2486            // Must not panic; 'a' should eventually appear.
2487            let _ = events;
2488        }
2489    }
2490
2491    /// Null bytes mixed with valid input.
2492    #[test]
2493    fn null_bytes_interleaved() {
2494        let mut parser = InputParser::new();
2495
2496        let input = b"\x00A\x00\x1b[A\x00B\x00";
2497        let events = parser.parse(input);
2498        // Should get events for 'A', Up arrow, and 'B' (nulls handled gracefully).
2499        assert!(
2500            events.len() >= 2,
2501            "Expected at least 2 events, got {}",
2502            events.len()
2503        );
2504    }
2505
2506    // ── Additional fuzz invariant tests (bd-10i.11.3) ─────────────────
2507
2508    /// Generate an OSC 52 clipboard sequence with arbitrary base64 payload.
2509    fn osc52_sequence() -> impl Strategy<Value = Vec<u8>> {
2510        let selector = prop_oneof![Just(b'c'), Just(b'p'), Just(b's')];
2511        // Generate valid base64 characters with occasional invalid ones
2512        let payload = prop::collection::vec(
2513            prop_oneof![
2514                0x41u8..=0x5A, // A-Z
2515                0x61u8..=0x7A, // a-z
2516                0x30u8..=0x39, // 0-9
2517                Just(b'+'),
2518                Just(b'/'),
2519                Just(b'='),
2520            ],
2521            0..=128,
2522        );
2523        let terminator = prop_oneof![
2524            Just(vec![0x1B, b'\\']), // ESC backslash (ST)
2525            Just(vec![0x07]),        // BEL
2526        ];
2527        (selector, payload, terminator).prop_map(|(sel, pay, term)| {
2528            let mut buf = vec![0x1B, b']', b'5', b'2', b';', sel, b';'];
2529            buf.extend_from_slice(&pay);
2530            buf.extend_from_slice(&term);
2531            buf
2532        })
2533    }
2534
2535    /// Generate an SGR mouse sequence.
2536    fn sgr_mouse_sequence() -> impl Strategy<Value = Vec<u8>> {
2537        let button_code = 0u16..128;
2538        let x = 1u16..300;
2539        let y = 1u16..100;
2540        let final_byte = prop_oneof![Just(b'M'), Just(b'm')];
2541        (button_code, x, y, final_byte)
2542            .prop_map(|(btn, x, y, fb)| format!("\x1b[<{btn};{x};{y}{}", fb as char).into_bytes())
2543    }
2544
2545    /// Generate Kitty keyboard protocol sequences.
2546    fn kitty_keyboard_sequence() -> impl Strategy<Value = Vec<u8>> {
2547        let keycode = prop_oneof![
2548            0x20u32..0x7F,       // ASCII range
2549            0x57344u32..0x57400, // Kitty special keys
2550            0x100u32..0x200,     // Extended range
2551        ];
2552        let modifier = 1u32..16;
2553        let kind = prop_oneof![Just(1u32), Just(2u32), Just(3u32)]; // press/repeat/release
2554        (keycode, prop::option::of(modifier), prop::option::of(kind)).prop_map(
2555            |(kc, mods, kind)| match (mods, kind) {
2556                (Some(m), Some(k)) => format!("\x1b[{kc};{m}:{k}u").into_bytes(),
2557                (Some(m), None) => format!("\x1b[{kc};{m}u").into_bytes(),
2558                _ => format!("\x1b[{kc}u").into_bytes(),
2559            },
2560        )
2561    }
2562
2563    proptest! {
2564        // --- OSC 52 clipboard tests ---
2565
2566        /// OSC 52 clipboard sequences never panic.
2567        #[test]
2568        fn osc52_never_panics(seq in osc52_sequence()) {
2569            let mut parser = InputParser::new();
2570            let events = parser.parse(&seq);
2571            // If parsed, should be a Clipboard event
2572            for event in &events {
2573                if let Event::Clipboard(c) = event {
2574                    prop_assert!(!c.content.is_empty() || c.content.is_empty(),
2575                        "Clipboard event must have a content field");
2576                }
2577            }
2578        }
2579
2580        /// OSC 52 with corrupt base64 doesn't panic.
2581        #[test]
2582        fn osc52_corrupt_base64_safe(payload in arb_byte_vec(128)) {
2583            let mut parser = InputParser::new();
2584            let mut input = b"\x1b]52;c;".to_vec();
2585            input.extend_from_slice(&payload);
2586            input.push(0x07); // BEL terminator
2587            let _ = parser.parse(&input);
2588        }
2589
2590        // --- SGR mouse tests ---
2591
2592        /// All SGR mouse sequences parse without panicking.
2593        #[test]
2594        fn sgr_mouse_never_panics(seq in sgr_mouse_sequence()) {
2595            let mut parser = InputParser::new();
2596            let events = parser.parse(&seq);
2597            for event in &events {
2598                // Verify events are well-formed (exercises Debug impl)
2599                let _ = format!("{event:?}");
2600            }
2601        }
2602
2603        /// SGR mouse with extreme coordinates doesn't overflow.
2604        #[test]
2605        fn sgr_mouse_extreme_coords(
2606            btn in 0u16..128,
2607            x in 0u16..=65535,
2608            y in 0u16..=65535,
2609        ) {
2610            let mut parser = InputParser::new();
2611            let input = format!("\x1b[<{btn};{x};{y}M").into_bytes();
2612            let events = parser.parse(&input);
2613            for event in &events {
2614                if let Event::Mouse(m) = event {
2615                    prop_assert!(m.x <= x, "Mouse x {} > input x {}", m.x, x);
2616                    prop_assert!(m.y <= y, "Mouse y {} > input y {}", m.y, y);
2617                }
2618            }
2619        }
2620
2621        // --- Kitty keyboard protocol tests ---
2622
2623        /// Kitty keyboard sequences never panic.
2624        #[test]
2625        fn kitty_keyboard_never_panics(seq in kitty_keyboard_sequence()) {
2626            let mut parser = InputParser::new();
2627            let _ = parser.parse(&seq);
2628        }
2629
2630        // --- State boundary tests ---
2631
2632        /// Truncated CSI followed by new valid sequence works correctly.
2633        #[test]
2634        fn truncated_csi_then_valid(
2635            params in prop::collection::vec(0x30u8..=0x3F, 1..=10),
2636            valid_char in 0x20u8..0x7F,
2637        ) {
2638            let mut parser = InputParser::new();
2639
2640            // Send truncated CSI (no final byte)
2641            let mut partial = vec![0x1B, b'['];
2642            partial.extend_from_slice(&params);
2643            let _ = parser.parse(&partial);
2644
2645            // Now send a fresh ESC sequence that should reset state
2646            let events = parser.parse(&[0x1B, b'[', b'A']); // Up arrow
2647            // Parser should eventually emit events (possibly including
2648            // interpretation of partial as complete)
2649            let _ = events;
2650
2651            // Verify recovery with a simple key
2652            let events = parser.parse(&[valid_char]);
2653            let _ = events;
2654        }
2655
2656        /// Truncated OSC followed by new valid sequence works.
2657        #[test]
2658        fn truncated_osc_then_valid(
2659            content in prop::collection::vec(0x20u8..=0x7E, 1..=32),
2660        ) {
2661            let mut parser = InputParser::new();
2662
2663            // Send unterminated OSC
2664            let mut partial = vec![0x1B, b']'];
2665            partial.extend_from_slice(&content);
2666            let _ = parser.parse(&partial);
2667
2668            // Send a new ESC to interrupt, then a valid key
2669            let events = parser.parse(b"\x1bz");
2670            let _ = events;
2671        }
2672
2673        // --- Near-limit tests ---
2674
2675        /// CSI sequence just under MAX_CSI_LEN produces events.
2676        #[test]
2677        fn csi_near_limit_produces_event(
2678            fill_byte in 0x30u8..=0x39, // digit parameter bytes
2679        ) {
2680            let mut parser = InputParser::new();
2681
2682            let mut input = vec![0x1B, b'['];
2683            // Fill to just under limit
2684            input.extend(std::iter::repeat_n(fill_byte, MAX_CSI_LEN - 1));
2685            input.push(b'm'); // final byte (SGR)
2686
2687            let events = parser.parse(&input);
2688            // Should NOT have been ignored (under limit)
2689            // The sequence is valid structurally even if params are nonsensical
2690            let _ = events;
2691
2692            // Parser should still work
2693            let events = parser.parse(b"a");
2694            prop_assert!(!events.is_empty(), "Parser stuck after near-limit CSI");
2695        }
2696
2697        /// OSC sequence just under MAX_OSC_LEN still processes.
2698        #[test]
2699        fn osc_near_limit_processes(
2700            fill_byte in 0x20u8..=0x7E,
2701        ) {
2702            let mut parser = InputParser::new();
2703
2704            let mut input = vec![0x1B, b']'];
2705            input.extend(std::iter::repeat_n(fill_byte, MAX_OSC_LEN - 1));
2706            input.push(0x07); // BEL terminator
2707
2708            let _ = parser.parse(&input);
2709
2710            // Parser should still work
2711            let events = parser.parse(b"b");
2712            prop_assert!(!events.is_empty(), "Parser stuck after near-limit OSC");
2713        }
2714
2715        // --- Consecutive paste tests ---
2716
2717        /// Multiple back-to-back paste sequences all emit events.
2718        #[test]
2719        fn consecutive_pastes_emit_events(count in 2usize..=5) {
2720            let mut parser = InputParser::new();
2721            let mut input = Vec::new();
2722
2723            for i in 0..count {
2724                input.extend_from_slice(b"\x1b[200~");
2725                input.extend_from_slice(format!("paste_{i}").as_bytes());
2726                input.extend_from_slice(b"\x1b[201~");
2727            }
2728
2729            let events = parser.parse(&input);
2730            let paste_events: Vec<_> = events.iter()
2731                .filter(|e| matches!(e, Event::Paste(_)))
2732                .collect();
2733
2734            prop_assert_eq!(paste_events.len(), count,
2735                "Expected {} paste events, got {}", count, paste_events.len());
2736        }
2737
2738        /// Paste with invalid UTF-8 bytes doesn't panic.
2739        #[test]
2740        fn paste_with_invalid_utf8(content in arb_byte_vec(256)) {
2741            let mut parser = InputParser::new();
2742            let mut input = b"\x1b[200~".to_vec();
2743            input.extend_from_slice(&content);
2744            input.extend_from_slice(b"\x1b[201~");
2745
2746            let events = parser.parse(&input);
2747            for event in &events {
2748                if let Event::Paste(p) = event {
2749                    // Text should be valid UTF-8 (lossy conversion happens internally)
2750                    prop_assert!(p.text.is_char_boundary(0), "Paste text is not valid UTF-8");
2751                }
2752            }
2753        }
2754
2755        // --- Recovery invariants ---
2756
2757        /// After any arbitrary input, feeding ESC then a known key recovers.
2758        #[test]
2759        fn recovery_via_esc_reset(garbage in arb_byte_vec(256)) {
2760            let mut parser = InputParser::new();
2761            let _ = parser.parse(&garbage);
2762
2763            // Terminate any pending OSC (BEL works from any OSC sub-state),
2764            // then ESC to flush any other intermediate state.
2765            let _ = parser.parse(b"\x07\x1b\\\x1b");
2766            let _ = parser.parse(b"\x1b");
2767
2768            // Now feed a clean character.
2769            let _ = parser.parse(b"z");
2770
2771            // Feed one more clean character to verify.
2772            let events = parser.parse(b"q");
2773            // After terminating all pending sequences and feeding clean input,
2774            // the parser must produce events.
2775            prop_assert!(!events.is_empty(),
2776                "Parser did not recover after garbage + reset");
2777        }
2778    }
2779}