1use crate::chord::{KeyChord, KeyKind, SpecialKey};
42
43pub fn key_to_ansi(chord: &KeyChord) -> Option<Vec<u8>> {
53 key_to_ansi_with_mode(chord, false)
54}
55
56pub fn key_to_ansi_with_mode(
61 chord: &KeyChord,
62 cursor_keys_application_mode: bool,
63) -> Option<Vec<u8>> {
64 let mods = chord.mods;
65
66 if mods.alt() {
72 let stripped = KeyChord {
73 key: chord.key,
74 mods: mods.without(crate::chord::KeyMods::ALT),
75 };
76 return key_to_ansi_with_mode(&stripped, cursor_keys_application_mode).map(|mut bytes| {
77 let mut out = Vec::with_capacity(1 + bytes.len());
78 out.push(0x1b);
79 out.append(&mut bytes);
80 out
81 });
82 }
83
84 if mods.ctrl()
90 && let KeyKind::Char(c) = chord.key
91 && let Some(b) = ctrl_char_byte(c)
92 {
93 return Some(vec![b]);
94 }
95
96 match chord.key {
97 KeyKind::Char(c) => {
98 let mut buf = [0u8; 4];
102 let s = c.encode_utf8(&mut buf);
103 Some(s.as_bytes().to_vec())
104 }
105 KeyKind::Special(SpecialKey::Enter) => Some(vec![b'\r']),
106 KeyKind::Special(SpecialKey::Tab) => {
107 if mods.shift() {
108 Some(b"\x1b[Z".to_vec())
109 } else {
110 Some(vec![b'\t'])
111 }
112 }
113 KeyKind::Special(SpecialKey::Backspace) => Some(vec![0x7f]),
114 KeyKind::Special(SpecialKey::Esc) => Some(vec![0x1b]),
115 KeyKind::Special(SpecialKey::Space) => Some(vec![b' ']),
121 KeyKind::Special(SpecialKey::Up) => {
123 Some(cursor_key(b'A', mods, cursor_keys_application_mode))
124 }
125 KeyKind::Special(SpecialKey::Down) => {
126 Some(cursor_key(b'B', mods, cursor_keys_application_mode))
127 }
128 KeyKind::Special(SpecialKey::Right) => {
129 Some(cursor_key(b'C', mods, cursor_keys_application_mode))
130 }
131 KeyKind::Special(SpecialKey::Left) => {
132 Some(cursor_key(b'D', mods, cursor_keys_application_mode))
133 }
134 KeyKind::Special(SpecialKey::Home) => {
135 Some(cursor_key(b'H', mods, cursor_keys_application_mode))
136 }
137 KeyKind::Special(SpecialKey::End) => {
138 Some(cursor_key(b'F', mods, cursor_keys_application_mode))
139 }
140 KeyKind::Special(SpecialKey::PageUp) => Some(tilde_key(5, mods)),
142 KeyKind::Special(SpecialKey::PageDown) => Some(tilde_key(6, mods)),
143 KeyKind::Special(SpecialKey::Insert) => Some(tilde_key(2, mods)),
144 KeyKind::Special(SpecialKey::Delete) => Some(tilde_key(3, mods)),
145 KeyKind::Special(SpecialKey::F(n)) => fn_key(n, mods),
147 }
148}
149
150fn cursor_key(letter: u8, mods: crate::chord::KeyMods, app_mode: bool) -> Vec<u8> {
156 let mod_param = modifier_param(mods);
157 if mod_param == 1 {
158 if app_mode {
159 vec![0x1b, b'O', letter]
160 } else {
161 vec![0x1b, b'[', letter]
162 }
163 } else {
164 let mut out = Vec::with_capacity(8);
165 out.extend_from_slice(b"\x1b[1;");
166 out.extend_from_slice(mod_param.to_string().as_bytes());
167 out.push(letter);
168 out
169 }
170}
171
172fn tilde_key(n: u16, mods: crate::chord::KeyMods) -> Vec<u8> {
177 let mod_param = modifier_param(mods);
178 let mut out = Vec::with_capacity(8);
179 out.extend_from_slice(b"\x1b[");
180 out.extend_from_slice(n.to_string().as_bytes());
181 if mod_param != 1 {
182 out.push(b';');
183 out.extend_from_slice(mod_param.to_string().as_bytes());
184 }
185 out.push(b'~');
186 out
187}
188
189fn fn_key(n: u8, mods: crate::chord::KeyMods) -> Option<Vec<u8>> {
196 let mod_param = modifier_param(mods);
197 let ss3_letter = match n {
200 1 => Some(b'P'),
201 2 => Some(b'Q'),
202 3 => Some(b'R'),
203 4 => Some(b'S'),
204 _ => None,
205 };
206 if let Some(letter) = ss3_letter {
207 if mod_param == 1 {
208 return Some(vec![0x1b, b'O', letter]);
209 }
210 let mut out = Vec::with_capacity(8);
211 out.extend_from_slice(b"\x1b[1;");
212 out.extend_from_slice(mod_param.to_string().as_bytes());
213 out.push(letter);
214 return Some(out);
215 }
216 let param = match n {
219 5 => 15,
220 6 => 17,
221 7 => 18,
222 8 => 19,
223 9 => 20,
224 10 => 21,
225 11 => 23,
226 12 => 24,
227 _ => return None,
228 };
229 Some(tilde_key(param, mods))
230}
231
232fn modifier_param(mods: crate::chord::KeyMods) -> u8 {
241 let mut bits: u8 = 0;
242 if mods.shift() {
243 bits |= 0b001;
244 }
245 if mods.alt() {
246 bits |= 0b010;
247 }
248 if mods.ctrl() {
249 bits |= 0b100;
250 }
251 bits + 1
252}
253
254fn ctrl_char_byte(c: char) -> Option<u8> {
270 match c {
271 'a'..='z' => Some(c as u8 - b'a' + 1),
272 'A'..='Z' => Some(c as u8 - b'A' + 1),
273 '[' => Some(0x1b),
274 '\\' => Some(0x1c),
275 ']' => Some(0x1d),
276 '^' | '~' => Some(0x1e),
277 '_' | '?' => Some(0x1f),
278 ' ' | '@' => Some(0x00),
279 _ => None,
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use crate::chord::KeyMods;
287
288 fn bare(key: KeyKind) -> KeyChord {
289 KeyChord {
290 key,
291 mods: KeyMods::NONE,
292 }
293 }
294
295 fn ctrl(c: char) -> KeyChord {
296 KeyChord::ctrl(c)
297 }
298
299 #[test]
300 fn printable_ascii_passes_through_as_one_byte() {
301 assert_eq!(key_to_ansi(&bare(KeyKind::Char('a'))), Some(b"a".to_vec()));
302 assert_eq!(key_to_ansi(&bare(KeyKind::Char('Z'))), Some(b"Z".to_vec()));
303 assert_eq!(key_to_ansi(&bare(KeyKind::Char('0'))), Some(b"0".to_vec()));
304 assert_eq!(key_to_ansi(&bare(KeyKind::Char(' '))), Some(b" ".to_vec()));
305 assert_eq!(key_to_ansi(&bare(KeyKind::Char('-'))), Some(b"-".to_vec()));
306 }
307
308 #[test]
309 fn enter_tab_backspace_esc_map_to_canonical_bytes() {
310 assert_eq!(
311 key_to_ansi(&bare(KeyKind::Special(SpecialKey::Enter))),
312 Some(vec![b'\r']),
313 );
314 assert_eq!(
315 key_to_ansi(&bare(KeyKind::Special(SpecialKey::Tab))),
316 Some(vec![b'\t']),
317 );
318 assert_eq!(
319 key_to_ansi(&bare(KeyKind::Special(SpecialKey::Backspace))),
320 Some(vec![0x7f]),
321 );
322 assert_eq!(
323 key_to_ansi(&bare(KeyKind::Special(SpecialKey::Esc))),
324 Some(vec![0x1b]),
325 );
326 }
327
328 #[test]
329 fn ctrl_letters_map_to_low_control_bytes() {
330 assert_eq!(key_to_ansi(&ctrl('a')), Some(vec![0x01]));
331 assert_eq!(key_to_ansi(&ctrl('c')), Some(vec![0x03])); assert_eq!(key_to_ansi(&ctrl('d')), Some(vec![0x04])); assert_eq!(key_to_ansi(&ctrl('w')), Some(vec![0x17])); assert_eq!(key_to_ansi(&ctrl('z')), Some(vec![0x1a])); assert_eq!(key_to_ansi(&ctrl('A')), Some(vec![0x01]));
337 }
338
339 #[test]
340 fn ctrl_punctuation_maps_per_xterm_table() {
341 assert_eq!(key_to_ansi(&ctrl('[')), Some(vec![0x1b])); assert_eq!(key_to_ansi(&ctrl('\\')), Some(vec![0x1c]));
343 assert_eq!(key_to_ansi(&ctrl(']')), Some(vec![0x1d]));
344 assert_eq!(key_to_ansi(&ctrl('^')), Some(vec![0x1e]));
345 assert_eq!(key_to_ansi(&ctrl('_')), Some(vec![0x1f]));
346 assert_eq!(key_to_ansi(&ctrl(' ')), Some(vec![0x00]));
347 }
348
349 #[test]
350 fn shift_tab_becomes_backtab_csi_z() {
351 let backtab = KeyChord {
352 key: KeyKind::Special(SpecialKey::Tab),
353 mods: KeyMods::SHIFT,
354 };
355 assert_eq!(key_to_ansi(&backtab), Some(b"\x1b[Z".to_vec()));
356 }
357
358 fn special(k: SpecialKey, mods: KeyMods) -> KeyChord {
361 KeyChord {
362 key: KeyKind::Special(k),
363 mods,
364 }
365 }
366
367 #[test]
368 fn bare_arrows_emit_csi_letter_in_normal_mode() {
369 assert_eq!(
370 key_to_ansi(&bare(KeyKind::Special(SpecialKey::Up))),
371 Some(b"\x1b[A".to_vec()),
372 );
373 assert_eq!(
374 key_to_ansi(&bare(KeyKind::Special(SpecialKey::Down))),
375 Some(b"\x1b[B".to_vec()),
376 );
377 assert_eq!(
378 key_to_ansi(&bare(KeyKind::Special(SpecialKey::Right))),
379 Some(b"\x1b[C".to_vec()),
380 );
381 assert_eq!(
382 key_to_ansi(&bare(KeyKind::Special(SpecialKey::Left))),
383 Some(b"\x1b[D".to_vec()),
384 );
385 }
386
387 #[test]
388 fn bare_arrows_emit_ss3_letter_in_application_mode() {
389 assert_eq!(
392 key_to_ansi_with_mode(&bare(KeyKind::Special(SpecialKey::Up)), true),
393 Some(b"\x1bOA".to_vec()),
394 );
395 assert_eq!(
396 key_to_ansi_with_mode(&bare(KeyKind::Special(SpecialKey::Left)), true),
397 Some(b"\x1bOD".to_vec()),
398 );
399 }
400
401 #[test]
402 fn shifted_arrows_use_xterm_modifier_param() {
403 assert_eq!(
404 key_to_ansi(&special(SpecialKey::Up, KeyMods::SHIFT)),
405 Some(b"\x1b[1;2A".to_vec()),
406 );
407 assert_eq!(
408 key_to_ansi(&special(SpecialKey::Right, KeyMods::CTRL)),
409 Some(b"\x1b[1;5C".to_vec()),
410 );
411 assert_eq!(
413 key_to_ansi(&special(SpecialKey::Down, KeyMods::CTRL | KeyMods::SHIFT,)),
414 Some(b"\x1b[1;6B".to_vec()),
415 );
416 }
417
418 #[test]
419 fn home_end_follow_cursor_key_family() {
420 assert_eq!(
421 key_to_ansi(&bare(KeyKind::Special(SpecialKey::Home))),
422 Some(b"\x1b[H".to_vec()),
423 );
424 assert_eq!(
425 key_to_ansi(&bare(KeyKind::Special(SpecialKey::End))),
426 Some(b"\x1b[F".to_vec()),
427 );
428 }
429
430 #[test]
431 fn insert_delete_page_keys_emit_tilde_form() {
432 assert_eq!(
433 key_to_ansi(&bare(KeyKind::Special(SpecialKey::Insert))),
434 Some(b"\x1b[2~".to_vec()),
435 );
436 assert_eq!(
437 key_to_ansi(&bare(KeyKind::Special(SpecialKey::Delete))),
438 Some(b"\x1b[3~".to_vec()),
439 );
440 assert_eq!(
441 key_to_ansi(&bare(KeyKind::Special(SpecialKey::PageUp))),
442 Some(b"\x1b[5~".to_vec()),
443 );
444 assert_eq!(
445 key_to_ansi(&bare(KeyKind::Special(SpecialKey::PageDown))),
446 Some(b"\x1b[6~".to_vec()),
447 );
448 }
449
450 #[test]
451 fn modified_tilde_keys_carry_xterm_param() {
452 assert_eq!(
453 key_to_ansi(&special(SpecialKey::Delete, KeyMods::SHIFT)),
454 Some(b"\x1b[3;2~".to_vec()),
455 );
456 assert_eq!(
457 key_to_ansi(&special(SpecialKey::PageUp, KeyMods::CTRL)),
458 Some(b"\x1b[5;5~".to_vec()),
459 );
460 }
461
462 #[test]
463 fn f1_to_f4_use_ss3_form() {
464 for (n, letter) in [(1u8, b'P'), (2, b'Q'), (3, b'R'), (4, b'S')] {
465 assert_eq!(
466 key_to_ansi(&bare(KeyKind::Special(SpecialKey::F(n)))),
467 Some(vec![0x1b, b'O', letter]),
468 "F{n} should emit ESC O {}",
469 letter as char,
470 );
471 }
472 }
473
474 #[test]
475 fn f5_to_f12_use_tilde_form_with_xterm_ids() {
476 let cases = [
477 (5u8, 15u16),
478 (6, 17),
479 (7, 18),
480 (8, 19),
481 (9, 20),
482 (10, 21),
483 (11, 23),
484 (12, 24),
485 ];
486 for (n, param) in cases {
487 let expected = format!("\x1b[{param}~");
488 assert_eq!(
489 key_to_ansi(&bare(KeyKind::Special(SpecialKey::F(n)))),
490 Some(expected.into_bytes()),
491 "F{n} should emit ESC [ {param} ~",
492 );
493 }
494 }
495
496 #[test]
497 fn unmapped_fn_returns_none() {
498 assert_eq!(
501 key_to_ansi(&bare(KeyKind::Special(SpecialKey::F(13)))),
502 None,
503 );
504 assert_eq!(key_to_ansi(&bare(KeyKind::Special(SpecialKey::F(0)))), None,);
505 }
506
507 #[test]
508 fn alt_letter_uses_esc_prefix_meta_convention() {
509 let alt_x = KeyChord {
513 key: KeyKind::Char('x'),
514 mods: KeyMods::ALT,
515 };
516 assert_eq!(key_to_ansi(&alt_x), Some(b"\x1bx".to_vec()));
517 }
518
519 #[test]
520 fn alt_arrow_emits_double_esc_csi_idiom() {
521 let alt_left = special(SpecialKey::Left, KeyMods::ALT);
523 assert_eq!(key_to_ansi(&alt_left), Some(b"\x1b\x1b[D".to_vec()));
524 }
525}