ratatui_testlib/
events.rs

1//! Keyboard event types and escape sequence encoding.
2//!
3//! This module provides types for representing keyboard input and converting them
4//! to VT100/ANSI escape sequences that can be sent to PTY-based applications.
5//!
6//! # Key Features
7//!
8//! - **Type-safe key codes**: Enum-based key representation
9//! - **Modifier support**: Ctrl, Alt, Shift, Meta via bitflags
10//! - **VT100 compliance**: Standard escape sequences for terminal compatibility
11//! - **Zero allocation**: Static byte slices where possible
12//!
13//! # Example
14//!
15//! ```rust
16//! use ratatui_testlib::events::{KeyCode, Modifiers, KeyEvent};
17//!
18//! // Simple key
19//! let key = KeyEvent::new(KeyCode::Char('a'));
20//!
21//! // Key with modifiers
22//! let ctrl_c = KeyEvent::with_modifiers(
23//!     KeyCode::Char('c'),
24//!     Modifiers::CTRL
25//! );
26//!
27//! // Navigation keys
28//! let up = KeyEvent::new(KeyCode::Up);
29//! let enter = KeyEvent::new(KeyCode::Enter);
30//! ```
31
32use bitflags::bitflags;
33
34/// Represents a keyboard key.
35///
36/// This enum covers all keys commonly used in TUI applications, including:
37/// - Alphanumeric characters
38/// - Special keys (Enter, Tab, Esc, etc.)
39/// - Navigation keys (arrows, Home, End, etc.)
40/// - Function keys (F1-F12)
41///
42/// # Example
43///
44/// ```rust
45/// use ratatui_testlib::events::KeyCode;
46///
47/// let letter = KeyCode::Char('a');
48/// let enter = KeyCode::Enter;
49/// let arrow = KeyCode::Up;
50/// let function = KeyCode::F(1); // F1
51/// ```
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
53pub enum KeyCode {
54    /// A character key (letters, numbers, symbols).
55    Char(char),
56
57    /// Enter key (newline).
58    Enter,
59
60    /// Escape key.
61    Esc,
62
63    /// Tab key.
64    Tab,
65
66    /// Backspace key.
67    Backspace,
68
69    /// Delete key.
70    Delete,
71
72    /// Insert key.
73    Insert,
74
75    /// Up arrow key.
76    Up,
77
78    /// Down arrow key.
79    Down,
80
81    /// Left arrow key.
82    Left,
83
84    /// Right arrow key.
85    Right,
86
87    /// Home key.
88    Home,
89
90    /// End key.
91    End,
92
93    /// Page Up key.
94    PageUp,
95
96    /// Page Down key.
97    PageDown,
98
99    /// Function keys F1-F12.
100    ///
101    /// # Example
102    ///
103    /// ```rust
104    /// use ratatui_testlib::events::KeyCode;
105    ///
106    /// let f1 = KeyCode::F(1);
107    /// let f12 = KeyCode::F(12);
108    /// ```
109    F(u8),
110}
111
112bitflags! {
113    /// Modifier keys that can be combined with other keys.
114    ///
115    /// These are bitflags, so multiple modifiers can be combined:
116    ///
117    /// ```rust
118    /// use ratatui_testlib::events::Modifiers;
119    ///
120    /// let ctrl_shift = Modifiers::CTRL | Modifiers::SHIFT;
121    /// let ctrl_alt = Modifiers::CTRL | Modifiers::ALT;
122    /// ```
123    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
124    pub struct Modifiers: u8 {
125        /// Shift key.
126        const SHIFT = 0b0001;
127
128        /// Control key.
129        const CTRL  = 0b0010;
130
131        /// Alt/Option key.
132        const ALT   = 0b0100;
133
134        /// Meta/Command/Windows key.
135        const META  = 0b1000;
136    }
137}
138
139/// A keyboard event combining a key code and optional modifiers.
140///
141/// # Example
142///
143/// ```rust
144/// use ratatui_testlib::events::{KeyCode, Modifiers, KeyEvent};
145///
146/// // Simple key press
147/// let key = KeyEvent::new(KeyCode::Char('a'));
148///
149/// // Ctrl+C
150/// let ctrl_c = KeyEvent::with_modifiers(
151///     KeyCode::Char('c'),
152///     Modifiers::CTRL
153/// );
154///
155/// // Encode to bytes for sending to PTY
156/// let bytes = ctrl_c.to_bytes();
157/// ```
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub struct KeyEvent {
160    /// The key code.
161    pub code: KeyCode,
162
163    /// Modifier keys held during the key press.
164    pub modifiers: Modifiers,
165}
166
167impl KeyEvent {
168    /// Creates a new key event without modifiers.
169    ///
170    /// # Example
171    ///
172    /// ```rust
173    /// use ratatui_testlib::events::{KeyCode, KeyEvent};
174    ///
175    /// let key = KeyEvent::new(KeyCode::Char('a'));
176    /// ```
177    pub fn new(code: KeyCode) -> Self {
178        Self {
179            code,
180            modifiers: Modifiers::empty(),
181        }
182    }
183
184    /// Creates a new key event with modifiers.
185    ///
186    /// # Example
187    ///
188    /// ```rust
189    /// use ratatui_testlib::events::{KeyCode, Modifiers, KeyEvent};
190    ///
191    /// let ctrl_c = KeyEvent::with_modifiers(
192    ///     KeyCode::Char('c'),
193    ///     Modifiers::CTRL
194    /// );
195    /// ```
196    pub fn with_modifiers(code: KeyCode, modifiers: Modifiers) -> Self {
197        Self { code, modifiers }
198    }
199
200    /// Converts the key event to bytes suitable for sending to a PTY.
201    ///
202    /// This generates standard VT100/ANSI escape sequences that are
203    /// compatible with most terminal applications.
204    ///
205    /// # Returns
206    ///
207    /// A vector of bytes representing the escape sequence for this key event.
208    ///
209    /// # Example
210    ///
211    /// ```rust
212    /// use ratatui_testlib::events::{KeyCode, Modifiers, KeyEvent};
213    ///
214    /// let key = KeyEvent::new(KeyCode::Char('a'));
215    /// let bytes = key.to_bytes();
216    /// assert_eq!(bytes, b"a");
217    ///
218    /// let ctrl_c = KeyEvent::with_modifiers(
219    ///     KeyCode::Char('c'),
220    ///     Modifiers::CTRL
221    /// );
222    /// let bytes = ctrl_c.to_bytes();
223    /// assert_eq!(bytes, vec![3]); // Ctrl+C = 0x03
224    /// ```
225    pub fn to_bytes(&self) -> Vec<u8> {
226        encode_key_event(self)
227    }
228}
229
230/// Encodes a key event into VT100/ANSI escape sequence bytes.
231///
232/// This function handles:
233/// - Regular characters
234/// - Control key combinations (Ctrl+A-Z)
235/// - Alt key combinations (ESC + key)
236/// - Special keys (arrows, function keys, etc.)
237/// - Navigation keys (Home, End, PageUp, PageDown)
238///
239/// # Arguments
240///
241/// * `event` - The key event to encode
242///
243/// # Returns
244///
245/// A vector of bytes representing the escape sequence.
246pub fn encode_key_event(event: &KeyEvent) -> Vec<u8> {
247    // Handle Ctrl modifier first for character keys
248    if event.modifiers.contains(Modifiers::CTRL) {
249        if let KeyCode::Char(c) = event.code {
250            // Ctrl+A-Z maps to 1-26
251            // Ctrl+[ = ESC (27), Ctrl+\ = 28, Ctrl+] = 29, Ctrl+^ = 30, Ctrl+_ = 31
252            return encode_ctrl_char(c);
253        }
254    }
255
256    // Handle Alt modifier for character keys
257    if event.modifiers.contains(Modifiers::ALT) {
258        if let KeyCode::Char(c) = event.code {
259            // Alt+key = ESC + key
260            let mut bytes = vec![0x1b]; // ESC
261            bytes.extend_from_slice(c.to_string().as_bytes());
262            return bytes;
263        }
264    }
265
266    // Handle unmodified keys
267    match event.code {
268        KeyCode::Char(c) => c.to_string().into_bytes(),
269        KeyCode::Enter => b"\n".to_vec(),
270        KeyCode::Tab => b"\t".to_vec(),
271        KeyCode::Esc => vec![0x1b],
272        KeyCode::Backspace => vec![0x7f], // DEL character
273        KeyCode::Delete => b"\x1b[3~".to_vec(),
274        KeyCode::Insert => b"\x1b[2~".to_vec(),
275        KeyCode::Up => b"\x1b[A".to_vec(),
276        KeyCode::Down => b"\x1b[B".to_vec(),
277        KeyCode::Right => b"\x1b[C".to_vec(),
278        KeyCode::Left => b"\x1b[D".to_vec(),
279        KeyCode::Home => b"\x1b[H".to_vec(),
280        KeyCode::End => b"\x1b[F".to_vec(),
281        KeyCode::PageUp => b"\x1b[5~".to_vec(),
282        KeyCode::PageDown => b"\x1b[6~".to_vec(),
283        KeyCode::F(n) => encode_function_key(n),
284    }
285}
286
287/// Encodes Ctrl+character combinations.
288///
289/// Ctrl key combinations use the ASCII control character range:
290/// - Ctrl+A = 0x01
291/// - Ctrl+B = 0x02
292/// - ...
293/// - Ctrl+Z = 0x1A
294/// - Ctrl+[ = 0x1B (ESC)
295/// - Ctrl+\ = 0x1C
296/// - Ctrl+] = 0x1D
297/// - Ctrl+^ = 0x1E (often Ctrl+Shift+6)
298/// - Ctrl+_ = 0x1F (often Ctrl+Shift+-)
299///
300/// # Arguments
301///
302/// * `c` - The character to combine with Ctrl
303///
304/// # Returns
305///
306/// A vector containing the control character byte.
307fn encode_ctrl_char(c: char) -> Vec<u8> {
308    let c_upper = c.to_ascii_uppercase();
309
310    let byte = match c_upper {
311        // Ctrl+A through Ctrl+Z
312        'A'..='Z' => (c_upper as u8) - b'A' + 1,
313
314        // Special control characters
315        '@' => 0,      // Ctrl+@ = NUL
316        '[' => 27,     // Ctrl+[ = ESC
317        '\\' => 28,    // Ctrl+\ = FS
318        ']' => 29,     // Ctrl+] = GS
319        '^' => 30,     // Ctrl+^ = RS
320        '_' => 31,     // Ctrl+_ = US
321        '?' => 127,    // Ctrl+? = DEL
322
323        // For lowercase, convert to uppercase
324        'a'..='z' => (c_upper as u8) - b'A' + 1,
325
326        // For other characters, try to map sensibly
327        _ => {
328            // Default: just send the character unchanged
329            return c.to_string().into_bytes();
330        }
331    };
332
333    vec![byte]
334}
335
336/// Encodes function keys (F1-F12) to their VT100 escape sequences.
337///
338/// Function key mappings:
339/// - F1-F4 use SS3 sequences (ESC O ...)
340/// - F5-F12 use CSI sequences (ESC [ ... ~)
341///
342/// # Arguments
343///
344/// * `n` - Function key number (1-12)
345///
346/// # Returns
347///
348/// A vector containing the escape sequence for the function key.
349fn encode_function_key(n: u8) -> Vec<u8> {
350    match n {
351        // F1-F4 use SS3 (ESC O) sequences
352        1 => b"\x1bOP".to_vec(),
353        2 => b"\x1bOQ".to_vec(),
354        3 => b"\x1bOR".to_vec(),
355        4 => b"\x1bOS".to_vec(),
356
357        // F5-F12 use CSI (ESC [) sequences
358        5 => b"\x1b[15~".to_vec(),
359        6 => b"\x1b[17~".to_vec(),
360        7 => b"\x1b[18~".to_vec(),
361        8 => b"\x1b[19~".to_vec(),
362        9 => b"\x1b[20~".to_vec(),
363        10 => b"\x1b[21~".to_vec(),
364        11 => b"\x1b[23~".to_vec(),
365        12 => b"\x1b[24~".to_vec(),
366
367        // For invalid function key numbers, return empty sequence
368        _ => Vec::new(),
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375
376    #[test]
377    fn test_key_event_new() {
378        let event = KeyEvent::new(KeyCode::Char('a'));
379        assert_eq!(event.code, KeyCode::Char('a'));
380        assert_eq!(event.modifiers, Modifiers::empty());
381    }
382
383    #[test]
384    fn test_key_event_with_modifiers() {
385        let event = KeyEvent::with_modifiers(KeyCode::Char('c'), Modifiers::CTRL);
386        assert_eq!(event.code, KeyCode::Char('c'));
387        assert_eq!(event.modifiers, Modifiers::CTRL);
388    }
389
390    #[test]
391    fn test_encode_simple_char() {
392        let event = KeyEvent::new(KeyCode::Char('a'));
393        assert_eq!(event.to_bytes(), b"a");
394
395        let event = KeyEvent::new(KeyCode::Char('Z'));
396        assert_eq!(event.to_bytes(), b"Z");
397
398        let event = KeyEvent::new(KeyCode::Char('5'));
399        assert_eq!(event.to_bytes(), b"5");
400    }
401
402    #[test]
403    fn test_encode_special_chars() {
404        let event = KeyEvent::new(KeyCode::Enter);
405        assert_eq!(event.to_bytes(), b"\n");
406
407        let event = KeyEvent::new(KeyCode::Tab);
408        assert_eq!(event.to_bytes(), b"\t");
409
410        let event = KeyEvent::new(KeyCode::Esc);
411        assert_eq!(event.to_bytes(), vec![0x1b]);
412
413        let event = KeyEvent::new(KeyCode::Backspace);
414        assert_eq!(event.to_bytes(), vec![0x7f]);
415    }
416
417    #[test]
418    fn test_encode_navigation_keys() {
419        let event = KeyEvent::new(KeyCode::Up);
420        assert_eq!(event.to_bytes(), b"\x1b[A");
421
422        let event = KeyEvent::new(KeyCode::Down);
423        assert_eq!(event.to_bytes(), b"\x1b[B");
424
425        let event = KeyEvent::new(KeyCode::Right);
426        assert_eq!(event.to_bytes(), b"\x1b[C");
427
428        let event = KeyEvent::new(KeyCode::Left);
429        assert_eq!(event.to_bytes(), b"\x1b[D");
430
431        let event = KeyEvent::new(KeyCode::Home);
432        assert_eq!(event.to_bytes(), b"\x1b[H");
433
434        let event = KeyEvent::new(KeyCode::End);
435        assert_eq!(event.to_bytes(), b"\x1b[F");
436    }
437
438    #[test]
439    fn test_encode_page_keys() {
440        let event = KeyEvent::new(KeyCode::PageUp);
441        assert_eq!(event.to_bytes(), b"\x1b[5~");
442
443        let event = KeyEvent::new(KeyCode::PageDown);
444        assert_eq!(event.to_bytes(), b"\x1b[6~");
445    }
446
447    #[test]
448    fn test_encode_delete_insert() {
449        let event = KeyEvent::new(KeyCode::Delete);
450        assert_eq!(event.to_bytes(), b"\x1b[3~");
451
452        let event = KeyEvent::new(KeyCode::Insert);
453        assert_eq!(event.to_bytes(), b"\x1b[2~");
454    }
455
456    #[test]
457    fn test_encode_function_keys() {
458        // F1-F4 use SS3 sequences
459        let event = KeyEvent::new(KeyCode::F(1));
460        assert_eq!(event.to_bytes(), b"\x1bOP");
461
462        let event = KeyEvent::new(KeyCode::F(2));
463        assert_eq!(event.to_bytes(), b"\x1bOQ");
464
465        let event = KeyEvent::new(KeyCode::F(3));
466        assert_eq!(event.to_bytes(), b"\x1bOR");
467
468        let event = KeyEvent::new(KeyCode::F(4));
469        assert_eq!(event.to_bytes(), b"\x1bOS");
470
471        // F5-F12 use CSI sequences
472        let event = KeyEvent::new(KeyCode::F(5));
473        assert_eq!(event.to_bytes(), b"\x1b[15~");
474
475        let event = KeyEvent::new(KeyCode::F(12));
476        assert_eq!(event.to_bytes(), b"\x1b[24~");
477    }
478
479    #[test]
480    fn test_encode_ctrl_combinations() {
481        // Ctrl+A = 0x01
482        let event = KeyEvent::with_modifiers(KeyCode::Char('a'), Modifiers::CTRL);
483        assert_eq!(event.to_bytes(), vec![1]);
484
485        // Ctrl+C = 0x03
486        let event = KeyEvent::with_modifiers(KeyCode::Char('c'), Modifiers::CTRL);
487        assert_eq!(event.to_bytes(), vec![3]);
488
489        // Ctrl+D = 0x04 (EOF)
490        let event = KeyEvent::with_modifiers(KeyCode::Char('d'), Modifiers::CTRL);
491        assert_eq!(event.to_bytes(), vec![4]);
492
493        // Ctrl+Z = 0x1A
494        let event = KeyEvent::with_modifiers(KeyCode::Char('z'), Modifiers::CTRL);
495        assert_eq!(event.to_bytes(), vec![26]);
496
497        // Ctrl+[ = ESC (0x1B)
498        let event = KeyEvent::with_modifiers(KeyCode::Char('['), Modifiers::CTRL);
499        assert_eq!(event.to_bytes(), vec![27]);
500    }
501
502    #[test]
503    fn test_encode_ctrl_uppercase() {
504        // Ctrl+A and Ctrl+a should produce the same result
505        let event_lower = KeyEvent::with_modifiers(KeyCode::Char('a'), Modifiers::CTRL);
506        let event_upper = KeyEvent::with_modifiers(KeyCode::Char('A'), Modifiers::CTRL);
507        assert_eq!(event_lower.to_bytes(), event_upper.to_bytes());
508    }
509
510    #[test]
511    fn test_encode_alt_combinations() {
512        // Alt+a = ESC + 'a'
513        let event = KeyEvent::with_modifiers(KeyCode::Char('a'), Modifiers::ALT);
514        assert_eq!(event.to_bytes(), b"\x1ba");
515
516        // Alt+x = ESC + 'x'
517        let event = KeyEvent::with_modifiers(KeyCode::Char('x'), Modifiers::ALT);
518        assert_eq!(event.to_bytes(), b"\x1bx");
519    }
520
521    #[test]
522    fn test_modifier_combinations() {
523        let ctrl = Modifiers::CTRL;
524        let shift = Modifiers::SHIFT;
525        let alt = Modifiers::ALT;
526
527        let ctrl_shift = ctrl | shift;
528        assert!(ctrl_shift.contains(Modifiers::CTRL));
529        assert!(ctrl_shift.contains(Modifiers::SHIFT));
530        assert!(!ctrl_shift.contains(Modifiers::ALT));
531
532        let ctrl_alt = ctrl | alt;
533        assert!(ctrl_alt.contains(Modifiers::CTRL));
534        assert!(ctrl_alt.contains(Modifiers::ALT));
535    }
536
537    #[test]
538    fn test_keycode_equality() {
539        assert_eq!(KeyCode::Char('a'), KeyCode::Char('a'));
540        assert_ne!(KeyCode::Char('a'), KeyCode::Char('b'));
541        assert_eq!(KeyCode::Enter, KeyCode::Enter);
542        assert_eq!(KeyCode::F(1), KeyCode::F(1));
543        assert_ne!(KeyCode::F(1), KeyCode::F(2));
544    }
545
546    #[test]
547    fn test_key_event_equality() {
548        let event1 = KeyEvent::new(KeyCode::Char('a'));
549        let event2 = KeyEvent::new(KeyCode::Char('a'));
550        assert_eq!(event1, event2);
551
552        let event3 = KeyEvent::with_modifiers(KeyCode::Char('a'), Modifiers::CTRL);
553        assert_ne!(event1, event3);
554    }
555}