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}