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    #[must_use]
421    pub fn wrap(&self, text: &[u8]) -> Vec<u8> {
422        if self.enabled {
423            let mut result = Vec::with_capacity(Self::START.len() + text.len() + Self::END.len());
424            result.extend_from_slice(Self::START);
425            result.extend_from_slice(text);
426            result.extend_from_slice(Self::END);
427            result
428        } else {
429            text.to_vec()
430        }
431    }
432}
433
434/// Input forwarder that manages state and writes to a PTY.
435pub struct InputForwarder<W: Write> {
436    writer: W,
437    bracketed_paste: BracketedPaste,
438}
439
440impl<W: Write> InputForwarder<W> {
441    /// Create a new input forwarder.
442    pub fn new(writer: W) -> Self {
443        Self {
444            writer,
445            bracketed_paste: BracketedPaste::new(),
446        }
447    }
448
449    /// Get a reference to the bracketed paste handler.
450    #[must_use]
451    pub const fn bracketed_paste(&self) -> &BracketedPaste {
452        &self.bracketed_paste
453    }
454
455    /// Get a mutable reference to the bracketed paste handler.
456    pub fn bracketed_paste_mut(&mut self) -> &mut BracketedPaste {
457        &mut self.bracketed_paste
458    }
459
460    /// Set bracketed paste mode.
461    pub fn set_bracketed_paste(&mut self, enabled: bool) {
462        self.bracketed_paste.set_enabled(enabled);
463    }
464
465    /// Forward a key event to the PTY.
466    pub fn forward_key(&mut self, event: KeyEvent) -> io::Result<()> {
467        let seq = key_to_sequence(event);
468        if !seq.is_empty() {
469            self.writer.write_all(&seq)?;
470            self.writer.flush()?;
471        }
472        Ok(())
473    }
474
475    /// Forward multiple key events.
476    pub fn forward_keys(&mut self, events: &[KeyEvent]) -> io::Result<()> {
477        for event in events {
478            let seq = key_to_sequence(*event);
479            if !seq.is_empty() {
480                self.writer.write_all(&seq)?;
481            }
482        }
483        self.writer.flush()
484    }
485
486    /// Forward raw bytes to the PTY.
487    pub fn forward_raw(&mut self, data: &[u8]) -> io::Result<()> {
488        self.writer.write_all(data)?;
489        self.writer.flush()
490    }
491
492    /// Forward text as a paste (with bracketing if enabled).
493    pub fn forward_paste(&mut self, text: &str) -> io::Result<()> {
494        let data = self.bracketed_paste.wrap(text.as_bytes());
495        self.writer.write_all(&data)?;
496        self.writer.flush()
497    }
498
499    /// Get mutable access to the underlying writer.
500    pub fn writer_mut(&mut self) -> &mut W {
501        &mut self.writer
502    }
503
504    /// Consume the forwarder and return the underlying writer.
505    pub fn into_writer(self) -> W {
506        self.writer
507    }
508}
509
510#[cfg(test)]
511mod tests {
512    use super::*;
513
514    #[test]
515    fn test_plain_char() {
516        let event = KeyEvent::plain(Key::Char('a'));
517        assert_eq!(key_to_sequence(event), b"a");
518    }
519
520    #[test]
521    fn test_shift_char() {
522        let event = KeyEvent::new(Key::Char('a'), Modifiers::SHIFT);
523        assert_eq!(key_to_sequence(event), b"A");
524    }
525
526    #[test]
527    fn test_ctrl_char() {
528        let event = KeyEvent::new(Key::Char('c'), Modifiers::CTRL);
529        assert_eq!(key_to_sequence(event), vec![0x03]); // ETX
530    }
531
532    #[test]
533    fn test_ctrl_a() {
534        let event = KeyEvent::new(Key::Char('a'), Modifiers::CTRL);
535        assert_eq!(key_to_sequence(event), vec![0x01]); // SOH
536    }
537
538    #[test]
539    fn test_ctrl_z() {
540        let event = KeyEvent::new(Key::Char('z'), Modifiers::CTRL);
541        assert_eq!(key_to_sequence(event), vec![0x1a]); // SUB
542    }
543
544    #[test]
545    fn test_alt_char() {
546        let event = KeyEvent::new(Key::Char('x'), Modifiers::ALT);
547        assert_eq!(key_to_sequence(event), vec![0x1b, b'x']);
548    }
549
550    #[test]
551    fn test_ctrl_alt_char() {
552        let event = KeyEvent::new(
553            Key::Char('c'),
554            Modifiers {
555                ctrl: true,
556                alt: true,
557                shift: false,
558            },
559        );
560        assert_eq!(key_to_sequence(event), vec![0x1b, 0x03]);
561    }
562
563    #[test]
564    fn test_arrow_keys() {
565        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Up)), b"\x1b[A");
566        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Down)), b"\x1b[B");
567        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Right)), b"\x1b[C");
568        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Left)), b"\x1b[D");
569    }
570
571    #[test]
572    fn test_arrow_with_modifiers() {
573        let event = KeyEvent::new(Key::Up, Modifiers::CTRL);
574        assert_eq!(key_to_sequence(event), b"\x1b[1;5A");
575
576        let event = KeyEvent::new(Key::Down, Modifiers::SHIFT);
577        assert_eq!(key_to_sequence(event), b"\x1b[1;2B");
578
579        let event = KeyEvent::new(Key::Left, Modifiers::ALT);
580        assert_eq!(key_to_sequence(event), b"\x1b[1;3D");
581    }
582
583    #[test]
584    fn test_function_keys_f1_f4() {
585        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(1))), b"\x1bOP");
586        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(2))), b"\x1bOQ");
587        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(3))), b"\x1bOR");
588        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(4))), b"\x1bOS");
589    }
590
591    #[test]
592    fn test_function_keys_f5_f12() {
593        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(5))), b"\x1b[15~");
594        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(6))), b"\x1b[17~");
595        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(10))), b"\x1b[21~");
596        assert_eq!(key_to_sequence(KeyEvent::plain(Key::F(12))), b"\x1b[24~");
597    }
598
599    #[test]
600    fn test_function_keys_with_modifiers() {
601        let event = KeyEvent::new(Key::F(1), Modifiers::SHIFT);
602        assert_eq!(key_to_sequence(event), b"\x1b[1;2P");
603
604        let event = KeyEvent::new(Key::F(5), Modifiers::CTRL);
605        assert_eq!(key_to_sequence(event), b"\x1b[15;5~");
606    }
607
608    #[test]
609    fn test_backspace() {
610        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Backspace)), vec![0x7f]);
611    }
612
613    #[test]
614    fn test_enter() {
615        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Enter)), vec![0x0d]);
616    }
617
618    #[test]
619    fn test_tab() {
620        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Tab)), vec![0x09]);
621    }
622
623    #[test]
624    fn test_shift_tab() {
625        let event = KeyEvent::new(Key::Tab, Modifiers::SHIFT);
626        assert_eq!(key_to_sequence(event), b"\x1b[Z");
627    }
628
629    #[test]
630    fn test_escape() {
631        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Escape)), vec![0x1b]);
632    }
633
634    #[test]
635    fn test_home_end() {
636        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Home)), b"\x1b[H");
637        assert_eq!(key_to_sequence(KeyEvent::plain(Key::End)), b"\x1b[F");
638    }
639
640    #[test]
641    fn test_page_keys() {
642        assert_eq!(key_to_sequence(KeyEvent::plain(Key::PageUp)), b"\x1b[5~");
643        assert_eq!(key_to_sequence(KeyEvent::plain(Key::PageDown)), b"\x1b[6~");
644    }
645
646    #[test]
647    fn test_insert_delete() {
648        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Insert)), b"\x1b[2~");
649        assert_eq!(key_to_sequence(KeyEvent::plain(Key::Delete)), b"\x1b[3~");
650    }
651
652    #[test]
653    fn test_modifiers_csi_param() {
654        assert_eq!(Modifiers::NONE.csi_param(), 1);
655        assert_eq!(Modifiers::SHIFT.csi_param(), 2);
656        assert_eq!(Modifiers::ALT.csi_param(), 3);
657        assert_eq!(Modifiers::CTRL.csi_param(), 5);
658
659        let ctrl_shift = Modifiers {
660            ctrl: true,
661            alt: false,
662            shift: true,
663        };
664        assert_eq!(ctrl_shift.csi_param(), 6);
665
666        let all = Modifiers {
667            ctrl: true,
668            alt: true,
669            shift: true,
670        };
671        assert_eq!(all.csi_param(), 8);
672    }
673
674    #[test]
675    fn test_bracketed_paste_disabled() {
676        let bp = BracketedPaste::new();
677        assert!(!bp.is_enabled());
678        assert_eq!(bp.wrap(b"hello"), b"hello");
679    }
680
681    #[test]
682    fn test_bracketed_paste_enabled() {
683        let mut bp = BracketedPaste::new();
684        bp.enable();
685        assert!(bp.is_enabled());
686
687        let wrapped = bp.wrap(b"hello");
688        assert!(wrapped.starts_with(BracketedPaste::START));
689        assert!(wrapped.ends_with(BracketedPaste::END));
690        assert!(wrapped[BracketedPaste::START.len()..].starts_with(b"hello"));
691    }
692
693    #[test]
694    fn test_input_forwarder() {
695        let mut buffer = Vec::new();
696
697        {
698            let mut forwarder = InputForwarder::new(&mut buffer);
699            forwarder
700                .forward_key(KeyEvent::plain(Key::Char('a')))
701                .unwrap();
702            forwarder.forward_key(KeyEvent::plain(Key::Enter)).unwrap();
703        }
704
705        assert_eq!(buffer, vec![b'a', 0x0d]);
706    }
707
708    #[test]
709    fn test_input_forwarder_paste() {
710        let mut buffer = Vec::new();
711
712        {
713            let mut forwarder = InputForwarder::new(&mut buffer);
714            forwarder.forward_paste("text").unwrap();
715        }
716
717        assert_eq!(buffer, b"text");
718    }
719
720    #[test]
721    fn test_input_forwarder_bracketed_paste() {
722        let mut buffer = Vec::new();
723
724        {
725            let mut forwarder = InputForwarder::new(&mut buffer);
726            forwarder.set_bracketed_paste(true);
727            forwarder.forward_paste("text").unwrap();
728        }
729
730        let expected = [BracketedPaste::START, b"text", BracketedPaste::END].concat();
731        assert_eq!(buffer, expected);
732    }
733
734    #[test]
735    fn test_utf8_char() {
736        let event = KeyEvent::plain(Key::Char('日'));
737        let seq = key_to_sequence(event);
738        assert_eq!(std::str::from_utf8(&seq).unwrap(), "日");
739    }
740
741    #[test]
742    fn test_emoji_char() {
743        let event = KeyEvent::plain(Key::Char('🎉'));
744        let seq = key_to_sequence(event);
745        assert_eq!(std::str::from_utf8(&seq).unwrap(), "🎉");
746    }
747
748    #[test]
749    fn test_ctrl_special_chars() {
750        // Ctrl+[ = ESC
751        let event = KeyEvent::new(Key::Char('['), Modifiers::CTRL);
752        assert_eq!(key_to_sequence(event), vec![0x1b]);
753
754        // Ctrl+@ = NUL
755        let event = KeyEvent::new(Key::Char('@'), Modifiers::CTRL);
756        assert_eq!(key_to_sequence(event), vec![0x00]);
757
758        // Ctrl+? = DEL
759        let event = KeyEvent::new(Key::Char('?'), Modifiers::CTRL);
760        assert_eq!(key_to_sequence(event), vec![0x7f]);
761    }
762}