use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
pub fn encode_key(key: KeyEvent, kitty_mode: bool) -> Vec<u8> {
if kitty_mode {
if let Some(bytes) = try_encode_csi_u(&key) {
return bytes;
}
}
encode_legacy(key)
}
fn try_encode_csi_u(key: &KeyEvent) -> Option<Vec<u8>> {
let mods = key.modifiers;
if mods.is_empty() {
return None;
}
if mods == KeyModifiers::CONTROL {
if let KeyCode::Char(c) = key.code {
if c.is_ascii_alphabetic() {
return None; }
}
}
match key.code {
KeyCode::Up | KeyCode::Down | KeyCode::Left | KeyCode::Right
| KeyCode::Home | KeyCode::End | KeyCode::PageUp | KeyCode::PageDown
| KeyCode::Insert | KeyCode::Delete | KeyCode::F(_) => {
return None; }
_ => {}
}
let codepoint = match key.code {
KeyCode::Char(c) => c as u32,
KeyCode::Enter => 13,
KeyCode::Tab => 9,
KeyCode::Backspace => 127,
KeyCode::Esc => 27,
_ => return None, };
let modifier = kitty_modifier(mods);
Some(format!("\x1b[{codepoint};{modifier}u").into_bytes())
}
fn encode_legacy(key: KeyEvent) -> Vec<u8> {
let mods = key.modifiers;
if !mods.is_empty() {
if let Some(bytes) = encode_modified_special(key.code, mods) {
return bytes;
}
}
if mods.contains(KeyModifiers::ALT) {
let inner = KeyEvent::new(key.code, mods.difference(KeyModifiers::ALT));
let mut bytes = vec![0x1b];
bytes.extend(encode_legacy_inner(inner));
return bytes;
}
encode_legacy_inner(key)
}
fn encode_modified_special(code: KeyCode, mods: KeyModifiers) -> Option<Vec<u8>> {
let modifier = xterm_modifier(mods);
if modifier <= 1 {
return None; }
match code {
KeyCode::Up => Some(format!("\x1b[1;{modifier}A").into_bytes()),
KeyCode::Down => Some(format!("\x1b[1;{modifier}B").into_bytes()),
KeyCode::Right => Some(format!("\x1b[1;{modifier}C").into_bytes()),
KeyCode::Left => Some(format!("\x1b[1;{modifier}D").into_bytes()),
KeyCode::Home => Some(format!("\x1b[1;{modifier}H").into_bytes()),
KeyCode::End => Some(format!("\x1b[1;{modifier}F").into_bytes()),
KeyCode::Insert => Some(format!("\x1b[2;{modifier}~").into_bytes()),
KeyCode::Delete => Some(format!("\x1b[3;{modifier}~").into_bytes()),
KeyCode::PageUp => Some(format!("\x1b[5;{modifier}~").into_bytes()),
KeyCode::PageDown => Some(format!("\x1b[6;{modifier}~").into_bytes()),
KeyCode::F(1) => Some(format!("\x1b[1;{modifier}P").into_bytes()),
KeyCode::F(2) => Some(format!("\x1b[1;{modifier}Q").into_bytes()),
KeyCode::F(3) => Some(format!("\x1b[1;{modifier}R").into_bytes()),
KeyCode::F(4) => Some(format!("\x1b[1;{modifier}S").into_bytes()),
KeyCode::F(n @ 5..=12) => {
let code = match n {
5 => 15,
6 => 17,
7 => 18,
8 => 19,
9 => 20,
10 => 21,
11 => 23,
12 => 24,
_ => unreachable!(),
};
Some(format!("\x1b[{code};{modifier}~").into_bytes())
}
_ => None,
}
}
fn xterm_modifier(mods: KeyModifiers) -> u32 {
let mut m = 1u32;
if mods.contains(KeyModifiers::SHIFT) {
m += 1;
}
if mods.contains(KeyModifiers::ALT) {
m += 2;
}
if mods.contains(KeyModifiers::CONTROL) {
m += 4;
}
m
}
fn kitty_modifier(mods: KeyModifiers) -> u32 {
let mut m = xterm_modifier(mods);
if mods.contains(KeyModifiers::SUPER) {
m += 8;
}
if mods.contains(KeyModifiers::HYPER) {
m += 16;
}
if mods.contains(KeyModifiers::META) {
m += 32;
}
m
}
fn encode_legacy_inner(key: KeyEvent) -> Vec<u8> {
match key.code {
KeyCode::Char(ch) => {
if key.modifiers.contains(KeyModifiers::CONTROL) {
let upper = ch.to_ascii_uppercase();
match upper {
'A'..='Z' => vec![upper as u8 - 64],
' ' | '@' | '2' => vec![0],
'[' | '3' => vec![27],
'\\' | '4' => vec![28],
']' | '5' => vec![29],
'^' | '6' => vec![30],
'_' | '7' | '-' => vec![31],
_ => vec![ch as u8],
}
} else {
let mut buf = [0u8; 4];
ch.encode_utf8(&mut buf).as_bytes().to_vec()
}
}
KeyCode::Enter => vec![b'\r'],
KeyCode::Backspace => vec![127],
KeyCode::Tab => vec![9],
KeyCode::BackTab => vec![27, 91, 90],
KeyCode::Esc => vec![27],
KeyCode::Left => vec![27, 91, 68],
KeyCode::Right => vec![27, 91, 67],
KeyCode::Up => vec![27, 91, 65],
KeyCode::Down => vec![27, 91, 66],
KeyCode::Home => vec![27, 91, 72],
KeyCode::End => vec![27, 91, 70],
KeyCode::PageUp => vec![27, 91, 53, 126],
KeyCode::PageDown => vec![27, 91, 54, 126],
KeyCode::Delete => vec![27, 91, 51, 126],
KeyCode::Insert => vec![27, 91, 50, 126],
KeyCode::F(n) => encode_f_key(n),
_ => vec![],
}
}
fn encode_f_key(n: u8) -> Vec<u8> {
match n {
1 => vec![27, 79, 80],
2 => vec![27, 79, 81],
3 => vec![27, 79, 82],
4 => vec![27, 79, 83],
5 => vec![27, 91, 49, 53, 126],
6 => vec![27, 91, 49, 55, 126],
7 => vec![27, 91, 49, 56, 126],
8 => vec![27, 91, 49, 57, 126],
9 => vec![27, 91, 50, 48, 126],
10 => vec![27, 91, 50, 49, 126],
11 => vec![27, 91, 50, 51, 126],
12 => vec![27, 91, 50, 52, 126],
_ => vec![],
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn legacy_enter() {
let key = KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
assert_eq!(encode_key(key, false), vec![b'\r']);
}
#[test]
fn legacy_ctrl_c() {
let key = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL);
assert_eq!(encode_key(key, false), vec![3]);
}
#[test]
fn legacy_shift_enter_is_just_cr() {
let key = KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT);
assert_eq!(encode_key(key, false), vec![b'\r']);
}
#[test]
fn legacy_alt_up() {
let key = KeyEvent::new(KeyCode::Up, KeyModifiers::ALT);
assert_eq!(encode_key(key, false), b"\x1b[1;3A");
}
#[test]
fn legacy_shift_right() {
let key = KeyEvent::new(KeyCode::Right, KeyModifiers::SHIFT);
assert_eq!(encode_key(key, false), b"\x1b[1;2C");
}
#[test]
fn legacy_ctrl_left() {
let key = KeyEvent::new(KeyCode::Left, KeyModifiers::CONTROL);
assert_eq!(encode_key(key, false), b"\x1b[1;5D");
}
#[test]
fn legacy_ctrl_shift_end() {
let key = KeyEvent::new(
KeyCode::End,
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
);
assert_eq!(encode_key(key, false), b"\x1b[1;6F");
}
#[test]
fn legacy_alt_delete() {
let key = KeyEvent::new(KeyCode::Delete, KeyModifiers::ALT);
assert_eq!(encode_key(key, false), b"\x1b[3;3~");
}
#[test]
fn legacy_shift_f5() {
let key = KeyEvent::new(KeyCode::F(5), KeyModifiers::SHIFT);
assert_eq!(encode_key(key, false), b"\x1b[15;2~");
}
#[test]
fn legacy_alt_char_still_esc_prefix() {
let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::ALT);
assert_eq!(encode_key(key, false), b"\x1ba");
}
#[test]
fn kitty_shift_enter() {
let key = KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT);
assert_eq!(encode_key(key, true), b"\x1b[13;2u");
}
#[test]
fn kitty_ctrl_shift_a() {
let key = KeyEvent::new(
KeyCode::Char('a'),
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
);
assert_eq!(encode_key(key, true), b"\x1b[97;6u");
}
#[test]
fn kitty_alt_enter() {
let key = KeyEvent::new(KeyCode::Enter, KeyModifiers::ALT);
assert_eq!(encode_key(key, true), b"\x1b[13;3u");
}
#[test]
fn kitty_plain_ctrl_c_uses_legacy() {
let key = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL);
assert_eq!(encode_key(key, true), vec![3]);
}
#[test]
fn kitty_unmodified_uses_legacy() {
let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty());
assert_eq!(encode_key(key, true), b"a");
}
#[test]
fn kitty_shift_tab() {
let key = KeyEvent::new(KeyCode::Tab, KeyModifiers::SHIFT);
assert_eq!(encode_key(key, true), b"\x1b[9;2u");
}
#[test]
fn kitty_ctrl_shift_enter() {
let key = KeyEvent::new(
KeyCode::Enter,
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
);
assert_eq!(encode_key(key, true), b"\x1b[13;6u");
}
}