use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
#[derive(Debug, Clone, Copy, Default)]
pub struct EncodeContext {
pub application_cursor: bool,
}
pub fn encode(key: KeyEvent, ctx: EncodeContext) -> Option<Vec<u8>> {
if key.kind == KeyEventKind::Release {
return None;
}
let m = key.modifiers;
let ctrl = m.contains(KeyModifiers::CONTROL);
let alt = m.contains(KeyModifiers::ALT);
let shift = m.contains(KeyModifiers::SHIFT);
match key.code {
KeyCode::Char(c) => Some(encode_char(c, ctrl, alt)),
KeyCode::Enter => Some(modified_or(13, shift, ctrl, alt, b"\r")),
KeyCode::Tab => Some(modified_or(9, shift, ctrl, alt, b"\t")),
KeyCode::BackTab => Some(b"\x1b[Z".to_vec()),
KeyCode::Backspace if alt && !ctrl && !shift => Some(b"\x1b\x7f".to_vec()),
KeyCode::Backspace => Some(modified_or(127, shift, ctrl, alt, b"\x7f")),
KeyCode::Esc => Some(b"\x1b".to_vec()),
KeyCode::Left if alt && !ctrl && !shift => Some(b"\x1bb".to_vec()),
KeyCode::Right if alt && !ctrl && !shift => Some(b"\x1bf".to_vec()),
KeyCode::Left => Some(arrow_seq(b'D', shift, ctrl, alt, ctx.application_cursor)),
KeyCode::Right => Some(arrow_seq(b'C', shift, ctrl, alt, ctx.application_cursor)),
KeyCode::Up => Some(arrow_seq(b'A', shift, ctrl, alt, ctx.application_cursor)),
KeyCode::Down => Some(arrow_seq(b'B', shift, ctrl, alt, ctx.application_cursor)),
KeyCode::Home => Some(arrow_seq(b'H', shift, ctrl, alt, ctx.application_cursor)),
KeyCode::End => Some(arrow_seq(b'F', shift, ctrl, alt, ctx.application_cursor)),
KeyCode::PageUp => Some(tilde_seq(b"5", shift, ctrl, alt)),
KeyCode::PageDown => Some(tilde_seq(b"6", shift, ctrl, alt)),
KeyCode::Insert => Some(tilde_seq(b"2", shift, ctrl, alt)),
KeyCode::Delete => Some(tilde_seq(b"3", shift, ctrl, alt)),
KeyCode::F(n) => function_key(n, shift, ctrl, alt),
_ => None,
}
}
fn encode_char(c: char, ctrl: bool, alt: bool) -> Vec<u8> {
let bytes: Vec<u8> = if ctrl {
ctrl_char_bytes(c)
} else {
let mut s = [0u8; 4];
c.encode_utf8(&mut s).as_bytes().to_vec()
};
if alt {
let mut out = Vec::with_capacity(bytes.len() + 1);
out.push(0x1b);
out.extend_from_slice(&bytes);
out
} else {
bytes
}
}
fn ctrl_char_bytes(c: char) -> Vec<u8> {
let lc = c.to_ascii_lowercase();
let byte: u8 = match lc {
'a'..='z' => (lc as u8) & 0x1f,
'@' => 0x00,
'[' => 0x1b,
'\\' => 0x1c,
']' => 0x1d,
'^' => 0x1e,
'_' => 0x1f,
'?' => 0x7f, ' ' => 0x00, other => other as u8,
};
vec![byte]
}
#[allow(dead_code)]
fn prepend_alt(alt: bool, bytes: Vec<u8>) -> Option<Vec<u8>> {
if alt {
let mut out = Vec::with_capacity(bytes.len() + 1);
out.push(0x1b);
out.extend_from_slice(&bytes);
Some(out)
} else {
Some(bytes)
}
}
fn modified_or(keycode: u16, shift: bool, ctrl: bool, alt: bool, bare: &[u8]) -> Vec<u8> {
let code = modifier_code(shift, ctrl, alt);
if code == 1 {
return bare.to_vec();
}
let mut out = Vec::with_capacity(12);
out.extend_from_slice(b"\x1b[27;");
out.extend_from_slice(code.to_string().as_bytes());
out.push(b';');
out.extend_from_slice(keycode.to_string().as_bytes());
out.push(b'~');
out
}
fn arrow_seq(letter: u8, shift: bool, ctrl: bool, alt: bool, app_cursor: bool) -> Vec<u8> {
let code = modifier_code(shift, ctrl, alt);
if code == 1 {
if app_cursor {
vec![0x1b, b'O', letter]
} else {
vec![0x1b, b'[', letter]
}
} else {
let mut out = Vec::with_capacity(8);
out.extend_from_slice(b"\x1b[1;");
out.extend_from_slice(code.to_string().as_bytes());
out.push(letter);
out
}
}
fn tilde_seq(num: &[u8], shift: bool, ctrl: bool, alt: bool) -> Vec<u8> {
let code = modifier_code(shift, ctrl, alt);
let mut out = Vec::with_capacity(8);
out.extend_from_slice(b"\x1b[");
out.extend_from_slice(num);
if code != 1 {
out.push(b';');
out.extend_from_slice(code.to_string().as_bytes());
}
out.push(b'~');
out
}
fn modifier_code(shift: bool, ctrl: bool, alt: bool) -> u8 {
let mut code = 1u8;
if shift {
code += 1;
}
if alt {
code += 2;
}
if ctrl {
code += 4;
}
code
}
fn function_key(n: u8, shift: bool, ctrl: bool, alt: bool) -> Option<Vec<u8>> {
let code = modifier_code(shift, ctrl, alt);
match n {
1..=4 => {
let letter = b"PQRS"[(n - 1) as usize];
if code == 1 {
Some(vec![0x1b, b'O', letter])
} else {
let mut out = Vec::with_capacity(8);
out.extend_from_slice(b"\x1b[1;");
out.extend_from_slice(code.to_string().as_bytes());
out.push(letter);
Some(out)
}
}
5 => Some(tilde_seq(b"15", shift, ctrl, alt)),
6 => Some(tilde_seq(b"17", shift, ctrl, alt)),
7 => Some(tilde_seq(b"18", shift, ctrl, alt)),
8 => Some(tilde_seq(b"19", shift, ctrl, alt)),
9 => Some(tilde_seq(b"20", shift, ctrl, alt)),
10 => Some(tilde_seq(b"21", shift, ctrl, alt)),
11 => Some(tilde_seq(b"23", shift, ctrl, alt)),
12 => Some(tilde_seq(b"24", shift, ctrl, alt)),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
fn k(code: KeyCode, mods: KeyModifiers) -> KeyEvent {
KeyEvent::new(code, mods)
}
fn encode(key: KeyEvent) -> Option<Vec<u8>> {
super::encode(key, EncodeContext::default())
}
#[test]
fn plain_letter() {
assert_eq!(
encode(k(KeyCode::Char('a'), KeyModifiers::NONE)),
Some(b"a".to_vec())
);
}
#[test]
fn ctrl_letter() {
assert_eq!(
encode(k(KeyCode::Char('a'), KeyModifiers::CONTROL)),
Some(vec![0x01])
);
assert_eq!(
encode(k(KeyCode::Char('c'), KeyModifiers::CONTROL)),
Some(vec![0x03])
);
}
#[test]
fn alt_letter_prepends_esc() {
assert_eq!(
encode(k(KeyCode::Char('x'), KeyModifiers::ALT)),
Some(vec![0x1b, b'x'])
);
}
#[test]
fn enter_is_cr() {
assert_eq!(
encode(k(KeyCode::Enter, KeyModifiers::NONE)),
Some(b"\r".to_vec())
);
}
#[test]
fn shift_enter_uses_modify_other_keys() {
assert_eq!(
encode(k(KeyCode::Enter, KeyModifiers::SHIFT)),
Some(b"\x1b[27;2;13~".to_vec())
);
}
#[test]
fn ctrl_enter_uses_modify_other_keys() {
assert_eq!(
encode(k(KeyCode::Enter, KeyModifiers::CONTROL)),
Some(b"\x1b[27;5;13~".to_vec())
);
}
#[test]
fn ctrl_tab_uses_modify_other_keys() {
assert_eq!(
encode(k(KeyCode::Tab, KeyModifiers::CONTROL)),
Some(b"\x1b[27;5;9~".to_vec())
);
}
#[test]
fn backspace_is_del() {
assert_eq!(
encode(k(KeyCode::Backspace, KeyModifiers::NONE)),
Some(b"\x7f".to_vec())
);
}
#[test]
fn alt_backspace_deletes_word() {
assert_eq!(
encode(k(KeyCode::Backspace, KeyModifiers::ALT)),
Some(b"\x1b\x7f".to_vec())
);
}
#[test]
fn ctrl_backspace_uses_modify_other_keys() {
assert_eq!(
encode(k(KeyCode::Backspace, KeyModifiers::CONTROL)),
Some(b"\x1b[27;5;127~".to_vec())
);
}
#[test]
fn alt_left_right_move_by_word() {
assert_eq!(
encode(k(KeyCode::Left, KeyModifiers::ALT)),
Some(b"\x1bb".to_vec())
);
assert_eq!(
encode(k(KeyCode::Right, KeyModifiers::ALT)),
Some(b"\x1bf".to_vec())
);
}
#[test]
fn ctrl_alt_left_falls_through_to_csi() {
assert_eq!(
encode(k(KeyCode::Left, KeyModifiers::ALT | KeyModifiers::CONTROL)),
Some(b"\x1b[1;7D".to_vec())
);
}
#[test]
fn esc_is_esc() {
assert_eq!(
encode(k(KeyCode::Esc, KeyModifiers::NONE)),
Some(b"\x1b".to_vec())
);
}
#[test]
fn arrow_bare() {
assert_eq!(
encode(k(KeyCode::Up, KeyModifiers::NONE)),
Some(b"\x1b[A".to_vec())
);
assert_eq!(
encode(k(KeyCode::Left, KeyModifiers::NONE)),
Some(b"\x1b[D".to_vec())
);
}
#[test]
fn arrow_bare_application_cursor_mode() {
let ctx = EncodeContext {
application_cursor: true,
};
assert_eq!(
super::encode(k(KeyCode::Up, KeyModifiers::NONE), ctx),
Some(b"\x1bOA".to_vec())
);
assert_eq!(
super::encode(k(KeyCode::Down, KeyModifiers::NONE), ctx),
Some(b"\x1bOB".to_vec())
);
assert_eq!(
super::encode(k(KeyCode::Right, KeyModifiers::NONE), ctx),
Some(b"\x1bOC".to_vec())
);
assert_eq!(
super::encode(k(KeyCode::Left, KeyModifiers::NONE), ctx),
Some(b"\x1bOD".to_vec())
);
}
#[test]
fn arrow_with_modifier_ignores_application_cursor() {
let ctx = EncodeContext {
application_cursor: true,
};
assert_eq!(
super::encode(k(KeyCode::Up, KeyModifiers::SHIFT), ctx),
Some(b"\x1b[1;2A".to_vec())
);
}
#[test]
fn home_end_obey_application_cursor() {
let ctx = EncodeContext {
application_cursor: true,
};
assert_eq!(
super::encode(k(KeyCode::Home, KeyModifiers::NONE), ctx),
Some(b"\x1bOH".to_vec())
);
assert_eq!(
super::encode(k(KeyCode::End, KeyModifiers::NONE), ctx),
Some(b"\x1bOF".to_vec())
);
}
#[test]
fn arrow_with_shift() {
assert_eq!(
encode(k(KeyCode::Up, KeyModifiers::SHIFT)),
Some(b"\x1b[1;2A".to_vec())
);
}
#[test]
fn arrow_with_ctrl() {
assert_eq!(
encode(k(KeyCode::Right, KeyModifiers::CONTROL)),
Some(b"\x1b[1;5C".to_vec())
);
}
#[test]
fn pgup_bare_and_with_ctrl() {
assert_eq!(
encode(k(KeyCode::PageUp, KeyModifiers::NONE)),
Some(b"\x1b[5~".to_vec())
);
assert_eq!(
encode(k(KeyCode::PageUp, KeyModifiers::CONTROL)),
Some(b"\x1b[5;5~".to_vec())
);
}
#[test]
fn f1_uses_ss3() {
assert_eq!(
encode(k(KeyCode::F(1), KeyModifiers::NONE)),
Some(b"\x1bOP".to_vec())
);
}
#[test]
fn f5_uses_tilde() {
assert_eq!(
encode(k(KeyCode::F(5), KeyModifiers::NONE)),
Some(b"\x1b[15~".to_vec())
);
}
#[test]
fn release_is_dropped() {
let mut ev = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
ev.kind = KeyEventKind::Release;
assert_eq!(encode(ev), None);
}
}