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