use crate::input::{KeyCode, KeyEvent, KeyModifiers};
fn apply_shift(c: char, modifiers: KeyModifiers) -> char {
if modifiers.contains(KeyModifiers::SHIFT) && c.is_ascii_lowercase() {
c.to_ascii_uppercase()
} else {
c
}
}
pub(crate) fn key_to_bytes(
key: &KeyEvent,
application_cursor: bool,
backspace_bs: bool,
line_feed_new_line: bool,
) -> Option<Vec<u8>> {
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
let alt = key.modifiers.contains(KeyModifiers::ALT);
match key.code {
KeyCode::Char(c) if ctrl => {
let c = c.to_ascii_lowercase();
let byte = match c {
'a'..='z' => c as u8 - b'a' + 1,
'@' => 0,
'[' | '3' => 0x1b,
'\\' | '4' => 0x1c,
']' | '5' => 0x1d,
'^' | '6' => 0x1e,
'_' | '7' => 0x1f,
'?' | '8' => 0x7f,
_ => return None,
};
if alt {
Some(vec![0x1b, byte])
} else {
Some(vec![byte])
}
}
KeyCode::Char(c) if alt => {
let c = apply_shift(c, key.modifiers);
let mut out = vec![0x1b];
let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
out.extend_from_slice(s.as_bytes());
Some(out)
}
KeyCode::Char(c) => {
let c = apply_shift(c, key.modifiers);
let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
Some(s.as_bytes().to_vec())
}
KeyCode::Enter if line_feed_new_line => Some(b"\r\n".to_vec()),
KeyCode::Enter => Some(b"\r".to_vec()),
KeyCode::Backspace if alt && backspace_bs => Some(b"\x1b\x08".to_vec()),
KeyCode::Backspace if alt => Some(b"\x1b\x7f".to_vec()),
KeyCode::Backspace if backspace_bs => Some(vec![0x08]),
KeyCode::Backspace => Some(vec![0x7f]),
KeyCode::Tab => Some(b"\t".to_vec()),
KeyCode::BackTab => Some(b"\x1b[Z".to_vec()),
KeyCode::Esc => Some(b"\x1b".to_vec()),
KeyCode::Up if has_modifiers(key) => modified_key(b'A', key),
KeyCode::Down if has_modifiers(key) => modified_key(b'B', key),
KeyCode::Right if has_modifiers(key) => modified_key(b'C', key),
KeyCode::Left if has_modifiers(key) => modified_key(b'D', key),
KeyCode::Home if has_modifiers(key) => modified_key(b'H', key),
KeyCode::End if has_modifiers(key) => modified_key(b'F', key),
KeyCode::Up if application_cursor => Some(b"\x1bOA".to_vec()),
KeyCode::Down if application_cursor => Some(b"\x1bOB".to_vec()),
KeyCode::Right if application_cursor => Some(b"\x1bOC".to_vec()),
KeyCode::Left if application_cursor => Some(b"\x1bOD".to_vec()),
KeyCode::Home if application_cursor => Some(b"\x1bOH".to_vec()),
KeyCode::End if application_cursor => Some(b"\x1bOF".to_vec()),
KeyCode::Up => Some(b"\x1b[A".to_vec()),
KeyCode::Down => Some(b"\x1b[B".to_vec()),
KeyCode::Right => Some(b"\x1b[C".to_vec()),
KeyCode::Left => Some(b"\x1b[D".to_vec()),
KeyCode::Home => Some(b"\x1b[H".to_vec()),
KeyCode::End => Some(b"\x1b[F".to_vec()),
KeyCode::Insert if has_modifiers(key) => modified_tilde(2, key),
KeyCode::Delete if has_modifiers(key) => modified_tilde(3, key),
KeyCode::PageUp if has_modifiers(key) => modified_tilde(5, key),
KeyCode::PageDown if has_modifiers(key) => modified_tilde(6, key),
KeyCode::PageUp => Some(b"\x1b[5~".to_vec()),
KeyCode::PageDown => Some(b"\x1b[6~".to_vec()),
KeyCode::Insert => Some(b"\x1b[2~".to_vec()),
KeyCode::Delete => Some(b"\x1b[3~".to_vec()),
KeyCode::F(n) if has_modifiers(key) => modified_f_key(n, key),
KeyCode::F(n) => f_key_bytes(n),
_ => None,
}
}
fn has_modifiers(key: &KeyEvent) -> bool {
key.modifiers
.intersects(KeyModifiers::SHIFT | KeyModifiers::ALT | KeyModifiers::CONTROL)
}
fn xterm_modifier(key: &KeyEvent) -> u8 {
let mut m: u8 = 1;
if key.modifiers.contains(KeyModifiers::SHIFT) {
m += 1;
}
if key.modifiers.contains(KeyModifiers::ALT) {
m += 2;
}
if key.modifiers.contains(KeyModifiers::CONTROL) {
m += 4;
}
m
}
fn modified_key(final_byte: u8, key: &KeyEvent) -> Option<Vec<u8>> {
let m = xterm_modifier(key);
Some(format!("\x1b[1;{m}{}", final_byte as char).into_bytes())
}
fn modified_tilde(number: u8, key: &KeyEvent) -> Option<Vec<u8>> {
let m = xterm_modifier(key);
Some(format!("\x1b[{number};{m}~").into_bytes())
}
fn modified_f_key(n: u8, key: &KeyEvent) -> Option<Vec<u8>> {
let m = xterm_modifier(key);
let number = match n {
1 => 11,
2 => 12,
3 => 13,
4 => 14,
5 => 15,
6 => 17,
7 => 18,
8 => 19,
9 => 20,
10 => 21,
11 => 23,
12 => 24,
_ => return None,
};
Some(format!("\x1b[{number};{m}~").into_bytes())
}
fn f_key_bytes(n: u8) -> Option<Vec<u8>> {
let seq = match n {
1 => b"\x1bOP".as_slice(),
2 => b"\x1bOQ",
3 => b"\x1bOR",
4 => b"\x1bOS",
5 => b"\x1b[15~",
6 => b"\x1b[17~",
7 => b"\x1b[18~",
8 => b"\x1b[19~",
9 => b"\x1b[20~",
10 => b"\x1b[21~",
11 => b"\x1b[23~",
12 => b"\x1b[24~",
_ => return None,
};
Some(seq.to_vec())
}
#[cfg(test)]
mod tests {
use super::*;
fn key(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, KeyModifiers::NONE)
}
fn ctrl_key(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, KeyModifiers::CONTROL)
}
fn alt_key(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, KeyModifiers::ALT)
}
fn shift_key(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, KeyModifiers::SHIFT)
}
fn ctrl_alt_key(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, KeyModifiers::CONTROL | KeyModifiers::ALT)
}
#[test]
fn ascii_char() {
assert_eq!(
key_to_bytes(&key(KeyCode::Char('a')), false, false, false),
Some(b"a".to_vec())
);
}
#[test]
fn shift_char_lowercase_input() {
assert_eq!(
key_to_bytes(&shift_key(KeyCode::Char('a')), false, false, false),
Some(b"A".to_vec())
);
assert_eq!(
key_to_bytes(&shift_key(KeyCode::Char('z')), false, false, false),
Some(b"Z".to_vec())
);
}
#[test]
fn shift_char_uppercase_input() {
assert_eq!(
key_to_bytes(&shift_key(KeyCode::Char('A')), false, false, false),
Some(b"A".to_vec())
);
}
#[test]
fn ctrl_shift_char() {
let key = KeyEvent::new(
KeyCode::Char('A'),
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
);
assert_eq!(key_to_bytes(&key, false, false, false), Some(vec![1]));
let key = KeyEvent::new(
KeyCode::Char('a'),
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
);
assert_eq!(key_to_bytes(&key, false, false, false), Some(vec![1]));
}
#[test]
fn alt_shift_char() {
let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::ALT | KeyModifiers::SHIFT);
assert_eq!(
key_to_bytes(&key, false, false, false),
Some(b"\x1bA".to_vec())
);
}
#[test]
fn ctrl_c() {
assert_eq!(
key_to_bytes(&ctrl_key(KeyCode::Char('c')), false, false, false),
Some(vec![3])
);
}
#[test]
fn alt_char() {
assert_eq!(
key_to_bytes(&alt_key(KeyCode::Char('b')), false, false, false),
Some(b"\x1bb".to_vec())
);
assert_eq!(
key_to_bytes(&alt_key(KeyCode::Char('f')), false, false, false),
Some(b"\x1bf".to_vec())
);
assert_eq!(
key_to_bytes(&alt_key(KeyCode::Char('.')), false, false, false),
Some(b"\x1b.".to_vec())
);
}
#[test]
fn ctrl_alt_char() {
assert_eq!(
key_to_bytes(&ctrl_alt_key(KeyCode::Char('c')), false, false, false),
Some(vec![0x1b, 3])
);
}
#[test]
fn alt_backspace() {
assert_eq!(
key_to_bytes(&alt_key(KeyCode::Backspace), false, false, false),
Some(b"\x1b\x7f".to_vec())
);
}
#[test]
fn legacy_backspace_with_decbkm_returns_bs() {
assert_eq!(
key_to_bytes(&key(KeyCode::Backspace), false, true, false),
Some(vec![0x08])
);
}
#[test]
fn legacy_backspace_alt_with_decbkm_returns_esc_bs() {
assert_eq!(
key_to_bytes(&alt_key(KeyCode::Backspace), false, true, false),
Some(b"\x1b\x08".to_vec())
);
}
#[test]
fn legacy_backspace_decbkm_differential() {
let key = key(KeyCode::Backspace);
assert_eq!(
key_to_bytes(&key, false, false, false),
Some(vec![0x7f]),
"DECBKM cleared: Backspace must return DEL (0x7f)",
);
assert_eq!(
key_to_bytes(&key, false, true, false),
Some(vec![0x08]),
"DECBKM set: Backspace must return BS (0x08)",
);
}
#[test]
fn legacy_backspace_alt_decbkm_differential() {
let key = alt_key(KeyCode::Backspace);
assert_eq!(
key_to_bytes(&key, false, false, false),
Some(b"\x1b\x7f".to_vec()),
"DECBKM cleared: Alt+Backspace must return ESC DEL",
);
assert_eq!(
key_to_bytes(&key, false, true, false),
Some(b"\x1b\x08".to_vec()),
"DECBKM set: Alt+Backspace must return ESC BS",
);
}
#[test]
fn arrow_keys() {
assert_eq!(
key_to_bytes(&key(KeyCode::Up), false, false, false),
Some(b"\x1b[A".to_vec())
);
assert_eq!(
key_to_bytes(&key(KeyCode::Down), false, false, false),
Some(b"\x1b[B".to_vec())
);
}
#[test]
fn arrow_keys_application_cursor() {
assert_eq!(
key_to_bytes(&key(KeyCode::Up), true, false, false),
Some(b"\x1bOA".to_vec())
);
assert_eq!(
key_to_bytes(&key(KeyCode::Down), true, false, false),
Some(b"\x1bOB".to_vec())
);
assert_eq!(
key_to_bytes(&key(KeyCode::Right), true, false, false),
Some(b"\x1bOC".to_vec())
);
assert_eq!(
key_to_bytes(&key(KeyCode::Left), true, false, false),
Some(b"\x1bOD".to_vec())
);
assert_eq!(
key_to_bytes(&key(KeyCode::Home), true, false, false),
Some(b"\x1bOH".to_vec())
);
assert_eq!(
key_to_bytes(&key(KeyCode::End), true, false, false),
Some(b"\x1bOF".to_vec())
);
}
#[test]
fn shift_arrow_keys() {
assert_eq!(
key_to_bytes(&shift_key(KeyCode::Up), false, false, false),
Some(b"\x1b[1;2A".to_vec())
);
assert_eq!(
key_to_bytes(&shift_key(KeyCode::Down), false, false, false),
Some(b"\x1b[1;2B".to_vec())
);
assert_eq!(
key_to_bytes(&shift_key(KeyCode::Right), false, false, false),
Some(b"\x1b[1;2C".to_vec())
);
assert_eq!(
key_to_bytes(&shift_key(KeyCode::Left), false, false, false),
Some(b"\x1b[1;2D".to_vec())
);
}
#[test]
fn shift_home_end() {
assert_eq!(
key_to_bytes(&shift_key(KeyCode::Home), false, false, false),
Some(b"\x1b[1;2H".to_vec())
);
assert_eq!(
key_to_bytes(&shift_key(KeyCode::End), false, false, false),
Some(b"\x1b[1;2F".to_vec())
);
}
#[test]
fn ctrl_arrow_keys() {
assert_eq!(
key_to_bytes(&ctrl_key(KeyCode::Right), false, false, false),
Some(b"\x1b[1;5C".to_vec())
);
assert_eq!(
key_to_bytes(&ctrl_key(KeyCode::Left), false, false, false),
Some(b"\x1b[1;5D".to_vec())
);
}
#[test]
fn alt_arrow_keys() {
assert_eq!(
key_to_bytes(&alt_key(KeyCode::Up), false, false, false),
Some(b"\x1b[1;3A".to_vec())
);
}
#[test]
fn modified_tilde_keys() {
assert_eq!(
key_to_bytes(&shift_key(KeyCode::Delete), false, false, false),
Some(b"\x1b[3;2~".to_vec())
);
assert_eq!(
key_to_bytes(&ctrl_key(KeyCode::PageUp), false, false, false),
Some(b"\x1b[5;5~".to_vec())
);
}
#[test]
fn modified_f_keys() {
assert_eq!(
key_to_bytes(&shift_key(KeyCode::F(1)), false, false, false),
Some(b"\x1b[11;2~".to_vec())
);
assert_eq!(
key_to_bytes(&ctrl_key(KeyCode::F(5)), false, false, false),
Some(b"\x1b[15;5~".to_vec())
);
}
#[test]
fn enter_and_backspace() {
assert_eq!(
key_to_bytes(&key(KeyCode::Enter), false, false, false),
Some(b"\r".to_vec())
);
assert_eq!(
key_to_bytes(&key(KeyCode::Enter), false, false, true),
Some(b"\r\n".to_vec())
);
assert_eq!(
key_to_bytes(&key(KeyCode::Backspace), false, false, false),
Some(vec![0x7f])
);
}
#[test]
fn f_keys() {
assert_eq!(
key_to_bytes(&key(KeyCode::F(1)), false, false, false),
Some(b"\x1bOP".to_vec())
);
assert_eq!(
key_to_bytes(&key(KeyCode::F(5)), false, false, false),
Some(b"\x1b[15~".to_vec())
);
}
#[test]
fn unicode_char() {
let bytes = key_to_bytes(&key(KeyCode::Char('日')), false, false, false).unwrap();
assert_eq!(std::str::from_utf8(&bytes).unwrap(), "日");
}
}