Skip to main content

zellij_utils/vendored/termwiz/
input.rs

1//! This module provides an InputParser struct to help with parsing
2//! input received from a terminal.
3use crate::vendored::termwiz::keymap::{Found, KeyMap};
4use crate::vendored::termwiz::readbuf::ReadBuffer;
5use bitflags::bitflags;
6use std::fmt::Write;
7
8pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
9
10bitflags! {
11    #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
12    pub struct Modifiers: u16 {
13        const NONE = 0;
14        const SHIFT = 1 << 1;
15        const ALT = 1 << 2;
16        const CTRL = 1 << 3;
17        const SUPER = 1 << 4;
18        const LEFT_ALT = 1 << 5;
19        const RIGHT_ALT = 1 << 6;
20        const LEADER = 1 << 7;
21        const LEFT_CTRL = 1 << 8;
22        const RIGHT_CTRL = 1 << 9;
23        const LEFT_SHIFT = 1 << 10;
24        const RIGHT_SHIFT = 1 << 11;
25        const ENHANCED_KEY = 1 << 12;
26    }
27}
28
29impl Modifiers {
30    pub fn encode_xterm(self) -> u8 {
31        let mut number = 0;
32        if self.contains(Self::SHIFT) {
33            number |= 1;
34        }
35        if self.contains(Self::ALT) {
36            number |= 2;
37        }
38        if self.contains(Self::CTRL) {
39            number |= 4;
40        }
41        number
42    }
43
44    pub fn remove_positional_mods(self) -> Self {
45        self - (Self::LEFT_ALT
46            | Self::RIGHT_ALT
47            | Self::LEFT_CTRL
48            | Self::RIGHT_CTRL
49            | Self::LEFT_SHIFT
50            | Self::RIGHT_SHIFT
51            | Self::ENHANCED_KEY)
52    }
53}
54
55bitflags! {
56    #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
57    pub struct KittyKeyboardFlags: u16 {
58        const NONE = 0;
59        const DISAMBIGUATE_ESCAPE_CODES = 1;
60        const REPORT_EVENT_TYPES = 2;
61        const REPORT_ALTERNATE_KEYS = 4;
62        const REPORT_ALL_KEYS_AS_ESCAPE_CODES = 8;
63        const REPORT_ASSOCIATED_TEXT = 16;
64    }
65}
66
67pub fn ctrl_mapping(c: char) -> Option<char> {
68    Some(match c {
69        '@' | '`' | ' ' | '2' => '\x00',
70        'A' | 'a' => '\x01',
71        'B' | 'b' => '\x02',
72        'C' | 'c' => '\x03',
73        'D' | 'd' => '\x04',
74        'E' | 'e' => '\x05',
75        'F' | 'f' => '\x06',
76        'G' | 'g' => '\x07',
77        'H' | 'h' => '\x08',
78        'I' | 'i' => '\x09',
79        'J' | 'j' => '\x0a',
80        'K' | 'k' => '\x0b',
81        'L' | 'l' => '\x0c',
82        'M' | 'm' => '\x0d',
83        'N' | 'n' => '\x0e',
84        'O' | 'o' => '\x0f',
85        'P' | 'p' => '\x10',
86        'Q' | 'q' => '\x11',
87        'R' | 'r' => '\x12',
88        'S' | 's' => '\x13',
89        'T' | 't' => '\x14',
90        'U' | 'u' => '\x15',
91        'V' | 'v' => '\x16',
92        'W' | 'w' => '\x17',
93        'X' | 'x' => '\x18',
94        'Y' | 'y' => '\x19',
95        'Z' | 'z' => '\x1a',
96        '[' | '3' | '{' => '\x1b',
97        '\\' | '4' | '|' => '\x1c',
98        ']' | '5' | '}' => '\x1d',
99        '^' | '6' | '~' => '\x1e',
100        '_' | '7' | '/' => '\x1f',
101        '8' | '?' => '\x7f',
102        _ => return None,
103    })
104}
105
106bitflags! {
107    #[derive(Debug, Default, Clone, PartialEq, Eq)]
108    pub struct MouseButtons: u8 {
109        const NONE = 0;
110        const LEFT = 1<<1;
111        const RIGHT = 1<<2;
112        const MIDDLE = 1<<3;
113        const VERT_WHEEL = 1<<4;
114        const HORZ_WHEEL = 1<<5;
115        /// if set then the wheel movement was in the positive
116        /// direction, else the negative direction
117        const WHEEL_POSITIVE = 1<<6;
118    }
119}
120
121pub const CSI: &str = "\x1b[";
122pub const SS3: &str = "\x1bO";
123
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub enum InputEvent {
126    Key(KeyEvent),
127    Mouse(MouseEvent),
128    PixelMouse(PixelMouseEvent),
129    /// Detected that the user has resized the terminal
130    Resized {
131        cols: usize,
132        rows: usize,
133    },
134    /// For terminals that support Bracketed Paste mode,
135    /// pastes are collected and reported as this variant.
136    Paste(String),
137    /// The program has woken the input thread.
138    Wake,
139    /// An Operating System Command sequence was received.
140    /// Contains the raw payload between \x1b] and the terminator.
141    OperatingSystemCommand(Vec<u8>),
142    /// A CSI-based device control / status report reply emitted by the
143    /// host terminal (not a keyboard event). This variant is only produced
144    /// for a deliberately narrow whitelist of final bytes — `t` (pixel
145    /// dimensions reply), `y` (DECRPM reply), `c` (Primary-DA reply), and
146    /// `n` (DSR reply). The raw field contains the exact byte sequence of
147    /// the original report (including the leading ESC) so it can be
148    /// forwarded verbatim without re-serialization.
149    DeviceControlReply {
150        intermediates: Vec<u8>,
151        params: Vec<u8>,
152        final_byte: u8,
153        raw: Vec<u8>,
154    },
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
158pub struct MouseEvent {
159    pub x: u16,
160    pub y: u16,
161    pub mouse_buttons: MouseButtons,
162    pub modifiers: Modifiers,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct PixelMouseEvent {
167    pub x_pixels: u16,
168    pub y_pixels: u16,
169    pub mouse_buttons: MouseButtons,
170    pub modifiers: Modifiers,
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
174pub struct KeyEvent {
175    /// Which key was pressed
176    pub key: KeyCode,
177    /// Which modifiers are down
178    pub modifiers: Modifiers,
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum KeyboardEncoding {
183    Xterm,
184    /// <http://www.leonerd.org.uk/hacks/fixterms/>
185    CsiU,
186    /// <https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md>
187    Win32,
188    /// <https://sw.kovidgoyal.net/kitty/keyboard-protocol/>
189    Kitty(KittyKeyboardFlags),
190}
191
192/// Specifies terminal modes/configuration that can influence how a KeyCode
193/// is encoded when being sent to and application via the pty.
194#[derive(Debug, Clone, Copy)]
195pub struct KeyCodeEncodeModes {
196    pub encoding: KeyboardEncoding,
197    pub application_cursor_keys: bool,
198    pub newline_mode: bool,
199    pub modify_other_keys: Option<i64>,
200}
201
202/// Which key is pressed.  Not all of these are probable to appear
203/// on most systems.  A lot of this list is @wez trawling docs and
204/// making an entry for things that might be possible in this first pass.
205#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
206pub enum KeyCode {
207    /// The decoded unicode character
208    Char(char),
209
210    Hyper,
211    Super,
212    Meta,
213
214    /// Ctrl-break on windows
215    Cancel,
216    Backspace,
217    Tab,
218    Clear,
219    Enter,
220    Shift,
221    Escape,
222    LeftShift,
223    RightShift,
224    Control,
225    LeftControl,
226    RightControl,
227    Alt,
228    LeftAlt,
229    RightAlt,
230    Menu,
231    LeftMenu,
232    RightMenu,
233    Pause,
234    CapsLock,
235    PageUp,
236    PageDown,
237    End,
238    Home,
239    LeftArrow,
240    RightArrow,
241    UpArrow,
242    DownArrow,
243    Select,
244    Print,
245    Execute,
246    PrintScreen,
247    Insert,
248    Delete,
249    Help,
250    LeftWindows,
251    RightWindows,
252    Applications,
253    Sleep,
254    Numpad0,
255    Numpad1,
256    Numpad2,
257    Numpad3,
258    Numpad4,
259    Numpad5,
260    Numpad6,
261    Numpad7,
262    Numpad8,
263    Numpad9,
264    Multiply,
265    Add,
266    Separator,
267    Subtract,
268    Decimal,
269    Divide,
270    /// F1-F24 are possible
271    Function(u8),
272    NumLock,
273    ScrollLock,
274    Copy,
275    Cut,
276    Paste,
277    BrowserBack,
278    BrowserForward,
279    BrowserRefresh,
280    BrowserStop,
281    BrowserSearch,
282    BrowserFavorites,
283    BrowserHome,
284    VolumeMute,
285    VolumeDown,
286    VolumeUp,
287    MediaNextTrack,
288    MediaPrevTrack,
289    MediaStop,
290    MediaPlayPause,
291    ApplicationLeftArrow,
292    ApplicationRightArrow,
293    ApplicationUpArrow,
294    ApplicationDownArrow,
295    KeyPadHome,
296    KeyPadEnd,
297    KeyPadPageUp,
298    KeyPadPageDown,
299    KeyPadBegin,
300
301    #[doc(hidden)]
302    InternalPasteStart,
303    #[doc(hidden)]
304    InternalPasteEnd,
305}
306
307impl KeyCode {
308    /// if SHIFT is held and we have KeyCode::Char('c') we want to normalize
309    /// that keycode to KeyCode::Char('C'); that is what this function does.
310    pub fn normalize_shift_to_upper_case(self, modifiers: Modifiers) -> KeyCode {
311        if modifiers.contains(Modifiers::SHIFT) {
312            match self {
313                KeyCode::Char(c) if c.is_ascii_lowercase() => KeyCode::Char(c.to_ascii_uppercase()),
314                _ => self,
315            }
316        } else {
317            self
318        }
319    }
320
321    /// Return true if the key represents a modifier key.
322    pub fn is_modifier(self) -> bool {
323        matches!(
324            self,
325            Self::Hyper
326                | Self::Super
327                | Self::Meta
328                | Self::Shift
329                | Self::LeftShift
330                | Self::RightShift
331                | Self::Control
332                | Self::LeftControl
333                | Self::RightControl
334                | Self::Alt
335                | Self::LeftAlt
336                | Self::RightAlt
337                | Self::LeftWindows
338                | Self::RightWindows
339        )
340    }
341
342    /// Returns the byte sequence that represents this KeyCode and Modifier combination.
343    pub fn encode(
344        &self,
345        mods: Modifiers,
346        modes: KeyCodeEncodeModes,
347        is_down: bool,
348    ) -> Result<String> {
349        if !is_down {
350            // We only want down events
351            return Ok(String::new());
352        }
353        // We are encoding the key as an xterm-compatible sequence, which does not support
354        // positional modifiers.
355        let mods = mods.remove_positional_mods();
356
357        use KeyCode::*;
358
359        let key = self.normalize_shift_to_upper_case(mods);
360        // Normalize the modifier state for Char's that are uppercase; remove
361        // the SHIFT modifier so that reduce ambiguity below
362        let mods = match key {
363            Char(c)
364                if (c.is_ascii_punctuation() || c.is_ascii_uppercase())
365                    && mods.contains(Modifiers::SHIFT) =>
366            {
367                mods & !Modifiers::SHIFT
368            },
369            _ => mods,
370        };
371
372        // Normalize Backspace and Delete
373        let key = match key {
374            Char('\x7f') => Delete,
375            Char('\x08') => Backspace,
376            c => c,
377        };
378
379        let mut buf = String::new();
380
381        // TODO: also respect self.application_keypad
382
383        match key {
384            Char(c)
385                if is_ambiguous_ascii_ctrl(c)
386                    && mods.contains(Modifiers::CTRL)
387                    && modes.encoding == KeyboardEncoding::CsiU =>
388            {
389                csi_u_encode(&mut buf, c, mods, &modes)?;
390            },
391            Char(c) if c.is_ascii_uppercase() && mods.contains(Modifiers::CTRL) => {
392                csi_u_encode(&mut buf, c, mods, &modes)?;
393            },
394
395            Char(c) if mods.contains(Modifiers::CTRL) && modes.modify_other_keys == Some(2) => {
396                csi_u_encode(&mut buf, c, mods, &modes)?;
397            },
398            Char(c) if mods.contains(Modifiers::CTRL) && ctrl_mapping(c).is_some() => {
399                let c = ctrl_mapping(c).unwrap();
400                if mods.contains(Modifiers::ALT) {
401                    buf.push(0x1b as char);
402                }
403                buf.push(c);
404            },
405
406            // When alt is pressed, send escape first to indicate to the peer that
407            // ALT is pressed.  We do this only for ascii alnum characters because
408            // eg: on macOS generates altgr style glyphs and keeps the ALT key
409            // in the modifier set.  This confuses eg: zsh which then just displays
410            // <fffffffff> as the input, so we want to avoid that.
411            Char(c)
412                if (c.is_ascii_alphanumeric() || c.is_ascii_punctuation())
413                    && mods.contains(Modifiers::ALT) =>
414            {
415                buf.push(0x1b as char);
416                buf.push(c);
417            },
418
419            Backspace => {
420                // Backspace sends the default VERASE which is confusingly
421                // the DEL ascii codepoint rather than BS.
422                // We only send BS when CTRL is held.
423                if mods.contains(Modifiers::CTRL) {
424                    csi_u_encode(&mut buf, '\x08', mods, &modes)?;
425                } else if mods.contains(Modifiers::SHIFT) {
426                    csi_u_encode(&mut buf, '\x7f', mods, &modes)?;
427                } else {
428                    if mods.contains(Modifiers::ALT) {
429                        buf.push(0x1b as char);
430                    }
431                    buf.push('\x7f');
432                }
433            },
434
435            Enter | Escape => {
436                let c = match key {
437                    Enter => '\r',
438                    Escape => '\x1b',
439                    _ => unreachable!(),
440                };
441                if mods.contains(Modifiers::SHIFT) || mods.contains(Modifiers::CTRL) {
442                    csi_u_encode(&mut buf, c, mods, &modes)?;
443                } else {
444                    if mods.contains(Modifiers::ALT) {
445                        buf.push(0x1b as char);
446                    }
447                    buf.push(c);
448                    if modes.newline_mode && key == Enter {
449                        buf.push(0x0a as char);
450                    }
451                }
452            },
453
454            Tab if !mods.is_empty() && modes.modify_other_keys.is_some() => {
455                csi_u_encode(&mut buf, '\t', mods, &modes)?;
456            },
457
458            Tab => {
459                if mods.contains(Modifiers::ALT) {
460                    buf.push(0x1b as char);
461                }
462                let mods = mods & !Modifiers::ALT;
463                if mods == Modifiers::CTRL {
464                    buf.push_str("\x1b[9;5u");
465                } else if mods == Modifiers::CTRL | Modifiers::SHIFT {
466                    buf.push_str("\x1b[1;5Z");
467                } else if mods == Modifiers::SHIFT {
468                    buf.push_str("\x1b[Z");
469                } else {
470                    buf.push('\t');
471                }
472            },
473
474            Char(c) => {
475                if mods.is_empty() {
476                    buf.push(c);
477                } else {
478                    csi_u_encode(&mut buf, c, mods, &modes)?;
479                }
480            },
481
482            Home
483            | KeyPadHome
484            | End
485            | KeyPadEnd
486            | UpArrow
487            | DownArrow
488            | RightArrow
489            | LeftArrow
490            | ApplicationUpArrow
491            | ApplicationDownArrow
492            | ApplicationRightArrow
493            | ApplicationLeftArrow => {
494                let (force_app, c) = match key {
495                    UpArrow => (false, 'A'),
496                    DownArrow => (false, 'B'),
497                    RightArrow => (false, 'C'),
498                    LeftArrow => (false, 'D'),
499                    KeyPadHome | Home => (false, 'H'),
500                    End | KeyPadEnd => (false, 'F'),
501                    ApplicationUpArrow => (true, 'A'),
502                    ApplicationDownArrow => (true, 'B'),
503                    ApplicationRightArrow => (true, 'C'),
504                    ApplicationLeftArrow => (true, 'D'),
505                    _ => unreachable!(),
506                };
507
508                let csi_or_ss3 = if force_app || modes.application_cursor_keys {
509                    // Use SS3 in application mode
510                    SS3
511                } else {
512                    // otherwise use regular CSI
513                    CSI
514                };
515
516                if mods.contains(Modifiers::ALT)
517                    || mods.contains(Modifiers::SHIFT)
518                    || mods.contains(Modifiers::CTRL)
519                {
520                    write!(buf, "{}1;{}{}", CSI, 1 + mods.encode_xterm(), c)?;
521                } else {
522                    write!(buf, "{}{}", csi_or_ss3, c)?;
523                }
524            },
525
526            PageUp | PageDown | KeyPadPageUp | KeyPadPageDown | Insert | Delete => {
527                let c = match key {
528                    Insert => 2,
529                    Delete => 3,
530                    KeyPadPageUp | PageUp => 5,
531                    KeyPadPageDown | PageDown => 6,
532                    _ => unreachable!(),
533                };
534
535                if mods.contains(Modifiers::ALT)
536                    || mods.contains(Modifiers::SHIFT)
537                    || mods.contains(Modifiers::CTRL)
538                {
539                    write!(buf, "\x1b[{};{}~", c, 1 + mods.encode_xterm())?;
540                } else {
541                    write!(buf, "\x1b[{}~", c)?;
542                }
543            },
544
545            Function(n) => {
546                if mods.is_empty() && n < 5 {
547                    // F1-F4 are encoded using SS3 if there are no modifiers
548                    write!(
549                        buf,
550                        "{}",
551                        match n {
552                            1 => "\x1bOP",
553                            2 => "\x1bOQ",
554                            3 => "\x1bOR",
555                            4 => "\x1bOS",
556                            _ => unreachable!("wat?"),
557                        }
558                    )?;
559                } else if n < 5 {
560                    // Special case for F1-F4 with modifiers
561                    let code = match n {
562                        1 => 'P',
563                        2 => 'Q',
564                        3 => 'R',
565                        4 => 'S',
566                        _ => unreachable!("wat?"),
567                    };
568                    write!(buf, "\x1b[1;{}{code}", 1 + mods.encode_xterm())?;
569                } else {
570                    // Higher numbered F-keys using CSI instead of SS3.
571                    let intro = match n {
572                        1 => "\x1b[11",
573                        2 => "\x1b[12",
574                        3 => "\x1b[13",
575                        4 => "\x1b[14",
576                        5 => "\x1b[15",
577                        6 => "\x1b[17",
578                        7 => "\x1b[18",
579                        8 => "\x1b[19",
580                        9 => "\x1b[20",
581                        10 => "\x1b[21",
582                        11 => "\x1b[23",
583                        12 => "\x1b[24",
584                        13 => "\x1b[25",
585                        14 => "\x1b[26",
586                        15 => "\x1b[28",
587                        16 => "\x1b[29",
588                        17 => "\x1b[31",
589                        18 => "\x1b[32",
590                        19 => "\x1b[33",
591                        20 => "\x1b[34",
592                        21 => "\x1b[42",
593                        22 => "\x1b[43",
594                        23 => "\x1b[44",
595                        24 => "\x1b[45",
596                        _ => return Err(format!("unhandled fkey number {}", n).into()),
597                    };
598                    let encoded_mods = mods.encode_xterm();
599                    if encoded_mods == 0 {
600                        // If no modifiers are held, don't send the modifier
601                        // sequence, as the modifier encoding is a CSI-u extension.
602                        write!(buf, "{}~", intro)?;
603                    } else {
604                        write!(buf, "{};{}~", intro, 1 + encoded_mods)?;
605                    }
606                }
607            },
608
609            Numpad0 | Numpad3 | Numpad9 | Decimal => {
610                let intro = match key {
611                    Numpad0 => "\x1b[2",
612                    Numpad3 => "\x1b[6",
613                    Numpad9 => "\x1b[6",
614                    Decimal => "\x1b[3",
615                    _ => unreachable!(),
616                };
617
618                let encoded_mods = mods.encode_xterm();
619                if encoded_mods == 0 {
620                    write!(buf, "{}~", intro)?;
621                } else {
622                    write!(buf, "{};{}~", intro, 1 + encoded_mods)?;
623                }
624            },
625
626            Numpad1 | Numpad2 | Numpad4 | Numpad5 | KeyPadBegin | Numpad6 | Numpad7 | Numpad8 => {
627                let c = match key {
628                    Numpad1 => "F",
629                    Numpad2 => "B",
630                    Numpad4 => "D",
631                    KeyPadBegin | Numpad5 => "E",
632                    Numpad6 => "C",
633                    Numpad7 => "H",
634                    Numpad8 => "A",
635                    _ => unreachable!(),
636                };
637
638                let encoded_mods = mods.encode_xterm();
639                if encoded_mods == 0 {
640                    write!(buf, "{}{}", CSI, c)?;
641                } else {
642                    write!(buf, "{}1;{}{}", CSI, 1 + encoded_mods, c)?;
643                }
644            },
645
646            Multiply | Add | Separator | Subtract | Divide => {},
647
648            // Modifier keys pressed on their own don't expand to anything
649            Control | LeftControl | RightControl | Alt | LeftAlt | RightAlt | Menu | LeftMenu
650            | RightMenu | Super | Hyper | Shift | LeftShift | RightShift | Meta | LeftWindows
651            | RightWindows | NumLock | ScrollLock | Cancel | Clear | Pause | CapsLock | Select
652            | Print | PrintScreen | Execute | Help | Applications | Sleep | Copy | Cut | Paste
653            | BrowserBack | BrowserForward | BrowserRefresh | BrowserStop | BrowserSearch
654            | BrowserFavorites | BrowserHome | VolumeMute | VolumeDown | VolumeUp
655            | MediaNextTrack | MediaPrevTrack | MediaStop | MediaPlayPause | InternalPasteStart
656            | InternalPasteEnd => {},
657        };
658
659        Ok(buf)
660    }
661}
662
663/// characters that when masked for CTRL could be an ascii control character
664/// or could be a key that a user legitimately wants to process in their
665/// terminal application
666fn is_ambiguous_ascii_ctrl(c: char) -> bool {
667    matches!(c, 'i' | 'I' | 'm' | 'M' | '[' | '{' | '@')
668}
669
670fn is_ascii(c: char) -> bool {
671    (c as u32) < 0x80
672}
673
674fn csi_u_encode(
675    buf: &mut String,
676    c: char,
677    mods: Modifiers,
678    modes: &KeyCodeEncodeModes,
679) -> Result<()> {
680    if modes.encoding == KeyboardEncoding::CsiU && is_ascii(c) {
681        write!(buf, "\x1b[{};{}u", c as u32, 1 + mods.encode_xterm())?;
682        return Ok(());
683    }
684
685    // <https://invisible-island.net/xterm/modified-keys.html>
686    match (c, modes.modify_other_keys) {
687        ('c' | 'd' | '\x1b' | '\x7f' | '\x08', Some(1)) => {
688            // Exclude well-known keys from modifyOtherKeys mode 1
689        },
690        (c, Some(_)) => {
691            write!(buf, "\x1b[27;{};{}~", 1 + mods.encode_xterm(), c as u32)?;
692            return Ok(());
693        },
694        _ => {},
695    }
696
697    let c = if mods.contains(Modifiers::CTRL) && ctrl_mapping(c).is_some() {
698        ctrl_mapping(c).unwrap()
699    } else {
700        c
701    };
702    if mods.contains(Modifiers::ALT) {
703        buf.push(0x1b as char);
704    }
705    write!(buf, "{}", c)?;
706    Ok(())
707}
708
709#[derive(Debug, Clone, Copy, PartialEq, Eq)]
710enum MouseButton {
711    Button1Press,
712    Button1Release,
713    Button1Drag,
714    Button2Press,
715    Button2Release,
716    Button2Drag,
717    Button3Press,
718    Button3Release,
719    Button3Drag,
720    Button4Press,
721    Button4Release,
722    Button5Press,
723    Button5Release,
724    Button6Press,
725    Button6Release,
726    Button7Press,
727    Button7Release,
728    None,
729}
730
731fn decode_mouse_button(control: u8, p0: i64) -> Option<MouseButton> {
732    match (control, p0 & 0b110_0011) {
733        (b'M', 0) => Some(MouseButton::Button1Press),
734        (b'm', 0) => Some(MouseButton::Button1Release),
735        (b'M', 1) => Some(MouseButton::Button2Press),
736        (b'm', 1) => Some(MouseButton::Button2Release),
737        (b'M', 2) => Some(MouseButton::Button3Press),
738        (b'm', 2) => Some(MouseButton::Button3Release),
739        (b'M', 64) => Some(MouseButton::Button4Press),
740        (b'm', 64) => Some(MouseButton::Button4Release),
741        (b'M', 65) => Some(MouseButton::Button5Press),
742        (b'm', 65) => Some(MouseButton::Button5Release),
743        (b'M', 66) => Some(MouseButton::Button6Press),
744        (b'm', 66) => Some(MouseButton::Button6Release),
745        (b'M', 67) => Some(MouseButton::Button7Press),
746        (b'm', 67) => Some(MouseButton::Button7Release),
747        (b'M', 32) => Some(MouseButton::Button1Drag),
748        (b'M', 33) => Some(MouseButton::Button2Drag),
749        (b'M', 34) => Some(MouseButton::Button3Drag),
750        (b'M', 35) | (b'm', 35) | (b'M', 3) | (b'm', 3) => Some(MouseButton::None),
751        _ => ::core::option::Option::None,
752    }
753}
754
755impl From<MouseButton> for MouseButtons {
756    fn from(button: MouseButton) -> MouseButtons {
757        match button {
758            MouseButton::Button1Press | MouseButton::Button1Drag => MouseButtons::LEFT,
759            MouseButton::Button2Press | MouseButton::Button2Drag => MouseButtons::MIDDLE,
760            MouseButton::Button3Press | MouseButton::Button3Drag => MouseButtons::RIGHT,
761            MouseButton::Button4Press => MouseButtons::VERT_WHEEL | MouseButtons::WHEEL_POSITIVE,
762            MouseButton::Button5Press => MouseButtons::VERT_WHEEL,
763            MouseButton::Button6Press => MouseButtons::HORZ_WHEEL | MouseButtons::WHEEL_POSITIVE,
764            MouseButton::Button7Press => MouseButtons::HORZ_WHEEL,
765            _ => MouseButtons::NONE,
766        }
767    }
768}
769
770fn decode_mouse_modifiers(p0: i64) -> Modifiers {
771    let mut modifiers = Modifiers::NONE;
772    if p0 & 4 != 0 {
773        modifiers |= Modifiers::SHIFT;
774    }
775    if p0 & 8 != 0 {
776        modifiers |= Modifiers::ALT;
777    }
778    if p0 & 16 != 0 {
779        modifiers |= Modifiers::CTRL;
780    }
781    modifiers
782}
783
784/// Try to parse an SGR mouse sequence from the buffer.
785/// Returns Some((InputEvent, bytes_consumed)) on success.
786/// Returns None if the buffer does not contain a complete SGR mouse sequence.
787fn parse_sgr_mouse(buf: &[u8]) -> Option<(InputEvent, usize)> {
788    // Must start with \x1b[<
789    if buf.len() < 6 || !buf.starts_with(b"\x1b[<") {
790        return None;
791    }
792    let rest = &buf[3..]; // skip \x1b[<
793
794    // Find the terminating M or m
795    let term_pos = rest.iter().position(|&b| b == b'M' || b == b'm')?;
796    let control = rest[term_pos];
797    let params_str = std::str::from_utf8(&rest[..term_pos]).ok()?;
798
799    // Parse three semicolon-separated integers
800    let mut parts = params_str.splitn(3, ';');
801    let p0: i64 = parts.next()?.parse().ok()?;
802    let p1: i64 = parts.next()?.parse().ok()?;
803    let p2: i64 = parts.next()?.parse().ok()?;
804
805    let button = decode_mouse_button(control, p0)?;
806    let modifiers = decode_mouse_modifiers(p0);
807    let mouse_buttons: MouseButtons = button.into();
808
809    let consumed = 3 + term_pos + 1; // \x1b[< + params + M/m
810
811    Some((
812        InputEvent::Mouse(MouseEvent {
813            x: p1 as u16,
814            y: p2 as u16,
815            mouse_buttons,
816            modifiers,
817        }),
818        consumed,
819    ))
820}
821
822/// Attempt to parse an OSC (Operating System Command) sequence from the buffer.
823/// Returns `Some((InputEvent::OperatingSystemCommand(payload), len))` if a complete
824/// OSC sequence is found, where `payload` is the bytes between `\x1b]` and the
825/// terminator, and `len` is the total number of bytes consumed.
826/// Returns `None` if the buffer does not start with `\x1b]` or the sequence is incomplete.
827/// Attempt to parse a CSI-based host-terminal report (device-attribute
828/// responses, DSR replies, DECRPM, pixel-dims reply, etc.) from the start
829/// of `buf`.
830///
831/// Only a narrow whitelist of final bytes is recognised: `t`, `y`, `c`,
832/// `n`. Any other final byte returns `None` so the bytes fall through to
833/// the regular CSI key-mapping machinery.
834///
835/// Returns `Some((event, len))` on a full match, `None` if the bytes do
836/// not look like a whitelisted CSI report (caller should try the next
837/// parser) and reserves returning None with the buffer starting with
838/// `ESC [` for two distinct cases — not currently disambiguated here:
839/// - truly malformed / unsupported sequence, or
840/// - incomplete input; caller handles incompleteness via `maybe_more`.
841fn parse_csi_report(buf: &[u8]) -> Option<(InputEvent, usize)> {
842    if buf.get(0) != Some(&0x1b) || buf.get(1) != Some(&b'[') {
843        return None;
844    }
845    // Scan forward looking for a final byte in the whitelist, or bail if
846    // we hit something that clearly is not a CSI report (a non-printable
847    // byte other than the known final bytes).
848    let mut i = 2;
849    let mut intermediates: Vec<u8> = Vec::new();
850    let mut params: Vec<u8> = Vec::new();
851    // Parameters (0x30..=0x3F) come first, then intermediates (0x20..=0x2F),
852    // then a final byte (0x40..=0x7E). We only scan up to a reasonable
853    // length to avoid pathological buffers.
854    let max_scan = buf.len().min(256);
855    while i < max_scan {
856        let b = buf[i];
857        match b {
858            // Parameters: digits, `;`, `:`, `?`, `<`, `=`, `>`
859            0x30..=0x3F => {
860                params.push(b);
861                i += 1;
862            },
863            // Intermediates: space, `!`, `"`, ... `/`
864            0x20..=0x2F => {
865                intermediates.push(b);
866                i += 1;
867            },
868            // Final byte (0x40..=0x7E): must be one of the whitelisted bytes.
869            b't' | b'y' | b'c' | b'n' => {
870                let raw = buf[0..=i].to_vec();
871                return Some((
872                    InputEvent::DeviceControlReply {
873                        intermediates,
874                        params,
875                        final_byte: b,
876                        raw,
877                    },
878                    i + 1,
879                ));
880            },
881            0x40..=0x7E => {
882                // Final byte outside the whitelist — not ours.
883                return None;
884            },
885            _ => {
886                // Something unexpected inside the CSI — give up.
887                return None;
888            },
889        }
890    }
891    None
892}
893
894fn parse_osc(buf: &[u8]) -> Option<(InputEvent, usize)> {
895    // OSC sequences start with ESC ] (0x1b 0x5d)
896    if buf.get(0) != Some(&0x1b) || buf.get(1) != Some(&b']') {
897        return None;
898    }
899    let mut i = 2;
900    while i < buf.len() {
901        match buf.get(i) {
902            Some(&0x07) => {
903                // BEL terminator
904                let payload = buf.get(2..i).unwrap_or_default().to_vec();
905                return Some((InputEvent::OperatingSystemCommand(payload), i + 1));
906            },
907            Some(&0x1b) => {
908                // Possible ST terminator (ESC \)
909                if buf.get(i + 1) == Some(&b'\\') {
910                    let payload = buf.get(2..i).unwrap_or_default().to_vec();
911                    return Some((InputEvent::OperatingSystemCommand(payload), i + 2));
912                }
913                // Bare ESC inside OSC — malformed, but don't consume further
914                return None;
915            },
916            Some(_) => {
917                i += 1;
918            },
919            None => {
920                // Should not happen since i < buf.len(), but handle gracefully
921                return None;
922            },
923        }
924    }
925    None // incomplete — no terminator found yet
926}
927
928#[derive(Debug, Clone, Copy, PartialEq, Eq)]
929enum InputState {
930    Normal,
931    EscapeMaybeAlt,
932    Pasting(usize),
933}
934
935#[derive(Debug)]
936pub struct InputParser {
937    key_map: KeyMap<InputEvent>,
938    buf: ReadBuffer,
939    state: InputState,
940}
941
942#[cfg(windows)]
943mod windows {
944    use super::*;
945    use std;
946    use winapi::um::wincon::{
947        INPUT_RECORD, KEY_EVENT, KEY_EVENT_RECORD, MOUSE_EVENT, MOUSE_EVENT_RECORD,
948        WINDOW_BUFFER_SIZE_EVENT, WINDOW_BUFFER_SIZE_RECORD,
949    };
950    use winapi::um::winuser;
951
952    fn modifiers_from_ctrl_key_state(state: u32) -> Modifiers {
953        use winapi::um::wincon::*;
954
955        let mut mods = Modifiers::NONE;
956
957        if (state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0 {
958            mods |= Modifiers::ALT;
959        }
960
961        if (state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0 {
962            mods |= Modifiers::CTRL;
963        }
964
965        if (state & SHIFT_PRESSED) != 0 {
966            mods |= Modifiers::SHIFT;
967        }
968
969        mods
970    }
971
972    impl InputParser {
973        fn decode_key_record<F: FnMut(InputEvent)>(
974            &mut self,
975            event: &KEY_EVENT_RECORD,
976            callback: &mut F,
977        ) {
978            if event.bKeyDown == 0 {
979                return;
980            }
981
982            let key_code = match std::char::from_u32(*unsafe { event.uChar.UnicodeChar() } as u32) {
983                Some(unicode) if unicode > '\x00' => {
984                    let mut buf = [0u8; 4];
985                    self.buf
986                        .extend_with(unicode.encode_utf8(&mut buf).as_bytes());
987                    self.process_bytes(callback, true);
988                    return;
989                },
990                _ => match event.wVirtualKeyCode as i32 {
991                    winuser::VK_CANCEL => KeyCode::Cancel,
992                    winuser::VK_BACK => KeyCode::Backspace,
993                    winuser::VK_TAB => KeyCode::Tab,
994                    winuser::VK_CLEAR => KeyCode::Clear,
995                    winuser::VK_RETURN => KeyCode::Enter,
996                    winuser::VK_SHIFT => KeyCode::Shift,
997                    winuser::VK_CONTROL => KeyCode::Control,
998                    winuser::VK_MENU => KeyCode::Menu,
999                    winuser::VK_PAUSE => KeyCode::Pause,
1000                    winuser::VK_CAPITAL => KeyCode::CapsLock,
1001                    winuser::VK_ESCAPE => KeyCode::Escape,
1002                    winuser::VK_PRIOR => KeyCode::PageUp,
1003                    winuser::VK_NEXT => KeyCode::PageDown,
1004                    winuser::VK_END => KeyCode::End,
1005                    winuser::VK_HOME => KeyCode::Home,
1006                    winuser::VK_LEFT => KeyCode::LeftArrow,
1007                    winuser::VK_RIGHT => KeyCode::RightArrow,
1008                    winuser::VK_UP => KeyCode::UpArrow,
1009                    winuser::VK_DOWN => KeyCode::DownArrow,
1010                    winuser::VK_SELECT => KeyCode::Select,
1011                    winuser::VK_PRINT => KeyCode::Print,
1012                    winuser::VK_EXECUTE => KeyCode::Execute,
1013                    winuser::VK_SNAPSHOT => KeyCode::PrintScreen,
1014                    winuser::VK_INSERT => KeyCode::Insert,
1015                    winuser::VK_DELETE => KeyCode::Delete,
1016                    winuser::VK_HELP => KeyCode::Help,
1017                    winuser::VK_LWIN => KeyCode::LeftWindows,
1018                    winuser::VK_RWIN => KeyCode::RightWindows,
1019                    winuser::VK_APPS => KeyCode::Applications,
1020                    winuser::VK_SLEEP => KeyCode::Sleep,
1021                    winuser::VK_NUMPAD0 => KeyCode::Numpad0,
1022                    winuser::VK_NUMPAD1 => KeyCode::Numpad1,
1023                    winuser::VK_NUMPAD2 => KeyCode::Numpad2,
1024                    winuser::VK_NUMPAD3 => KeyCode::Numpad3,
1025                    winuser::VK_NUMPAD4 => KeyCode::Numpad4,
1026                    winuser::VK_NUMPAD5 => KeyCode::Numpad5,
1027                    winuser::VK_NUMPAD6 => KeyCode::Numpad6,
1028                    winuser::VK_NUMPAD7 => KeyCode::Numpad7,
1029                    winuser::VK_NUMPAD8 => KeyCode::Numpad8,
1030                    winuser::VK_NUMPAD9 => KeyCode::Numpad9,
1031                    winuser::VK_MULTIPLY => KeyCode::Multiply,
1032                    winuser::VK_ADD => KeyCode::Add,
1033                    winuser::VK_SEPARATOR => KeyCode::Separator,
1034                    winuser::VK_SUBTRACT => KeyCode::Subtract,
1035                    winuser::VK_DECIMAL => KeyCode::Decimal,
1036                    winuser::VK_DIVIDE => KeyCode::Divide,
1037                    winuser::VK_F1 => KeyCode::Function(1),
1038                    winuser::VK_F2 => KeyCode::Function(2),
1039                    winuser::VK_F3 => KeyCode::Function(3),
1040                    winuser::VK_F4 => KeyCode::Function(4),
1041                    winuser::VK_F5 => KeyCode::Function(5),
1042                    winuser::VK_F6 => KeyCode::Function(6),
1043                    winuser::VK_F7 => KeyCode::Function(7),
1044                    winuser::VK_F8 => KeyCode::Function(8),
1045                    winuser::VK_F9 => KeyCode::Function(9),
1046                    winuser::VK_F10 => KeyCode::Function(10),
1047                    winuser::VK_F11 => KeyCode::Function(11),
1048                    winuser::VK_F12 => KeyCode::Function(12),
1049                    winuser::VK_F13 => KeyCode::Function(13),
1050                    winuser::VK_F14 => KeyCode::Function(14),
1051                    winuser::VK_F15 => KeyCode::Function(15),
1052                    winuser::VK_F16 => KeyCode::Function(16),
1053                    winuser::VK_F17 => KeyCode::Function(17),
1054                    winuser::VK_F18 => KeyCode::Function(18),
1055                    winuser::VK_F19 => KeyCode::Function(19),
1056                    winuser::VK_F20 => KeyCode::Function(20),
1057                    winuser::VK_F21 => KeyCode::Function(21),
1058                    winuser::VK_F22 => KeyCode::Function(22),
1059                    winuser::VK_F23 => KeyCode::Function(23),
1060                    winuser::VK_F24 => KeyCode::Function(24),
1061                    winuser::VK_NUMLOCK => KeyCode::NumLock,
1062                    winuser::VK_SCROLL => KeyCode::ScrollLock,
1063                    winuser::VK_LSHIFT => KeyCode::LeftShift,
1064                    winuser::VK_RSHIFT => KeyCode::RightShift,
1065                    winuser::VK_LCONTROL => KeyCode::LeftControl,
1066                    winuser::VK_RCONTROL => KeyCode::RightControl,
1067                    winuser::VK_LMENU => KeyCode::LeftMenu,
1068                    winuser::VK_RMENU => KeyCode::RightMenu,
1069                    winuser::VK_BROWSER_BACK => KeyCode::BrowserBack,
1070                    winuser::VK_BROWSER_FORWARD => KeyCode::BrowserForward,
1071                    winuser::VK_BROWSER_REFRESH => KeyCode::BrowserRefresh,
1072                    winuser::VK_BROWSER_STOP => KeyCode::BrowserStop,
1073                    winuser::VK_BROWSER_SEARCH => KeyCode::BrowserSearch,
1074                    winuser::VK_BROWSER_FAVORITES => KeyCode::BrowserFavorites,
1075                    winuser::VK_BROWSER_HOME => KeyCode::BrowserHome,
1076                    winuser::VK_VOLUME_MUTE => KeyCode::VolumeMute,
1077                    winuser::VK_VOLUME_DOWN => KeyCode::VolumeDown,
1078                    winuser::VK_VOLUME_UP => KeyCode::VolumeUp,
1079                    winuser::VK_MEDIA_NEXT_TRACK => KeyCode::MediaNextTrack,
1080                    winuser::VK_MEDIA_PREV_TRACK => KeyCode::MediaPrevTrack,
1081                    winuser::VK_MEDIA_STOP => KeyCode::MediaStop,
1082                    winuser::VK_MEDIA_PLAY_PAUSE => KeyCode::MediaPlayPause,
1083                    _ => return,
1084                },
1085            };
1086            let mut modifiers = modifiers_from_ctrl_key_state(event.dwControlKeyState);
1087
1088            let key_code = key_code.normalize_shift_to_upper_case(modifiers);
1089            if let KeyCode::Char(c) = key_code {
1090                if c.is_ascii_uppercase() {
1091                    modifiers.remove(Modifiers::SHIFT);
1092                }
1093            }
1094
1095            let input_event = InputEvent::Key(KeyEvent {
1096                key: key_code,
1097                modifiers,
1098            });
1099            for _ in 0..event.wRepeatCount {
1100                callback(input_event.clone());
1101            }
1102        }
1103
1104        fn decode_mouse_record<F: FnMut(InputEvent)>(
1105            &self,
1106            event: &MOUSE_EVENT_RECORD,
1107            callback: &mut F,
1108        ) {
1109            use winapi::um::wincon::*;
1110            let mut buttons = MouseButtons::NONE;
1111
1112            if (event.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) != 0 {
1113                buttons |= MouseButtons::LEFT;
1114            }
1115            if (event.dwButtonState & RIGHTMOST_BUTTON_PRESSED) != 0 {
1116                buttons |= MouseButtons::RIGHT;
1117            }
1118            if (event.dwButtonState & FROM_LEFT_2ND_BUTTON_PRESSED) != 0 {
1119                buttons |= MouseButtons::MIDDLE;
1120            }
1121
1122            let modifiers = modifiers_from_ctrl_key_state(event.dwControlKeyState);
1123
1124            if (event.dwEventFlags & MOUSE_WHEELED) != 0 {
1125                buttons |= MouseButtons::VERT_WHEEL;
1126                if (event.dwButtonState >> 8) != 0 {
1127                    buttons |= MouseButtons::WHEEL_POSITIVE;
1128                }
1129            } else if (event.dwEventFlags & MOUSE_HWHEELED) != 0 {
1130                buttons |= MouseButtons::HORZ_WHEEL;
1131                if (event.dwButtonState >> 8) != 0 {
1132                    buttons |= MouseButtons::WHEEL_POSITIVE;
1133                }
1134            }
1135
1136            let mouse = InputEvent::Mouse(MouseEvent {
1137                x: event.dwMousePosition.X as u16,
1138                y: event.dwMousePosition.Y as u16,
1139                mouse_buttons: buttons,
1140                modifiers,
1141            });
1142
1143            if (event.dwEventFlags & DOUBLE_CLICK) != 0 {
1144                callback(mouse.clone());
1145            }
1146            callback(mouse);
1147        }
1148
1149        fn decode_resize_record<F: FnMut(InputEvent)>(
1150            &self,
1151            event: &WINDOW_BUFFER_SIZE_RECORD,
1152            callback: &mut F,
1153        ) {
1154            callback(InputEvent::Resized {
1155                rows: event.dwSize.Y as usize,
1156                cols: event.dwSize.X as usize,
1157            });
1158        }
1159
1160        pub fn decode_input_records<F: FnMut(InputEvent)>(
1161            &mut self,
1162            records: &[INPUT_RECORD],
1163            callback: &mut F,
1164        ) {
1165            for record in records {
1166                match record.EventType {
1167                    KEY_EVENT => {
1168                        self.decode_key_record(unsafe { record.Event.KeyEvent() }, callback)
1169                    },
1170                    MOUSE_EVENT => {
1171                        self.decode_mouse_record(unsafe { record.Event.MouseEvent() }, callback)
1172                    },
1173                    WINDOW_BUFFER_SIZE_EVENT => self.decode_resize_record(
1174                        unsafe { record.Event.WindowBufferSizeEvent() },
1175                        callback,
1176                    ),
1177                    _ => {},
1178                }
1179            }
1180            self.process_bytes(callback, false);
1181        }
1182    }
1183}
1184
1185impl Default for InputParser {
1186    fn default() -> Self {
1187        Self::new()
1188    }
1189}
1190
1191impl InputParser {
1192    pub fn new() -> Self {
1193        Self {
1194            key_map: Self::build_basic_key_map(),
1195            buf: ReadBuffer::new(),
1196            state: InputState::Normal,
1197        }
1198    }
1199
1200    fn build_basic_key_map() -> KeyMap<InputEvent> {
1201        let mut map = KeyMap::new();
1202
1203        let modifier_combos = &[
1204            ("", Modifiers::NONE),
1205            (";1", Modifiers::NONE),
1206            (";2", Modifiers::SHIFT),
1207            (";3", Modifiers::ALT),
1208            (";4", Modifiers::ALT | Modifiers::SHIFT),
1209            (";5", Modifiers::CTRL),
1210            (";6", Modifiers::CTRL | Modifiers::SHIFT),
1211            (";7", Modifiers::CTRL | Modifiers::ALT),
1212            (";8", Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT),
1213        ];
1214        let meta = Modifiers::ALT;
1215        let meta_modifier_combos = &[
1216            (";9", meta),
1217            (";10", meta | Modifiers::SHIFT),
1218            (";11", meta | Modifiers::ALT),
1219            (";12", meta | Modifiers::ALT | Modifiers::SHIFT),
1220            (";13", meta | Modifiers::CTRL),
1221            (";14", meta | Modifiers::CTRL | Modifiers::SHIFT),
1222            (";15", meta | Modifiers::CTRL | Modifiers::ALT),
1223            (
1224                ";16",
1225                meta | Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT,
1226            ),
1227        ];
1228
1229        let modifier_combos_including_meta =
1230            || modifier_combos.iter().chain(meta_modifier_combos.iter());
1231
1232        for alpha in b'A'..=b'Z' {
1233            // Ctrl-[A..=Z] are sent as 1..=26
1234            let ctrl = [alpha & 0x1f];
1235            map.insert(
1236                &ctrl,
1237                InputEvent::Key(KeyEvent {
1238                    key: KeyCode::Char((alpha as char).to_ascii_lowercase()),
1239                    modifiers: Modifiers::CTRL,
1240                }),
1241            );
1242
1243            // ALT A-Z is often sent with a leading ESC
1244            let alt = [0x1b, alpha];
1245            map.insert(
1246                &alt,
1247                InputEvent::Key(KeyEvent {
1248                    key: KeyCode::Char(alpha as char),
1249                    modifiers: Modifiers::ALT,
1250                }),
1251            );
1252        }
1253
1254        for c in 0..=0x7fu8 {
1255            for (suffix, modifiers) in modifier_combos {
1256                // `CSI u` encodings for the ascii range;
1257                // see http://www.leonerd.org.uk/hacks/fixterms/
1258                let key = format!("\x1b[{}{}u", c, suffix);
1259                map.insert(
1260                    key,
1261                    InputEvent::Key(KeyEvent {
1262                        key: KeyCode::Char(c as char),
1263                        modifiers: *modifiers,
1264                    }),
1265                );
1266
1267                if !suffix.is_empty() {
1268                    // xterm modifyOtherKeys sequences
1269                    let key = format!("\x1b[27{};{}~", suffix, c);
1270                    map.insert(
1271                        key,
1272                        InputEvent::Key(KeyEvent {
1273                            key: match c {
1274                                8 | 0x7f => KeyCode::Backspace,
1275                                0x1b => KeyCode::Escape,
1276                                9 => KeyCode::Tab,
1277                                10 | 13 => KeyCode::Enter,
1278                                _ => KeyCode::Char(c as char),
1279                            },
1280                            modifiers: *modifiers,
1281                        }),
1282                    );
1283                }
1284            }
1285        }
1286
1287        // Common arrow keys
1288        for (keycode, dir) in &[
1289            (KeyCode::UpArrow, b'A'),
1290            (KeyCode::DownArrow, b'B'),
1291            (KeyCode::RightArrow, b'C'),
1292            (KeyCode::LeftArrow, b'D'),
1293            (KeyCode::Home, b'H'),
1294            (KeyCode::End, b'F'),
1295        ] {
1296            // Arrow keys in normal mode encoded using CSI
1297            let arrow = [0x1b, b'[', *dir];
1298            map.insert(
1299                &arrow,
1300                InputEvent::Key(KeyEvent {
1301                    key: *keycode,
1302                    modifiers: Modifiers::NONE,
1303                }),
1304            );
1305            for (suffix, modifiers) in modifier_combos_including_meta() {
1306                let key = format!("\x1b[1{}{}", suffix, *dir as char);
1307                map.insert(
1308                    key,
1309                    InputEvent::Key(KeyEvent {
1310                        key: *keycode,
1311                        modifiers: *modifiers,
1312                    }),
1313                );
1314            }
1315        }
1316        for &(keycode, dir) in &[
1317            (KeyCode::UpArrow, b'a'),
1318            (KeyCode::DownArrow, b'b'),
1319            (KeyCode::RightArrow, b'c'),
1320            (KeyCode::LeftArrow, b'd'),
1321        ] {
1322            // rxvt-specific modified arrows.
1323            for &(seq, mods) in &[
1324                ([0x1b, b'[', dir], Modifiers::SHIFT),
1325                ([0x1b, b'O', dir], Modifiers::CTRL),
1326            ] {
1327                map.insert(
1328                    &seq,
1329                    InputEvent::Key(KeyEvent {
1330                        key: keycode,
1331                        modifiers: mods,
1332                    }),
1333                );
1334            }
1335        }
1336
1337        for (keycode, dir) in &[
1338            (KeyCode::ApplicationUpArrow, b'A'),
1339            (KeyCode::ApplicationDownArrow, b'B'),
1340            (KeyCode::ApplicationRightArrow, b'C'),
1341            (KeyCode::ApplicationLeftArrow, b'D'),
1342        ] {
1343            // Arrow keys in application cursor mode encoded using SS3
1344            let app = [0x1b, b'O', *dir];
1345            map.insert(
1346                &app,
1347                InputEvent::Key(KeyEvent {
1348                    key: *keycode,
1349                    modifiers: Modifiers::NONE,
1350                }),
1351            );
1352            for (suffix, modifiers) in modifier_combos {
1353                let key = format!("\x1bO1{}{}", suffix, *dir as char);
1354                map.insert(
1355                    key,
1356                    InputEvent::Key(KeyEvent {
1357                        key: *keycode,
1358                        modifiers: *modifiers,
1359                    }),
1360                );
1361            }
1362        }
1363
1364        // Function keys 1-4 with no modifiers encoded using SS3
1365        for (keycode, c) in &[
1366            (KeyCode::Function(1), b'P'),
1367            (KeyCode::Function(2), b'Q'),
1368            (KeyCode::Function(3), b'R'),
1369            (KeyCode::Function(4), b'S'),
1370        ] {
1371            let key = [0x1b, b'O', *c];
1372            map.insert(
1373                &key,
1374                InputEvent::Key(KeyEvent {
1375                    key: *keycode,
1376                    modifiers: Modifiers::NONE,
1377                }),
1378            );
1379        }
1380
1381        // Function keys 1-4 with modifiers
1382        for (keycode, c) in &[
1383            (KeyCode::Function(1), b'P'),
1384            (KeyCode::Function(2), b'Q'),
1385            (KeyCode::Function(3), b'R'),
1386            (KeyCode::Function(4), b'S'),
1387        ] {
1388            for (suffix, modifiers) in modifier_combos_including_meta() {
1389                let key = format!("\x1b[1{suffix}{code}", code = *c as char, suffix = suffix);
1390                map.insert(
1391                    key,
1392                    InputEvent::Key(KeyEvent {
1393                        key: *keycode,
1394                        modifiers: *modifiers,
1395                    }),
1396                );
1397            }
1398        }
1399
1400        // Function keys with modifiers encoded using CSI.
1401        // http://aperiodic.net/phil/archives/Geekery/term-function-keys.html
1402        for (range, offset) in &[
1403            // F1-F5 encoded as 11-15
1404            (1..=5, 10),
1405            // F6-F10 encoded as 17-21
1406            (6..=10, 11),
1407            // F11-F14 encoded as 23-26
1408            (11..=14, 12),
1409            // F15-F16 encoded as 28-29
1410            (15..=16, 13),
1411            // F17-F20 encoded as 31-34
1412            (17..=20, 14),
1413        ] {
1414            for n in range.clone() {
1415                for (suffix, modifiers) in modifier_combos_including_meta() {
1416                    let key = format!("\x1b[{code}{suffix}~", code = n + offset, suffix = suffix);
1417                    map.insert(
1418                        key,
1419                        InputEvent::Key(KeyEvent {
1420                            key: KeyCode::Function(n),
1421                            modifiers: *modifiers,
1422                        }),
1423                    );
1424                }
1425            }
1426        }
1427
1428        for (keycode, c) in &[
1429            (KeyCode::Insert, b'2'),
1430            (KeyCode::Delete, b'3'),
1431            (KeyCode::Home, b'1'),
1432            (KeyCode::End, b'4'),
1433            (KeyCode::PageUp, b'5'),
1434            (KeyCode::PageDown, b'6'),
1435            // rxvt
1436            (KeyCode::Home, b'7'),
1437            (KeyCode::End, b'8'),
1438        ] {
1439            for (suffix, modifiers) in &[
1440                (b'~', Modifiers::NONE),
1441                (b'$', Modifiers::SHIFT),
1442                (b'^', Modifiers::CTRL),
1443                (b'@', Modifiers::SHIFT | Modifiers::CTRL),
1444            ] {
1445                let key = [0x1b, b'[', *c, *suffix];
1446                map.insert(
1447                    key,
1448                    InputEvent::Key(KeyEvent {
1449                        key: *keycode,
1450                        modifiers: *modifiers,
1451                    }),
1452                );
1453            }
1454        }
1455
1456        map.insert(
1457            &[0x7f],
1458            InputEvent::Key(KeyEvent {
1459                key: KeyCode::Backspace,
1460                modifiers: Modifiers::NONE,
1461            }),
1462        );
1463
1464        map.insert(
1465            &[0x8],
1466            InputEvent::Key(KeyEvent {
1467                key: KeyCode::Backspace,
1468                modifiers: Modifiers::NONE,
1469            }),
1470        );
1471
1472        map.insert(
1473            &[0x1b],
1474            InputEvent::Key(KeyEvent {
1475                key: KeyCode::Escape,
1476                modifiers: Modifiers::NONE,
1477            }),
1478        );
1479
1480        map.insert(
1481            &[b'\t'],
1482            InputEvent::Key(KeyEvent {
1483                key: KeyCode::Tab,
1484                modifiers: Modifiers::NONE,
1485            }),
1486        );
1487        map.insert(
1488            b"\x1b[Z",
1489            InputEvent::Key(KeyEvent {
1490                key: KeyCode::Tab,
1491                modifiers: Modifiers::SHIFT,
1492            }),
1493        );
1494
1495        map.insert(
1496            &[b'\r'],
1497            InputEvent::Key(KeyEvent {
1498                key: KeyCode::Enter,
1499                modifiers: Modifiers::NONE,
1500            }),
1501        );
1502        map.insert(
1503            &[b'\n'],
1504            InputEvent::Key(KeyEvent {
1505                key: KeyCode::Enter,
1506                modifiers: Modifiers::NONE,
1507            }),
1508        );
1509
1510        map.insert(
1511            b"\x1b[200~",
1512            InputEvent::Key(KeyEvent {
1513                key: KeyCode::InternalPasteStart,
1514                modifiers: Modifiers::NONE,
1515            }),
1516        );
1517        map.insert(
1518            b"\x1b[201~",
1519            InputEvent::Key(KeyEvent {
1520                key: KeyCode::InternalPasteEnd,
1521                modifiers: Modifiers::NONE,
1522            }),
1523        );
1524        map.insert(
1525            b"\x1b[",
1526            InputEvent::Key(KeyEvent {
1527                key: KeyCode::Char('['),
1528                modifiers: Modifiers::ALT,
1529            }),
1530        );
1531
1532        map
1533    }
1534
1535    /// Returns the first char from a str and the length of that char
1536    /// in *bytes*.
1537    fn first_char_and_len(s: &str) -> (char, usize) {
1538        let mut iter = s.chars();
1539        let c = iter.next().unwrap();
1540        (c, c.len_utf8())
1541    }
1542
1543    /// This is a horrible function to pull off the first unicode character
1544    /// from the sequence of bytes and return it and the remaining slice.
1545    fn decode_one_char(bytes: &[u8]) -> Option<(char, usize)> {
1546        let bytes = &bytes[..bytes.len().min(4)];
1547        match std::str::from_utf8(bytes) {
1548            Ok(s) => {
1549                let (c, len) = Self::first_char_and_len(s);
1550                Some((c, len))
1551            },
1552            Err(err) => {
1553                let (valid, _after_valid) = bytes.split_at(err.valid_up_to());
1554                if !valid.is_empty() {
1555                    let s = unsafe { std::str::from_utf8_unchecked(valid) };
1556                    let (c, len) = Self::first_char_and_len(s);
1557                    Some((c, len))
1558                } else {
1559                    None
1560                }
1561            },
1562        }
1563    }
1564
1565    fn dispatch_callback<F: FnMut(InputEvent)>(&mut self, mut callback: F, event: InputEvent) {
1566        match (self.state, &event) {
1567            (
1568                InputState::Normal,
1569                InputEvent::Key(KeyEvent {
1570                    key: KeyCode::InternalPasteStart,
1571                    ..
1572                }),
1573            ) => {
1574                self.state = InputState::Pasting(0);
1575            },
1576            (
1577                InputState::EscapeMaybeAlt,
1578                InputEvent::Key(KeyEvent {
1579                    key: KeyCode::InternalPasteStart,
1580                    ..
1581                }),
1582            ) => {
1583                // The prior ESC was not part of an ALT sequence, so emit
1584                // it before we start collecting for paste.
1585                callback(InputEvent::Key(KeyEvent {
1586                    key: KeyCode::Escape,
1587                    modifiers: Modifiers::NONE,
1588                }));
1589                self.state = InputState::Pasting(0);
1590            },
1591            (InputState::EscapeMaybeAlt, InputEvent::Key(KeyEvent { key, modifiers })) => {
1592                // Treat this as ALT-key
1593                let key = *key;
1594                let modifiers = *modifiers;
1595                self.state = InputState::Normal;
1596                callback(InputEvent::Key(KeyEvent {
1597                    key,
1598                    modifiers: modifiers | Modifiers::ALT,
1599                }));
1600            },
1601            (InputState::EscapeMaybeAlt, _) => {
1602                // The prior ESC was not part of an ALT sequence, so emit
1603                // both it and the current event
1604                callback(InputEvent::Key(KeyEvent {
1605                    key: KeyCode::Escape,
1606                    modifiers: Modifiers::NONE,
1607                }));
1608                callback(event);
1609            },
1610            (_, _) => callback(event),
1611        }
1612    }
1613
1614    fn process_bytes<F: FnMut(InputEvent)>(&mut self, mut callback: F, maybe_more: bool) {
1615        while !self.buf.is_empty() {
1616            match self.state {
1617                InputState::Pasting(offset) => {
1618                    let end_paste = b"\x1b[201~";
1619                    if let Some(idx) = self.buf.find_subsequence(offset, end_paste) {
1620                        let pasted =
1621                            String::from_utf8_lossy(&self.buf.as_slice()[0..idx]).to_string();
1622                        self.buf.advance(pasted.len() + end_paste.len());
1623                        callback(InputEvent::Paste(pasted));
1624                        self.state = InputState::Normal;
1625                    } else {
1626                        self.state =
1627                            InputState::Pasting(self.buf.len().saturating_sub(end_paste.len()));
1628                        return;
1629                    }
1630                },
1631                InputState::EscapeMaybeAlt | InputState::Normal => {
1632                    if self.state == InputState::Normal
1633                        && self.buf.as_slice().get(0) == Some(&b'\x1b')
1634                    {
1635                        if let Some((event, len)) = parse_sgr_mouse(self.buf.as_slice()) {
1636                            self.buf.advance(len);
1637                            callback(event);
1638                            continue;
1639                        }
1640
1641                        // OSC sequence check — must come before the incomplete-SGR-mouse early return
1642                        if let Some((event, len)) = parse_osc(self.buf.as_slice()) {
1643                            self.buf.advance(len);
1644                            callback(event);
1645                            continue;
1646                        }
1647
1648                        // Incomplete OSC — buffer and wait for more data
1649                        if maybe_more && self.buf.as_slice().starts_with(b"\x1b]") {
1650                            return;
1651                        }
1652
1653                        if maybe_more && self.buf.as_slice().starts_with(b"\x1b[<") {
1654                            return;
1655                        }
1656
1657                        // CSI-based host-terminal report (pixel-dims reply,
1658                        // DECRPM, DSR, Primary-DA). Must come before the
1659                        // regular CSI key-mapping machinery, which would
1660                        // otherwise match "\x1b[" as an escape prefix and
1661                        // pass the bytes through as keyboard input.
1662                        if let Some((event, len)) = parse_csi_report(self.buf.as_slice()) {
1663                            self.buf.advance(len);
1664                            callback(event);
1665                            continue;
1666                        }
1667
1668                        // Incomplete CSI ?... report (DECRPM, DSR 997, etc.) —
1669                        // wait for more data so the report-classification path
1670                        // can match the full sequence rather than letting the
1671                        // keymap dispatch the leading bytes as separate keys.
1672                        if maybe_more && self.buf.as_slice().starts_with(b"\x1b[?") {
1673                            return;
1674                        }
1675                    }
1676
1677                    match (
1678                        self.key_map.lookup(self.buf.as_slice(), maybe_more),
1679                        maybe_more,
1680                    ) {
1681                        // If we got an unambiguous ESC and we have more data to
1682                        // follow, then this is likely the Meta version of the
1683                        // following keypress.  Buffer up the escape key and
1684                        // consume it from the input.  dispatch_callback() will
1685                        // emit either the ESC or the ALT modified following key.
1686                        (
1687                            Found::Exact(
1688                                len,
1689                                InputEvent::Key(KeyEvent {
1690                                    key: KeyCode::Escape,
1691                                    modifiers: Modifiers::NONE,
1692                                }),
1693                            ),
1694                            _,
1695                        ) if self.state == InputState::Normal && self.buf.len() > len => {
1696                            self.state = InputState::EscapeMaybeAlt;
1697                            self.buf.advance(len);
1698                        },
1699                        (Found::Exact(len, event), _) | (Found::Ambiguous(len, event), false) => {
1700                            self.dispatch_callback(&mut callback, event.clone());
1701                            self.buf.advance(len);
1702                        },
1703                        (Found::Ambiguous(_, _), true) | (Found::NeedData, true) => {
1704                            return;
1705                        },
1706                        (Found::None, _) | (Found::NeedData, false) => {
1707                            // No pre-defined key, so pull out a unicode character
1708                            if let Some((c, len)) = Self::decode_one_char(self.buf.as_slice()) {
1709                                self.buf.advance(len);
1710                                self.dispatch_callback(
1711                                    &mut callback,
1712                                    InputEvent::Key(KeyEvent {
1713                                        key: KeyCode::Char(c),
1714                                        modifiers: Modifiers::NONE,
1715                                    }),
1716                                );
1717                            } else {
1718                                // We need more data to recognize the input, so
1719                                // yield the remainder of the slice
1720                                return;
1721                            }
1722                        },
1723                    }
1724                },
1725            }
1726        }
1727    }
1728
1729    /// Push a sequence of bytes into the parser.
1730    /// Each time input is recognized, the provided `callback` will be passed
1731    /// the decoded `InputEvent`.
1732    /// If not enough data are available to fully decode a sequence, the
1733    /// remaining data will be buffered until the next call.
1734    /// The `maybe_more` flag controls how ambiguous partial sequences are
1735    /// handled. The intent is that `maybe_more` should be set to true if
1736    /// you believe that you will be able to provide more data momentarily.
1737    /// This will cause the parser to defer judgement on partial prefix
1738    /// matches. You should attempt to read and pass the new data in
1739    /// immediately afterwards. If you have attempted a read and no data is
1740    /// immediately available, you should follow up with a call to parse
1741    /// with an empty slice and `maybe_more=false` to allow the partial
1742    /// data to be recognized and processed.
1743    pub fn parse<F: FnMut(InputEvent)>(&mut self, bytes: &[u8], callback: F, maybe_more: bool) {
1744        self.buf.extend_with(bytes);
1745        self.process_bytes(callback, maybe_more);
1746    }
1747
1748    pub fn parse_as_vec(&mut self, bytes: &[u8], maybe_more: bool) -> Vec<InputEvent> {
1749        let mut result = Vec::new();
1750        self.parse(bytes, |event| result.push(event), maybe_more);
1751        result
1752    }
1753
1754    #[cfg(windows)]
1755    pub fn decode_input_records_as_vec(
1756        &mut self,
1757        records: &[winapi::um::wincon::INPUT_RECORD],
1758    ) -> Vec<InputEvent> {
1759        let mut result = Vec::new();
1760        self.decode_input_records(records, &mut |event| result.push(event));
1761        result
1762    }
1763}
1764
1765#[cfg(test)]
1766mod test {
1767    use super::*;
1768
1769    const NO_MORE: bool = false;
1770    const MAYBE_MORE: bool = true;
1771
1772    #[test]
1773    fn simple() {
1774        let mut p = InputParser::new();
1775        let inputs = p.parse_as_vec(b"hello", NO_MORE);
1776        assert_eq!(
1777            vec![
1778                InputEvent::Key(KeyEvent {
1779                    modifiers: Modifiers::NONE,
1780                    key: KeyCode::Char('h'),
1781                }),
1782                InputEvent::Key(KeyEvent {
1783                    modifiers: Modifiers::NONE,
1784                    key: KeyCode::Char('e'),
1785                }),
1786                InputEvent::Key(KeyEvent {
1787                    modifiers: Modifiers::NONE,
1788                    key: KeyCode::Char('l'),
1789                }),
1790                InputEvent::Key(KeyEvent {
1791                    modifiers: Modifiers::NONE,
1792                    key: KeyCode::Char('l'),
1793                }),
1794                InputEvent::Key(KeyEvent {
1795                    modifiers: Modifiers::NONE,
1796                    key: KeyCode::Char('o'),
1797                }),
1798            ],
1799            inputs
1800        );
1801    }
1802
1803    #[test]
1804    fn control_characters() {
1805        let mut p = InputParser::new();
1806        let inputs = p.parse_as_vec(b"\x03\x1bJ\x7f", NO_MORE);
1807        assert_eq!(
1808            vec![
1809                InputEvent::Key(KeyEvent {
1810                    modifiers: Modifiers::CTRL,
1811                    key: KeyCode::Char('c'),
1812                }),
1813                InputEvent::Key(KeyEvent {
1814                    modifiers: Modifiers::ALT,
1815                    key: KeyCode::Char('J'),
1816                }),
1817                InputEvent::Key(KeyEvent {
1818                    modifiers: Modifiers::NONE,
1819                    key: KeyCode::Backspace,
1820                }),
1821            ],
1822            inputs
1823        );
1824    }
1825
1826    #[test]
1827    fn arrow_keys() {
1828        let mut p = InputParser::new();
1829        let inputs = p.parse_as_vec(b"\x1bOA\x1bOB\x1bOC\x1bOD", NO_MORE);
1830        assert_eq!(
1831            vec![
1832                InputEvent::Key(KeyEvent {
1833                    modifiers: Modifiers::NONE,
1834                    key: KeyCode::ApplicationUpArrow,
1835                }),
1836                InputEvent::Key(KeyEvent {
1837                    modifiers: Modifiers::NONE,
1838                    key: KeyCode::ApplicationDownArrow,
1839                }),
1840                InputEvent::Key(KeyEvent {
1841                    modifiers: Modifiers::NONE,
1842                    key: KeyCode::ApplicationRightArrow,
1843                }),
1844                InputEvent::Key(KeyEvent {
1845                    modifiers: Modifiers::NONE,
1846                    key: KeyCode::ApplicationLeftArrow,
1847                }),
1848            ],
1849            inputs
1850        );
1851    }
1852
1853    #[test]
1854    fn partial() {
1855        let mut p = InputParser::new();
1856        let mut inputs = Vec::new();
1857        // Fragment this F-key sequence across two different pushes
1858        p.parse(b"\x1b[11", |evt| inputs.push(evt), true);
1859        p.parse(b"~", |evt| inputs.push(evt), true);
1860        // make sure we recognize it as just the F-key
1861        assert_eq!(
1862            vec![InputEvent::Key(KeyEvent {
1863                modifiers: Modifiers::NONE,
1864                key: KeyCode::Function(1),
1865            })],
1866            inputs
1867        );
1868    }
1869
1870    #[test]
1871    fn partial_ambig() {
1872        let mut p = InputParser::new();
1873
1874        assert_eq!(
1875            vec![InputEvent::Key(KeyEvent {
1876                key: KeyCode::Escape,
1877                modifiers: Modifiers::NONE,
1878            })],
1879            p.parse_as_vec(b"\x1b", false)
1880        );
1881
1882        let mut inputs = Vec::new();
1883        // An incomplete F-key sequence fragmented across two different pushes
1884        p.parse(b"\x1b[11", |evt| inputs.push(evt), MAYBE_MORE);
1885        p.parse(b"", |evt| inputs.push(evt), NO_MORE);
1886        // since we finish with maybe_more false (NO_MORE), the results should be the longest matching
1887        // parts of said f-key sequence
1888        assert_eq!(
1889            vec![
1890                InputEvent::Key(KeyEvent {
1891                    modifiers: Modifiers::ALT,
1892                    key: KeyCode::Char('['),
1893                }),
1894                InputEvent::Key(KeyEvent {
1895                    modifiers: Modifiers::NONE,
1896                    key: KeyCode::Char('1'),
1897                }),
1898                InputEvent::Key(KeyEvent {
1899                    modifiers: Modifiers::NONE,
1900                    key: KeyCode::Char('1'),
1901                }),
1902            ],
1903            inputs
1904        );
1905    }
1906
1907    #[test]
1908    fn partial_mouse() {
1909        let mut p = InputParser::new();
1910        let mut inputs = Vec::new();
1911        // Fragment this mouse sequence across two different pushes
1912        p.parse(b"\x1b[<0;0;0", |evt| inputs.push(evt), true);
1913        p.parse(b"M", |evt| inputs.push(evt), true);
1914        // make sure we recognize it as just the mouse event
1915        assert_eq!(
1916            vec![InputEvent::Mouse(MouseEvent {
1917                x: 0,
1918                y: 0,
1919                mouse_buttons: MouseButtons::LEFT,
1920                modifiers: Modifiers::NONE,
1921            })],
1922            inputs
1923        );
1924    }
1925
1926    #[test]
1927    fn partial_mouse_ambig() {
1928        let mut p = InputParser::new();
1929        let mut inputs = Vec::new();
1930        // Fragment this mouse sequence across two different pushes
1931        p.parse(b"\x1b[<", |evt| inputs.push(evt), MAYBE_MORE);
1932        p.parse(b"0;0;0", |evt| inputs.push(evt), NO_MORE);
1933        // since we finish with maybe_more false (NO_MORE), the results should be the longest matching
1934        // parts of said mouse sequence
1935        assert_eq!(
1936            vec![
1937                InputEvent::Key(KeyEvent {
1938                    modifiers: Modifiers::ALT,
1939                    key: KeyCode::Char('['),
1940                }),
1941                InputEvent::Key(KeyEvent {
1942                    modifiers: Modifiers::NONE,
1943                    key: KeyCode::Char('<'),
1944                }),
1945                InputEvent::Key(KeyEvent {
1946                    modifiers: Modifiers::NONE,
1947                    key: KeyCode::Char('0'),
1948                }),
1949                InputEvent::Key(KeyEvent {
1950                    modifiers: Modifiers::NONE,
1951                    key: KeyCode::Char(';'),
1952                }),
1953                InputEvent::Key(KeyEvent {
1954                    modifiers: Modifiers::NONE,
1955                    key: KeyCode::Char('0'),
1956                }),
1957                InputEvent::Key(KeyEvent {
1958                    modifiers: Modifiers::NONE,
1959                    key: KeyCode::Char(';'),
1960                }),
1961                InputEvent::Key(KeyEvent {
1962                    modifiers: Modifiers::NONE,
1963                    key: KeyCode::Char('0'),
1964                }),
1965            ],
1966            inputs
1967        );
1968    }
1969
1970    #[test]
1971    fn alt_left_bracket() {
1972        // tests that `Alt` + `[` is recognized as a single
1973        // event rather than two events (one `Esc` the second `Char('[')`)
1974        let mut p = InputParser::new();
1975
1976        let mut inputs = Vec::new();
1977        p.parse(b"\x1b[", |evt| inputs.push(evt), false);
1978
1979        assert_eq!(
1980            vec![InputEvent::Key(KeyEvent {
1981                modifiers: Modifiers::ALT,
1982                key: KeyCode::Char('['),
1983            }),],
1984            inputs
1985        );
1986    }
1987
1988    #[test]
1989    fn modify_other_keys_parse() {
1990        let mut p = InputParser::new();
1991        let inputs = p.parse_as_vec(
1992            b"\x1b[27;5;13~\x1b[27;5;9~\x1b[27;6;8~\x1b[27;2;127~\x1b[27;6;27~",
1993            NO_MORE,
1994        );
1995        assert_eq!(
1996            vec![
1997                InputEvent::Key(KeyEvent {
1998                    key: KeyCode::Enter,
1999                    modifiers: Modifiers::CTRL,
2000                }),
2001                InputEvent::Key(KeyEvent {
2002                    key: KeyCode::Tab,
2003                    modifiers: Modifiers::CTRL,
2004                }),
2005                InputEvent::Key(KeyEvent {
2006                    key: KeyCode::Backspace,
2007                    modifiers: Modifiers::CTRL | Modifiers::SHIFT,
2008                }),
2009                InputEvent::Key(KeyEvent {
2010                    key: KeyCode::Backspace,
2011                    modifiers: Modifiers::SHIFT,
2012                }),
2013                InputEvent::Key(KeyEvent {
2014                    key: KeyCode::Escape,
2015                    modifiers: Modifiers::CTRL | Modifiers::SHIFT,
2016                }),
2017            ],
2018            inputs
2019        );
2020    }
2021
2022    #[test]
2023    fn modify_other_keys_encode() {
2024        let mode = KeyCodeEncodeModes {
2025            encoding: KeyboardEncoding::Xterm,
2026            newline_mode: false,
2027            application_cursor_keys: false,
2028            modify_other_keys: None,
2029        };
2030        let mode_1 = KeyCodeEncodeModes {
2031            encoding: KeyboardEncoding::Xterm,
2032            newline_mode: false,
2033            application_cursor_keys: false,
2034            modify_other_keys: Some(1),
2035        };
2036        let mode_2 = KeyCodeEncodeModes {
2037            encoding: KeyboardEncoding::Xterm,
2038            newline_mode: false,
2039            application_cursor_keys: false,
2040            modify_other_keys: Some(2),
2041        };
2042
2043        assert_eq!(
2044            KeyCode::Enter.encode(Modifiers::CTRL, mode, true).unwrap(),
2045            "\r".to_string()
2046        );
2047        assert_eq!(
2048            KeyCode::Enter
2049                .encode(Modifiers::CTRL, mode_1, true)
2050                .unwrap(),
2051            "\x1b[27;5;13~".to_string()
2052        );
2053        assert_eq!(
2054            KeyCode::Enter
2055                .encode(Modifiers::CTRL | Modifiers::SHIFT, mode_1, true)
2056                .unwrap(),
2057            "\x1b[27;6;13~".to_string()
2058        );
2059
2060        // This case is not conformant with xterm!
2061        // xterm just returns tab for CTRL-Tab when modify_other_keys
2062        // is not set.
2063        assert_eq!(
2064            KeyCode::Tab.encode(Modifiers::CTRL, mode, true).unwrap(),
2065            "\x1b[9;5u".to_string()
2066        );
2067        assert_eq!(
2068            KeyCode::Tab.encode(Modifiers::CTRL, mode_1, true).unwrap(),
2069            "\x1b[27;5;9~".to_string()
2070        );
2071        assert_eq!(
2072            KeyCode::Tab
2073                .encode(Modifiers::CTRL | Modifiers::SHIFT, mode_1, true)
2074                .unwrap(),
2075            "\x1b[27;6;9~".to_string()
2076        );
2077
2078        assert_eq!(
2079            KeyCode::Char('c')
2080                .encode(Modifiers::CTRL, mode, true)
2081                .unwrap(),
2082            "\x03".to_string()
2083        );
2084        assert_eq!(
2085            KeyCode::Char('c')
2086                .encode(Modifiers::CTRL, mode_1, true)
2087                .unwrap(),
2088            "\x03".to_string()
2089        );
2090        assert_eq!(
2091            KeyCode::Char('c')
2092                .encode(Modifiers::CTRL, mode_2, true)
2093                .unwrap(),
2094            "\x1b[27;5;99~".to_string()
2095        );
2096
2097        assert_eq!(
2098            KeyCode::Char('1')
2099                .encode(Modifiers::CTRL, mode, true)
2100                .unwrap(),
2101            "1".to_string()
2102        );
2103        assert_eq!(
2104            KeyCode::Char('1')
2105                .encode(Modifiers::CTRL, mode_2, true)
2106                .unwrap(),
2107            "\x1b[27;5;49~".to_string()
2108        );
2109
2110        assert_eq!(
2111            KeyCode::Char(',')
2112                .encode(Modifiers::CTRL, mode, true)
2113                .unwrap(),
2114            ",".to_string()
2115        );
2116        assert_eq!(
2117            KeyCode::Char(',')
2118                .encode(Modifiers::CTRL, mode_2, true)
2119                .unwrap(),
2120            "\x1b[27;5;44~".to_string()
2121        );
2122    }
2123
2124    #[test]
2125    fn encode_issue_892() {
2126        let mode = KeyCodeEncodeModes {
2127            encoding: KeyboardEncoding::Xterm,
2128            newline_mode: false,
2129            application_cursor_keys: false,
2130            modify_other_keys: None,
2131        };
2132
2133        assert_eq!(
2134            KeyCode::LeftArrow
2135                .encode(Modifiers::NONE, mode, true)
2136                .unwrap(),
2137            "\x1b[D".to_string()
2138        );
2139        assert_eq!(
2140            KeyCode::LeftArrow
2141                .encode(Modifiers::ALT, mode, true)
2142                .unwrap(),
2143            "\x1b[1;3D".to_string()
2144        );
2145        assert_eq!(
2146            KeyCode::Home.encode(Modifiers::NONE, mode, true).unwrap(),
2147            "\x1b[H".to_string()
2148        );
2149        assert_eq!(
2150            KeyCode::Home.encode(Modifiers::ALT, mode, true).unwrap(),
2151            "\x1b[1;3H".to_string()
2152        );
2153        assert_eq!(
2154            KeyCode::End.encode(Modifiers::NONE, mode, true).unwrap(),
2155            "\x1b[F".to_string()
2156        );
2157        assert_eq!(
2158            KeyCode::End.encode(Modifiers::ALT, mode, true).unwrap(),
2159            "\x1b[1;3F".to_string()
2160        );
2161        assert_eq!(
2162            KeyCode::Tab.encode(Modifiers::ALT, mode, true).unwrap(),
2163            "\x1b\t".to_string()
2164        );
2165        assert_eq!(
2166            KeyCode::PageUp.encode(Modifiers::ALT, mode, true).unwrap(),
2167            "\x1b[5;3~".to_string()
2168        );
2169        assert_eq!(
2170            KeyCode::Function(1)
2171                .encode(Modifiers::NONE, mode, true)
2172                .unwrap(),
2173            "\x1bOP".to_string()
2174        );
2175    }
2176
2177    #[test]
2178    fn partial_bracketed_paste() {
2179        let mut p = InputParser::new();
2180
2181        let input = b"\x1b[200~1234";
2182        let input2 = b"5678\x1b[201~";
2183
2184        let mut inputs = vec![];
2185
2186        p.parse(input, |e| inputs.push(e), false);
2187        p.parse(input2, |e| inputs.push(e), false);
2188
2189        assert_eq!(vec![InputEvent::Paste("12345678".to_owned())], inputs)
2190    }
2191
2192    #[test]
2193    fn mouse_horizontal_scroll() {
2194        let mut p = InputParser::new();
2195
2196        let input = b"\x1b[<66;42;12M\x1b[<67;42;12M";
2197        let res = p.parse_as_vec(input, MAYBE_MORE);
2198
2199        assert_eq!(
2200            vec![
2201                InputEvent::Mouse(MouseEvent {
2202                    x: 42,
2203                    y: 12,
2204                    mouse_buttons: MouseButtons::HORZ_WHEEL | MouseButtons::WHEEL_POSITIVE,
2205                    modifiers: Modifiers::NONE,
2206                }),
2207                InputEvent::Mouse(MouseEvent {
2208                    x: 42,
2209                    y: 12,
2210                    mouse_buttons: MouseButtons::HORZ_WHEEL,
2211                    modifiers: Modifiers::NONE,
2212                })
2213            ],
2214            res
2215        );
2216    }
2217
2218    #[test]
2219    fn encode_issue_3478_xterm() {
2220        let mode = KeyCodeEncodeModes {
2221            encoding: KeyboardEncoding::Xterm,
2222            newline_mode: false,
2223            application_cursor_keys: false,
2224            modify_other_keys: None,
2225        };
2226
2227        assert_eq!(
2228            KeyCode::Numpad0
2229                .encode(Modifiers::NONE, mode, true)
2230                .unwrap(),
2231            "\u{1b}[2~".to_string()
2232        );
2233        assert_eq!(
2234            KeyCode::Numpad0
2235                .encode(Modifiers::SHIFT, mode, true)
2236                .unwrap(),
2237            "\u{1b}[2;2~".to_string()
2238        );
2239
2240        assert_eq!(
2241            KeyCode::Numpad1
2242                .encode(Modifiers::NONE, mode, true)
2243                .unwrap(),
2244            "\u{1b}[F".to_string()
2245        );
2246        assert_eq!(
2247            KeyCode::Numpad1
2248                .encode(Modifiers::NONE | Modifiers::SHIFT, mode, true)
2249                .unwrap(),
2250            "\u{1b}[1;2F".to_string()
2251        );
2252    }
2253
2254    #[test]
2255    fn encode_tab_with_modifiers() {
2256        let mode = KeyCodeEncodeModes {
2257            encoding: KeyboardEncoding::Xterm,
2258            newline_mode: false,
2259            application_cursor_keys: false,
2260            modify_other_keys: None,
2261        };
2262
2263        let mods_to_result = [
2264            (Modifiers::SHIFT, "\u{1b}[Z"),
2265            (Modifiers::SHIFT | Modifiers::LEFT_SHIFT, "\u{1b}[Z"),
2266            (Modifiers::SHIFT | Modifiers::RIGHT_SHIFT, "\u{1b}[Z"),
2267            (Modifiers::CTRL, "\u{1b}[9;5u"),
2268            (Modifiers::CTRL | Modifiers::LEFT_CTRL, "\u{1b}[9;5u"),
2269            (Modifiers::CTRL | Modifiers::RIGHT_CTRL, "\u{1b}[9;5u"),
2270            (
2271                Modifiers::SHIFT | Modifiers::CTRL | Modifiers::LEFT_CTRL | Modifiers::LEFT_SHIFT,
2272                "\u{1b}[1;5Z",
2273            ),
2274        ];
2275        for (mods, result) in mods_to_result {
2276            assert_eq!(
2277                KeyCode::Tab.encode(mods, mode, true).unwrap(),
2278                result,
2279                "{:?}",
2280                mods
2281            );
2282        }
2283    }
2284
2285    #[test]
2286    fn mouse_button1_press() {
2287        let mut p = InputParser::new();
2288        let res = p.parse_as_vec(b"\x1b[<0;42;12M", true);
2289        assert_eq!(
2290            res,
2291            vec![InputEvent::Mouse(MouseEvent {
2292                x: 42,
2293                y: 12,
2294                mouse_buttons: MouseButtons::LEFT,
2295                modifiers: Modifiers::NONE,
2296            })]
2297        );
2298    }
2299
2300    #[test]
2301    fn mouse_button1_release() {
2302        let mut p = InputParser::new();
2303        let res = p.parse_as_vec(b"\x1b[<0;42;12m", true);
2304        assert_eq!(
2305            res,
2306            vec![InputEvent::Mouse(MouseEvent {
2307                x: 42,
2308                y: 12,
2309                mouse_buttons: MouseButtons::NONE,
2310                modifiers: Modifiers::NONE,
2311            })]
2312        );
2313    }
2314
2315    #[test]
2316    fn mouse_button3_with_shift() {
2317        let mut p = InputParser::new();
2318        // button 2 (right) = 2, SHIFT adds 4 to p0 -> 6
2319        let res = p.parse_as_vec(b"\x1b[<6;10;20M", true);
2320        assert_eq!(
2321            res,
2322            vec![InputEvent::Mouse(MouseEvent {
2323                x: 10,
2324                y: 20,
2325                mouse_buttons: MouseButtons::RIGHT,
2326                modifiers: Modifiers::SHIFT,
2327            })]
2328        );
2329    }
2330
2331    #[test]
2332    fn mouse_drag() {
2333        let mut p = InputParser::new();
2334        // button1 drag = 32
2335        let res = p.parse_as_vec(b"\x1b[<32;5;5M", true);
2336        assert_eq!(
2337            res,
2338            vec![InputEvent::Mouse(MouseEvent {
2339                x: 5,
2340                y: 5,
2341                mouse_buttons: MouseButtons::LEFT,
2342                modifiers: Modifiers::NONE,
2343            })]
2344        );
2345    }
2346
2347    #[test]
2348    fn mouse_vertical_scroll_up() {
2349        let mut p = InputParser::new();
2350        // button4 press = 64
2351        let res = p.parse_as_vec(b"\x1b[<64;1;1M", true);
2352        assert_eq!(
2353            res,
2354            vec![InputEvent::Mouse(MouseEvent {
2355                x: 1,
2356                y: 1,
2357                mouse_buttons: MouseButtons::VERT_WHEEL | MouseButtons::WHEEL_POSITIVE,
2358                modifiers: Modifiers::NONE,
2359            })]
2360        );
2361    }
2362
2363    #[test]
2364    fn mouse_vertical_scroll_down() {
2365        let mut p = InputParser::new();
2366        // button5 press = 65
2367        let res = p.parse_as_vec(b"\x1b[<65;1;1M", true);
2368        assert_eq!(
2369            res,
2370            vec![InputEvent::Mouse(MouseEvent {
2371                x: 1,
2372                y: 1,
2373                mouse_buttons: MouseButtons::VERT_WHEEL,
2374                modifiers: Modifiers::NONE,
2375            })]
2376        );
2377    }
2378
2379    #[test]
2380    fn mouse_motion_no_buttons() {
2381        let mut p = InputParser::new();
2382        // motion with no buttons = 35
2383        let res = p.parse_as_vec(b"\x1b[<35;10;10M", true);
2384        assert_eq!(
2385            res,
2386            vec![InputEvent::Mouse(MouseEvent {
2387                x: 10,
2388                y: 10,
2389                mouse_buttons: MouseButtons::NONE,
2390                modifiers: Modifiers::NONE,
2391            })]
2392        );
2393    }
2394
2395    #[test]
2396    fn mouse_with_ctrl_alt() {
2397        let mut p = InputParser::new();
2398        // button1 press = 0, ALT=8, CTRL=16 -> 0+8+16=24
2399        let res = p.parse_as_vec(b"\x1b[<24;1;1M", true);
2400        assert_eq!(
2401            res,
2402            vec![InputEvent::Mouse(MouseEvent {
2403                x: 1,
2404                y: 1,
2405                mouse_buttons: MouseButtons::LEFT,
2406                modifiers: Modifiers::ALT | Modifiers::CTRL,
2407            })]
2408        );
2409    }
2410
2411    #[test]
2412    fn mouse_large_coordinates() {
2413        let mut p = InputParser::new();
2414        let res = p.parse_as_vec(b"\x1b[<0;999;999M", true);
2415        assert_eq!(
2416            res,
2417            vec![InputEvent::Mouse(MouseEvent {
2418                x: 999,
2419                y: 999,
2420                mouse_buttons: MouseButtons::LEFT,
2421                modifiers: Modifiers::NONE,
2422            })]
2423        );
2424    }
2425
2426    #[test]
2427    fn mouse_followed_by_key() {
2428        let mut p = InputParser::new();
2429        let res = p.parse_as_vec(b"\x1b[<0;1;1Mhello", false);
2430        assert_eq!(res.len(), 6); // 1 mouse + 5 chars
2431        assert!(matches!(res[0], InputEvent::Mouse(_)));
2432        assert!(matches!(res[1], InputEvent::Key(_)));
2433    }
2434
2435    #[test]
2436    fn two_mouse_events_back_to_back() {
2437        let mut p = InputParser::new();
2438        let res = p.parse_as_vec(b"\x1b[<0;1;1M\x1b[<0;2;2M", true);
2439        assert_eq!(res.len(), 2);
2440    }
2441
2442    #[test]
2443    fn invalid_sgr_mouse_falls_through() {
2444        let mut p = InputParser::new();
2445        // Invalid: missing terminator, not enough params
2446        let res = p.parse_as_vec(b"\x1b[<0;1M", false);
2447        // Should NOT parse as mouse - falls through to keymap
2448        assert!(res.iter().all(|e| matches!(e, InputEvent::Key(_))));
2449    }
2450
2451    #[test]
2452    fn osc_bel_terminated() {
2453        // Complete OSC sequence with BEL terminator
2454        let mut p = InputParser::new();
2455        let inputs = p.parse_as_vec(b"\x1b]99;i=test:p=title;Hello\x07", NO_MORE);
2456        assert_eq!(
2457            vec![InputEvent::OperatingSystemCommand(
2458                b"99;i=test:p=title;Hello".to_vec()
2459            )],
2460            inputs
2461        );
2462    }
2463
2464    #[test]
2465    fn osc_st_terminated() {
2466        // Complete OSC sequence with ST terminator (ESC \)
2467        let mut p = InputParser::new();
2468        let inputs = p.parse_as_vec(b"\x1b]99;i=test:p=title;Hello\x1b\\", NO_MORE);
2469        assert_eq!(
2470            vec![InputEvent::OperatingSystemCommand(
2471                b"99;i=test:p=title;Hello".to_vec()
2472            )],
2473            inputs
2474        );
2475    }
2476
2477    #[test]
2478    fn osc_partial_across_reads() {
2479        // OSC sequence split across two reads — must buffer first part
2480        let mut p = InputParser::new();
2481        let mut inputs = Vec::new();
2482        p.parse(
2483            b"\x1b]99;i=test:p=title;Hel",
2484            |evt| inputs.push(evt),
2485            MAYBE_MORE,
2486        );
2487        assert!(inputs.is_empty(), "no events yet - sequence incomplete");
2488        p.parse(b"lo\x1b\\", |evt| inputs.push(evt), MAYBE_MORE);
2489        assert_eq!(
2490            vec![InputEvent::OperatingSystemCommand(
2491                b"99;i=test:p=title;Hello".to_vec()
2492            )],
2493            inputs
2494        );
2495    }
2496
2497    #[test]
2498    fn osc_followed_by_keypress() {
2499        // OSC sequence then regular key in same buffer
2500        let mut p = InputParser::new();
2501        let inputs = p.parse_as_vec(b"\x1b]99;i=test;clicked\x07x", NO_MORE);
2502        assert_eq!(
2503            vec![
2504                InputEvent::OperatingSystemCommand(b"99;i=test;clicked".to_vec()),
2505                InputEvent::Key(KeyEvent {
2506                    modifiers: Modifiers::NONE,
2507                    key: KeyCode::Char('x'),
2508                }),
2509            ],
2510            inputs
2511        );
2512    }
2513
2514    #[test]
2515    fn keypress_followed_by_osc() {
2516        // Regular key then OSC sequence in same buffer
2517        let mut p = InputParser::new();
2518        let inputs = p.parse_as_vec(b"x\x1b]99;i=test;clicked\x07", NO_MORE);
2519        assert_eq!(
2520            vec![
2521                InputEvent::Key(KeyEvent {
2522                    modifiers: Modifiers::NONE,
2523                    key: KeyCode::Char('x'),
2524                }),
2525                InputEvent::OperatingSystemCommand(b"99;i=test;clicked".to_vec()),
2526            ],
2527            inputs
2528        );
2529    }
2530
2531    #[test]
2532    fn osc_incomplete_degrades_to_keys() {
2533        // Incomplete OSC that never gets a terminator — when finalized with
2534        // maybe_more=false, must degrade to individual key events (not hang)
2535        let mut p = InputParser::new();
2536        let mut inputs = Vec::new();
2537        p.parse(b"\x1b]99;no-terminator", |evt| inputs.push(evt), MAYBE_MORE);
2538        assert!(inputs.is_empty(), "buffered while maybe_more=true");
2539        p.parse(b"", |evt| inputs.push(evt), NO_MORE);
2540        assert!(!inputs.is_empty(), "must emit something on finalization");
2541    }
2542
2543    #[test]
2544    fn osc_non_99_code() {
2545        // Non-99 OSC codes are also captured as OperatingSystemCommand
2546        let mut p = InputParser::new();
2547        let inputs = p.parse_as_vec(b"\x1b]11;rgb:0000/0000/0000\x1b\\", NO_MORE);
2548        assert_eq!(
2549            vec![InputEvent::OperatingSystemCommand(
2550                b"11;rgb:0000/0000/0000".to_vec()
2551            )],
2552            inputs
2553        );
2554    }
2555
2556    #[test]
2557    fn osc_empty_payload() {
2558        // Edge case: OSC with no payload between \x1b] and terminator
2559        let mut p = InputParser::new();
2560        let inputs = p.parse_as_vec(b"\x1b]\x07", NO_MORE);
2561        assert_eq!(
2562            vec![InputEvent::OperatingSystemCommand(b"".to_vec())],
2563            inputs
2564        );
2565    }
2566
2567    #[test]
2568    fn csi_not_captured_as_osc() {
2569        // ESC [ (CSI) must NOT be captured as an OSC sequence.
2570        // This validates that only ESC ] triggers OSC parsing.
2571        let mut p = InputParser::new();
2572        let inputs = p.parse_as_vec(b"\x1b[A", NO_MORE);
2573        assert_eq!(
2574            vec![InputEvent::Key(KeyEvent {
2575                modifiers: Modifiers::NONE,
2576                key: KeyCode::UpArrow,
2577            })],
2578            inputs
2579        );
2580    }
2581
2582    // =====================================================================
2583    // parse_csi_report (CSI report whitelist for host-reply forwarding)
2584    // =====================================================================
2585
2586    fn csi_reply(intermediates: &[u8], params: &[u8], final_byte: u8, raw: &[u8]) -> InputEvent {
2587        InputEvent::DeviceControlReply {
2588            intermediates: intermediates.to_vec(),
2589            params: params.to_vec(),
2590            final_byte,
2591            raw: raw.to_vec(),
2592        }
2593    }
2594
2595    #[test]
2596    fn csi_report_recognises_each_whitelisted_final_byte() {
2597        // `t` — pixel-dimension reply form `\x1b[4;H;Wt`.
2598        let bytes = b"\x1b[4;600;800t";
2599        let (evt, consumed) = parse_csi_report(bytes).expect("t accepted");
2600        assert_eq!(consumed, bytes.len());
2601        assert_eq!(evt, csi_reply(b"", b"4;600;800", b't', bytes));
2602
2603        // `y` — DECRPM, e.g. sync-output support. Intermediate `$`.
2604        let bytes = b"\x1b[?2026;1$y";
2605        let (evt, consumed) = parse_csi_report(bytes).expect("y accepted");
2606        assert_eq!(consumed, bytes.len());
2607        assert_eq!(evt, csi_reply(b"$", b"?2026;1", b'y', bytes));
2608
2609        // `c` — Primary-DA reply (barrier).
2610        let bytes = b"\x1b[?62;1;6c";
2611        let (evt, consumed) = parse_csi_report(bytes).expect("c accepted");
2612        assert_eq!(consumed, bytes.len());
2613        assert_eq!(evt, csi_reply(b"", b"?62;1;6", b'c', bytes));
2614
2615        // `n` — DSR reply (used for theme notifications).
2616        let bytes = b"\x1b[?997;1n";
2617        let (evt, consumed) = parse_csi_report(bytes).expect("n accepted");
2618        assert_eq!(consumed, bytes.len());
2619        assert_eq!(evt, csi_reply(b"", b"?997;1", b'n', bytes));
2620    }
2621
2622    #[test]
2623    fn csi_report_preserves_intermediates() {
2624        // DECRPM uses `$` as its intermediate byte — it must land in
2625        // `intermediates`, not `params`.
2626        let bytes = b"\x1b[?2026;2$y";
2627        let (evt, _len) = parse_csi_report(bytes).expect("DECRPM accepted");
2628        let InputEvent::DeviceControlReply {
2629            intermediates,
2630            params,
2631            final_byte,
2632            raw,
2633        } = evt
2634        else {
2635            panic!("expected DeviceControlReply, got {:?}", evt);
2636        };
2637        assert_eq!(intermediates, b"$");
2638        assert_eq!(params, b"?2026;2");
2639        assert_eq!(final_byte, b'y');
2640        assert_eq!(raw, bytes);
2641    }
2642
2643    #[test]
2644    fn csi_report_rejects_non_whitelisted_final_bytes() {
2645        // `A` = cursor-up (keyboard input, not a report).
2646        assert!(parse_csi_report(b"\x1b[A").is_none());
2647        // `R` = cursor-position report — not whitelisted; must pass
2648        // through to the keyboard path.
2649        assert!(parse_csi_report(b"\x1b[24;80R").is_none());
2650        // `m` = SGR; appears in render streams but should never reach
2651        // stdin as a report.
2652        assert!(parse_csi_report(b"\x1b[0m").is_none());
2653    }
2654
2655    #[test]
2656    fn csi_report_returns_none_on_truncated_input() {
2657        // No final byte within the supplied slice → caller should wait
2658        // for more bytes; `parse_csi_report` must not "commit" to a
2659        // partial parse.
2660        assert!(parse_csi_report(b"\x1b[4;600;800").is_none());
2661        // Only the lead-in; parameters haven't started.
2662        assert!(parse_csi_report(b"\x1b[").is_none());
2663        // Empty input — zero bytes to consume.
2664        assert!(parse_csi_report(b"").is_none());
2665    }
2666
2667    #[test]
2668    fn csi_report_raw_preserves_input_byte_for_byte() {
2669        // `raw` must include the leading ESC through the final byte
2670        // inclusive, without adding or dropping any byte — the
2671        // forwarding path writes it verbatim to the pane's pty.
2672        let bytes = b"\x1b[4;16;8t";
2673        let (evt, consumed) = parse_csi_report(bytes).expect("accepted");
2674        assert_eq!(consumed, bytes.len());
2675        let InputEvent::DeviceControlReply { raw, .. } = evt else {
2676            panic!("wrong variant");
2677        };
2678        assert_eq!(&raw[..], bytes, "raw must be byte-identical to input");
2679    }
2680}