Skip to main content

ftui_pty/
input_forwarding.rs

1//! Input forwarding for PTY processes.
2//!
3//! Converts keyboard input events into ANSI escape sequences for terminal input.
4//!
5//! # Invariants
6//!
7//! 1. **UTF-8 validity**: All output sequences are valid UTF-8 or raw bytes.
8//! 2. **Modifier precedence**: Ctrl > Alt > Shift in key transformation.
9//! 3. **Bracketed paste**: Paste content is wrapped when mode is enabled.
10//!
11//! # Failure Modes
12//!
13//! | Failure | Cause | Behavior |
14//! |---------|-------|----------|
15//! | Invalid key | Unsupported key code | Returns empty sequence |
16//! | Encoding error | Non-UTF8 char | Silently dropped |
17
18use std::io::{self, Write};
19
20/// Keyboard modifiers.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub struct Modifiers {
23    /// Control key is pressed.
24    pub ctrl: bool,
25    /// Alt/Meta key is pressed.
26    pub alt: bool,
27    /// Shift key is pressed.
28    pub shift: bool,
29}
30
31impl Modifiers {
32    /// No modifiers.
33    pub const NONE: Self = Self {
34        ctrl: false,
35        alt: false,
36        shift: false,
37    };
38
39    /// Ctrl modifier only.
40    pub const CTRL: Self = Self {
41        ctrl: true,
42        alt: false,
43        shift: false,
44    };
45
46    /// Alt modifier only.
47    pub const ALT: Self = Self {
48        ctrl: false,
49        alt: true,
50        shift: false,
51    };
52
53    /// Shift modifier only.
54    pub const SHIFT: Self = Self {
55        ctrl: false,
56        alt: false,
57        shift: true,
58    };
59
60    /// Check if any modifier is active.
61    #[must_use]
62    pub const fn any(self) -> bool {
63        self.ctrl || self.alt || self.shift
64    }
65
66    /// Get the CSI modifier parameter (1 + sum of modifier bits).
67    /// Used for extended key sequences like `CSI 1;{mod} A`.
68    #[must_use]
69    pub fn csi_param(self) -> u8 {
70        let mut param = 1u8;
71        if self.shift {
72            param += 1;
73        }
74        if self.alt {
75            param += 2;
76        }
77        if self.ctrl {
78            param += 4;
79        }
80        param
81    }
82}
83
84/// Key codes for keyboard input.
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum Key {
87    /// A printable character.
88    Char(char),
89    /// Function key (F1-F12).
90    F(u8),
91    /// Backspace key.
92    Backspace,
93    /// Enter/Return key.
94    Enter,
95    /// Tab key.
96    Tab,
97    /// Escape key.
98    Escape,
99    /// Up arrow.
100    Up,
101    /// Down arrow.
102    Down,
103    /// Left arrow.
104    Left,
105    /// Right arrow.
106    Right,
107    /// Home key.
108    Home,
109    /// End key.
110    End,
111    /// Page Up.
112    PageUp,
113    /// Page Down.
114    PageDown,
115    /// Insert key.
116    Insert,
117    /// Delete key.
118    Delete,
119}
120
121/// A keyboard event with key and modifiers.
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub struct KeyEvent {
124    /// The key that was pressed.
125    pub key: Key,
126    /// Active modifiers.
127    pub modifiers: Modifiers,
128}
129
130impl KeyEvent {
131    /// Create a new key event.
132    #[must_use]
133    pub const fn new(key: Key, modifiers: Modifiers) -> Self {
134        Self { key, modifiers }
135    }
136
137    /// Create a key event with no modifiers.
138    #[must_use]
139    pub const fn plain(key: Key) -> Self {
140        Self {
141            key,
142            modifiers: Modifiers::NONE,
143        }
144    }
145}
146
147impl From<char> for KeyEvent {
148    fn from(c: char) -> Self {
149        Self::plain(Key::Char(c))
150    }
151}
152
153/// Converts key events to ANSI escape sequences.
154///
155/// # Example
156///
157/// ```
158/// use ftui_pty::input_forwarding::{KeyEvent, Key, Modifiers, key_to_sequence};
159///
160/// // Simple character
161/// assert_eq!(key_to_sequence(KeyEvent::plain(Key::Char('a'))), b"a".to_vec());
162///
163/// // Ctrl+C
164/// let event = KeyEvent::new(Key::Char('c'), Modifiers::CTRL);
165/// assert_eq!(key_to_sequence(event), vec![0x03]); // ETX
166///
167/// // Up arrow
168/// assert_eq!(key_to_sequence(KeyEvent::plain(Key::Up)), b"\x1b[A".to_vec());
169/// ```
170#[must_use]
171pub fn key_to_sequence(event: KeyEvent) -> Vec<u8> {
172    let KeyEvent { key, modifiers } = event;
173
174    match key {
175        Key::Char(c) => char_sequence(c, modifiers),
176        Key::F(n) => function_key_sequence(n, modifiers),
177        Key::Backspace => backspace_sequence(modifiers),
178        Key::Enter => enter_sequence(modifiers),
179        Key::Tab => tab_sequence(modifiers),
180        Key::Escape => escape_sequence(modifiers),
181        Key::Up => cursor_key_sequence(b'A', modifiers),
182        Key::Down => cursor_key_sequence(b'B', modifiers),
183        Key::Right => cursor_key_sequence(b'C', modifiers),
184        Key::Left => cursor_key_sequence(b'D', modifiers),
185        Key::Home => home_end_sequence(b'H', modifiers),
186        Key::End => home_end_sequence(b'F', modifiers),
187        Key::PageUp => page_key_sequence(5, modifiers),
188        Key::PageDown => page_key_sequence(6, modifiers),
189        Key::Insert => insert_delete_sequence(2, modifiers),
190        Key::Delete => insert_delete_sequence(3, modifiers),
191    }
192}
193
194/// Convert a character with modifiers to an escape sequence.
195fn char_sequence(c: char, modifiers: Modifiers) -> Vec<u8> {
196    // Handle Ctrl+<key> combinations
197    if modifiers.ctrl
198        && !modifiers.alt
199        && let Some(ctrl_byte) = ctrl_char(c)
200    {
201        return vec![ctrl_byte];
202    }
203
204    // Handle Alt+<key> - prefix with ESC
205    if modifiers.alt && !modifiers.ctrl {
206        let mut seq = vec![0x1b]; // ESC
207        let ch = if modifiers.shift {
208            c.to_ascii_uppercase()
209        } else {
210            c
211        };
212        let mut buf = [0u8; 4];
213        seq.extend_from_slice(ch.encode_utf8(&mut buf).as_bytes());
214        return seq;
215    }
216
217    // Handle Ctrl+Alt combinations
218    if modifiers.ctrl
219        && modifiers.alt
220        && let Some(ctrl_byte) = ctrl_char(c)
221    {
222        return vec![0x1b, ctrl_byte]; // ESC + Ctrl code
223    }
224
225    // Plain character (with optional shift)
226    let ch = if modifiers.shift {
227        c.to_ascii_uppercase()
228    } else {
229        c
230    };
231    let mut buf = [0u8; 4];
232    ch.encode_utf8(&mut buf).as_bytes().to_vec()
233}
234
235/// Get the control character code for a letter (a-z, A-Z, some punctuation).
236fn ctrl_char(c: char) -> Option<u8> {
237    match c.to_ascii_lowercase() {
238        'a'..='z' => Some(c.to_ascii_lowercase() as u8 - b'a' + 1),
239        '[' | '3' => Some(0x1b),       // ESC
240        '\\' | '4' => Some(0x1c),      // FS
241        ']' | '5' => Some(0x1d),       // GS
242        '^' | '6' => Some(0x1e),       // RS
243        '_' | '7' => Some(0x1f),       // US
244        '?' | '8' => Some(0x7f),       // DEL
245        '@' | '2' | ' ' => Some(0x00), // NUL
246        _ => None,
247    }
248}
249
250/// Generate function key sequence (F1-F12).
251fn function_key_sequence(n: u8, modifiers: Modifiers) -> Vec<u8> {
252    // F1-F4 use ESC O P/Q/R/S (or ESC [ with modifiers)
253    // F5-F12 use ESC [ <code> ~
254    let (code, use_tilde) = match n {
255        1 => (b'P', false),
256        2 => (b'Q', false),
257        3 => (b'R', false),
258        4 => (b'S', false),
259        5 => (15, true),
260        6 => (17, true),
261        7 => (18, true),
262        8 => (19, true),
263        9 => (20, true),
264        10 => (21, true),
265        11 => (23, true),
266        12 => (24, true),
267        _ => return Vec::new(),
268    };
269
270    if use_tilde {
271        if modifiers.any() {
272            // ESC [ <code> ; <mod> ~
273            format!("\x1b[{};{}~", code, modifiers.csi_param()).into_bytes()
274        } else {
275            // ESC [ <code> ~
276            format!("\x1b[{}~", code).into_bytes()
277        }
278    } else if modifiers.any() {
279        // ESC [ 1 ; <mod> <code>
280        format!("\x1b[1;{}{}", modifiers.csi_param(), code as char).into_bytes()
281    } else {
282        // ESC O <code>
283        vec![0x1b, b'O', code]
284    }
285}
286
287/// Generate backspace sequence.
288fn backspace_sequence(modifiers: Modifiers) -> Vec<u8> {
289    if modifiers.ctrl {
290        vec![0x08] // BS (Ctrl+H behavior)
291    } else if modifiers.alt {
292        vec![0x1b, 0x7f] // ESC DEL
293    } else {
294        vec![0x7f] // DEL (standard backspace)
295    }
296}
297
298/// Generate enter sequence.
299fn enter_sequence(modifiers: Modifiers) -> Vec<u8> {
300    if modifiers.alt {
301        vec![0x1b, 0x0d] // ESC CR
302    } else {
303        vec![0x0d] // CR
304    }
305}
306
307/// Generate tab sequence.
308fn tab_sequence(modifiers: Modifiers) -> Vec<u8> {
309    if modifiers.shift {
310        b"\x1b[Z".to_vec() // CSI Z (backtab)
311    } else if modifiers.alt {
312        vec![0x1b, 0x09] // ESC TAB
313    } else {
314        vec![0x09] // TAB
315    }
316}
317
318/// Generate escape sequence.
319fn escape_sequence(modifiers: Modifiers) -> Vec<u8> {
320    if modifiers.alt {
321        vec![0x1b, 0x1b] // ESC ESC
322    } else {
323        vec![0x1b] // ESC
324    }
325}
326
327/// Generate cursor key sequence (arrows).
328fn cursor_key_sequence(code: u8, modifiers: Modifiers) -> Vec<u8> {
329    if modifiers.any() {
330        // ESC [ 1 ; <mod> <code>
331        format!("\x1b[1;{}{}", modifiers.csi_param(), code as char).into_bytes()
332    } else {
333        // ESC [ <code>
334        vec![0x1b, b'[', code]
335    }
336}
337
338/// Generate Home/End sequence.
339fn home_end_sequence(code: u8, modifiers: Modifiers) -> Vec<u8> {
340    if modifiers.any() {
341        // ESC [ 1 ; <mod> <code>
342        format!("\x1b[1;{}{}", modifiers.csi_param(), code as char).into_bytes()
343    } else {
344        // ESC [ <code>
345        vec![0x1b, b'[', code]
346    }
347}
348
349/// Generate Page Up/Down sequence.
350fn page_key_sequence(code: u8, modifiers: Modifiers) -> Vec<u8> {
351    if modifiers.any() {
352        // ESC [ <code> ; <mod> ~
353        format!("\x1b[{};{}~", code, modifiers.csi_param()).into_bytes()
354    } else {
355        // ESC [ <code> ~
356        format!("\x1b[{}~", code).into_bytes()
357    }
358}
359
360/// Generate Insert/Delete sequence.
361fn insert_delete_sequence(code: u8, modifiers: Modifiers) -> Vec<u8> {
362    if modifiers.any() {
363        // ESC [ <code> ; <mod> ~
364        format!("\x1b[{};{}~", code, modifiers.csi_param()).into_bytes()
365    } else {
366        // ESC [ <code> ~
367        format!("\x1b[{}~", code).into_bytes()
368    }
369}
370
371/// Wrapper for bracketed paste.
372///
373/// In bracketed paste mode, pasted text is wrapped with start/end markers
374/// so the terminal can distinguish it from typed input.
375#[derive(Debug, Clone)]
376pub struct BracketedPaste {
377    enabled: bool,
378}
379
380impl Default for BracketedPaste {
381    fn default() -> Self {
382        Self::new()
383    }
384}
385
386impl BracketedPaste {
387    /// Start marker for bracketed paste.
388    pub const START: &'static [u8] = b"\x1b[200~";
389    /// End marker for bracketed paste.
390    pub const END: &'static [u8] = b"\x1b[201~";
391
392    /// Create a new bracketed paste handler (disabled by default).
393    #[must_use]
394    pub const fn new() -> Self {
395        Self { enabled: false }
396    }
397
398    /// Check if bracketed paste mode is enabled.
399    #[must_use]
400    pub const fn is_enabled(&self) -> bool {
401        self.enabled
402    }
403
404    /// Enable bracketed paste mode.
405    pub fn enable(&mut self) {
406        self.enabled = true;
407    }
408
409    /// Disable bracketed paste mode.
410    pub fn disable(&mut self) {
411        self.enabled = false;
412    }
413
414    /// Set bracketed paste mode.
415    pub fn set_enabled(&mut self, enabled: bool) {
416        self.enabled = enabled;
417    }
418
419    /// Wrap text for paste, adding markers if enabled.
420    ///
421    /// Safety fallback:
422    /// If the payload already contains bracketed-paste delimiters, we
423    /// degrade to raw passthrough for this paste operation. This avoids
424    /// delimiter-injection ambiguity (`ESC [ 200~` / `ESC [ 201~`) while
425    /// preserving payload bytes exactly.
426    #[must_use]
427    pub fn wrap(&self, text: &[u8]) -> Vec<u8> {
428        if self.enabled {
429            if Self::contains_delimiter(text) {
430                return text.to_vec();
431            }
432            let mut result = Vec::with_capacity(Self::START.len() + text.len() + Self::END.len());
433            result.extend_from_slice(Self::START);
434            result.extend_from_slice(text);
435            result.extend_from_slice(Self::END);
436            result
437        } else {
438            text.to_vec()
439        }
440    }
441
442    #[must_use]
443    fn contains_delimiter(text: &[u8]) -> bool {
444        text.windows(Self::START.len())
445            .any(|window| window == Self::START)
446            || text
447                .windows(Self::END.len())
448                .any(|window| window == Self::END)
449    }
450}
451
452/// Input forwarder that manages state and writes to a PTY.
453pub struct InputForwarder<W: Write> {
454    writer: W,
455    bracketed_paste: BracketedPaste,
456}
457
458impl<W: Write> InputForwarder<W> {
459    /// Create a new input forwarder.
460    pub fn new(writer: W) -> Self {
461        Self {
462            writer,
463            bracketed_paste: BracketedPaste::new(),
464        }
465    }
466
467    /// Get a reference to the bracketed paste handler.
468    #[must_use]
469    pub const fn bracketed_paste(&self) -> &BracketedPaste {
470        &self.bracketed_paste
471    }
472
473    /// Get a mutable reference to the bracketed paste handler.
474    pub fn bracketed_paste_mut(&mut self) -> &mut BracketedPaste {
475        &mut self.bracketed_paste
476    }
477
478    /// Set bracketed paste mode.
479    pub fn set_bracketed_paste(&mut self, enabled: bool) {
480        self.bracketed_paste.set_enabled(enabled);
481    }
482
483    /// Forward a key event to the PTY.
484    pub fn forward_key(&mut self, event: KeyEvent) -> io::Result<()> {
485        let seq = key_to_sequence(event);
486        if !seq.is_empty() {
487            self.writer.write_all(&seq)?;
488            self.writer.flush()?;
489        }
490        Ok(())
491    }
492
493    /// Forward multiple key events.
494    pub fn forward_keys(&mut self, events: &[KeyEvent]) -> io::Result<()> {
495        for event in events {
496            let seq = key_to_sequence(*event);
497            if !seq.is_empty() {
498                self.writer.write_all(&seq)?;
499            }
500        }
501        self.writer.flush()
502    }
503
504    /// Forward raw bytes to the PTY.
505    pub fn forward_raw(&mut self, data: &[u8]) -> io::Result<()> {
506        self.writer.write_all(data)?;
507        self.writer.flush()
508    }
509
510    /// Forward text as a paste (with bracketing if enabled).
511    pub fn forward_paste(&mut self, text: &str) -> io::Result<()> {
512        let data = self.bracketed_paste.wrap(text.as_bytes());
513        self.writer.write_all(&data)?;
514        self.writer.flush()
515    }
516
517    /// Get mutable access to the underlying writer.
518    pub fn writer_mut(&mut self) -> &mut W {
519        &mut self.writer
520    }
521
522    /// Consume the forwarder and return the underlying writer.
523    pub fn into_writer(self) -> W {
524        self.writer
525    }
526}
527
528#[cfg(test)]
529mod tests {
530    use super::*;
531
532    #[test]
533    fn test_plain_char() {
534        let event = KeyEvent::plain(Key::Char('a'));
535        assert_eq!(key_to_sequence(event), b"a");
536    }
537
538    #[test]
539    fn test_shift_char() {
540        let event = KeyEvent::new(Key::Char('a'), Modifiers::SHIFT);
541        assert_eq!(key_to_sequence(event), b"A");
542    }
543
544    #[test]
545    fn test_ctrl_char() {
546        let event = KeyEvent::new(Key::Char('c'), Modifiers::CTRL);
547        assert_eq!(key_to_sequence(event), vec![0x03]); // ETX
548    }
549
550    #[test]
551    fn test_ctrl_a() {
552        let event = KeyEvent::new(Key::Char('a'), Modifiers::CTRL);
553        assert_eq!(key_to_sequence(event), vec![0x01]); // SOH
554    }
555
556    #[test]
557    fn test_ctrl_z() {
558        let event = KeyEvent::new(Key::Char('z'), Modifiers::CTRL);
559        assert_eq!(key_to_sequence(event), vec![0x1a]); // SUB
560    }
561
562    #[test]
563    fn test_alt_char() {
564        let event = KeyEvent::new(Key::Char('x'), Modifiers::ALT);
565        assert_eq!(key_to_sequence(event), vec![0x1b, b'x']);
566    }
567
568    #[test]
569    fn test_ctrl_alt_char() {
570        let event = KeyEvent::new(
571            Key::Char('c'),
572            Modifiers {
573                ctrl: true,
574                alt: true,
575                shift: false,
576            },
577        );
578        assert_eq!(key_to_sequence(event), vec![0x1b, 0x03]);
579    }
580
581    #[test]
582    fn test_arrow_keys() {
583        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Up)), b"\x1b[A");
584        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Down)), b"\x1b[B");
585        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Right)), b"\x1b[C");
586        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Left)), b"\x1b[D");
587    }
588
589    #[test]
590    fn test_arrow_with_modifiers() {
591        let event = KeyEvent::new(Key::Up, Modifiers::CTRL);
592        assert_eq!(key_to_sequence(event), b"\x1b[1;5A");
593
594        let event = KeyEvent::new(Key::Down, Modifiers::SHIFT);
595        assert_eq!(key_to_sequence(event), b"\x1b[1;2B");
596
597        let event = KeyEvent::new(Key::Left, Modifiers::ALT);
598        assert_eq!(key_to_sequence(event), b"\x1b[1;3D");
599    }
600
601    #[test]
602    fn test_function_keys_f1_f4() {
603        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(1))), b"\x1bOP");
604        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(2))), b"\x1bOQ");
605        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(3))), b"\x1bOR");
606        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(4))), b"\x1bOS");
607    }
608
609    #[test]
610    fn test_function_keys_f5_f12() {
611        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(5))), b"\x1b[15~");
612        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(6))), b"\x1b[17~");
613        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(10))), b"\x1b[21~");
614        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(12))), b"\x1b[24~");
615    }
616
617    #[test]
618    fn test_function_keys_with_modifiers() {
619        let event = KeyEvent::new(Key::F(1), Modifiers::SHIFT);
620        assert_eq!(key_to_sequence(event), b"\x1b[1;2P");
621
622        let event = KeyEvent::new(Key::F(5), Modifiers::CTRL);
623        assert_eq!(key_to_sequence(event), b"\x1b[15;5~");
624    }
625
626    #[test]
627    fn test_backspace() {
628        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Backspace)), vec![0x7f]);
629    }
630
631    #[test]
632    fn test_enter() {
633        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Enter)), vec![0x0d]);
634    }
635
636    #[test]
637    fn test_tab() {
638        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Tab)), vec![0x09]);
639    }
640
641    #[test]
642    fn test_shift_tab() {
643        let event = KeyEvent::new(Key::Tab, Modifiers::SHIFT);
644        assert_eq!(key_to_sequence(event), b"\x1b[Z");
645    }
646
647    #[test]
648    fn test_escape() {
649        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Escape)), vec![0x1b]);
650    }
651
652    #[test]
653    fn test_home_end() {
654        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Home)), b"\x1b[H");
655        assert_eq!(key_to_sequence(KeyEvent::plain(Key::End)), b"\x1b[F");
656    }
657
658    #[test]
659    fn test_page_keys() {
660        assert_eq!(key_to_sequence(KeyEvent::plain(Key::PageUp)), b"\x1b[5~");
661        assert_eq!(key_to_sequence(KeyEvent::plain(Key::PageDown)), b"\x1b[6~");
662    }
663
664    #[test]
665    fn test_insert_delete() {
666        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Insert)), b"\x1b[2~");
667        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Delete)), b"\x1b[3~");
668    }
669
670    #[test]
671    fn test_modifiers_csi_param() {
672        assert_eq!(Modifiers::NONE.csi_param(), 1);
673        assert_eq!(Modifiers::SHIFT.csi_param(), 2);
674        assert_eq!(Modifiers::ALT.csi_param(), 3);
675        assert_eq!(Modifiers::CTRL.csi_param(), 5);
676
677        let ctrl_shift = Modifiers {
678            ctrl: true,
679            alt: false,
680            shift: true,
681        };
682        assert_eq!(ctrl_shift.csi_param(), 6);
683
684        let all = Modifiers {
685            ctrl: true,
686            alt: true,
687            shift: true,
688        };
689        assert_eq!(all.csi_param(), 8);
690    }
691
692    #[test]
693    fn test_bracketed_paste_disabled() {
694        let bp = BracketedPaste::new();
695        assert!(!bp.is_enabled());
696        assert_eq!(bp.wrap(b"hello"), b"hello");
697    }
698
699    #[test]
700    fn test_bracketed_paste_enabled() {
701        let mut bp = BracketedPaste::new();
702        bp.enable();
703        assert!(bp.is_enabled());
704
705        let wrapped = bp.wrap(b"hello");
706        assert!(wrapped.starts_with(BracketedPaste::START));
707        assert!(wrapped.ends_with(BracketedPaste::END));
708        assert!(wrapped[BracketedPaste::START.len()..].starts_with(b"hello"));
709    }
710
711    #[test]
712    fn test_bracketed_paste_falls_back_to_raw_when_payload_contains_end_delimiter() {
713        let mut bp = BracketedPaste::new();
714        bp.enable();
715
716        let payload = b"prefix\x1b[201~suffix";
717        let wrapped = bp.wrap(payload);
718        assert_eq!(wrapped, payload);
719    }
720
721    #[test]
722    fn test_bracketed_paste_falls_back_to_raw_when_payload_contains_start_delimiter() {
723        let mut bp = BracketedPaste::new();
724        bp.enable();
725
726        let payload = b"prefix\x1b[200~suffix";
727        let wrapped = bp.wrap(payload);
728        assert_eq!(wrapped, payload);
729    }
730
731    #[test]
732    fn test_input_forwarder() {
733        let mut buffer = Vec::new();
734
735        {
736            let mut forwarder = InputForwarder::new(&mut buffer);
737            forwarder
738                .forward_key(KeyEvent::plain(Key::Char('a')))
739                .unwrap();
740            forwarder.forward_key(KeyEvent::plain(Key::Enter)).unwrap();
741        }
742
743        assert_eq!(buffer, vec![b'a', 0x0d]);
744    }
745
746    #[test]
747    fn test_input_forwarder_paste() {
748        let mut buffer = Vec::new();
749
750        {
751            let mut forwarder = InputForwarder::new(&mut buffer);
752            forwarder.forward_paste("text").unwrap();
753        }
754
755        assert_eq!(buffer, b"text");
756    }
757
758    #[test]
759    fn test_input_forwarder_bracketed_paste() {
760        let mut buffer = Vec::new();
761
762        {
763            let mut forwarder = InputForwarder::new(&mut buffer);
764            forwarder.set_bracketed_paste(true);
765            forwarder.forward_paste("text").unwrap();
766        }
767
768        let expected = [BracketedPaste::START, b"text", BracketedPaste::END].concat();
769        assert_eq!(buffer, expected);
770    }
771
772    #[test]
773    fn test_input_forwarder_bracketed_paste_uses_raw_fallback_for_delimiter_payload() {
774        let mut buffer = Vec::new();
775        let payload = "alpha\u{1b}[201~omega";
776
777        {
778            let mut forwarder = InputForwarder::new(&mut buffer);
779            forwarder.set_bracketed_paste(true);
780            forwarder.forward_paste(payload).unwrap();
781        }
782
783        assert_eq!(buffer, payload.as_bytes());
784    }
785
786    #[test]
787    fn test_utf8_char() {
788        let event = KeyEvent::plain(Key::Char('日'));
789        let seq = key_to_sequence(event);
790        assert_eq!(std::str::from_utf8(&seq).unwrap(), "日");
791    }
792
793    #[test]
794    fn test_emoji_char() {
795        let event = KeyEvent::plain(Key::Char('🎉'));
796        let seq = key_to_sequence(event);
797        assert_eq!(std::str::from_utf8(&seq).unwrap(), "🎉");
798    }
799
800    #[test]
801    fn test_ctrl_special_chars() {
802        // Ctrl+[ = ESC
803        let event = KeyEvent::new(Key::Char('['), Modifiers::CTRL);
804        assert_eq!(key_to_sequence(event), vec![0x1b]);
805
806        // Ctrl+@ = NUL
807        let event = KeyEvent::new(Key::Char('@'), Modifiers::CTRL);
808        assert_eq!(key_to_sequence(event), vec![0x00]);
809
810        // Ctrl+? = DEL
811        let event = KeyEvent::new(Key::Char('?'), Modifiers::CTRL);
812        assert_eq!(key_to_sequence(event), vec![0x7f]);
813    }
814}