#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Key {
Char(char),
Enter,
Esc,
Tab,
BackTab,
Backspace,
Delete,
Up,
Down,
Left,
Right,
Home,
End,
PageUp,
PageDown,
F(u8),
Ctrl(char),
Alt(char),
}
impl Key {
#[must_use]
#[track_caller]
pub fn encode(self) -> Vec<u8> {
match self {
Key::Char(c) => {
let mut buf = [0u8; 4];
c.encode_utf8(&mut buf).as_bytes().to_vec()
}
Key::Enter => vec![b'\r'],
Key::Esc => vec![0x1b],
Key::Tab => vec![b'\t'],
Key::BackTab => b"\x1b[Z".to_vec(),
Key::Backspace => vec![0x7f],
Key::Delete => b"\x1b[3~".to_vec(),
Key::Up => b"\x1b[A".to_vec(),
Key::Down => b"\x1b[B".to_vec(),
Key::Right => b"\x1b[C".to_vec(),
Key::Left => b"\x1b[D".to_vec(),
Key::Home => b"\x1b[H".to_vec(),
Key::End => b"\x1b[F".to_vec(),
Key::PageUp => b"\x1b[5~".to_vec(),
Key::PageDown => b"\x1b[6~".to_vec(),
Key::F(n) => match n {
1 => b"\x1bOP".to_vec(),
2 => b"\x1bOQ".to_vec(),
3 => b"\x1bOR".to_vec(),
4 => b"\x1bOS".to_vec(),
5 => b"\x1b[15~".to_vec(),
6 => b"\x1b[17~".to_vec(),
7 => b"\x1b[18~".to_vec(),
8 => b"\x1b[19~".to_vec(),
9 => b"\x1b[20~".to_vec(),
10 => b"\x1b[21~".to_vec(),
11 => b"\x1b[23~".to_vec(),
12 => b"\x1b[24~".to_vec(),
_ => panic!("Key::F({n}): only F1-F12 exist"),
},
Key::Ctrl(c) => vec![ctrl_byte(c)],
Key::Alt(c) => {
let mut out = vec![0x1b];
let mut buf = [0u8; 4];
out.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
out
}
}
}
}
#[track_caller]
fn ctrl_byte(c: char) -> u8 {
match c {
'a'..='z' => (c as u8) & 0x1f,
'A'..='Z' | '@' | '[' | '\\' | ']' | '^' | '_' => (c as u8) & 0x1f,
' ' => 0x00,
'?' => 0x7f,
_ => panic!("Key::Ctrl({c:?}): no control-code mapping for this character"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn xterm_encodings() {
let table: &[(Key, &[u8])] = &[
(Key::Char('j'), b"j"),
(Key::Char('é'), "é".as_bytes()),
(Key::Enter, b"\r"),
(Key::Esc, b"\x1b"),
(Key::Tab, b"\t"),
(Key::BackTab, b"\x1b[Z"),
(Key::Backspace, b"\x7f"),
(Key::Delete, b"\x1b[3~"),
(Key::Up, b"\x1b[A"),
(Key::Down, b"\x1b[B"),
(Key::Right, b"\x1b[C"),
(Key::Left, b"\x1b[D"),
(Key::Home, b"\x1b[H"),
(Key::End, b"\x1b[F"),
(Key::PageUp, b"\x1b[5~"),
(Key::PageDown, b"\x1b[6~"),
(Key::F(1), b"\x1bOP"),
(Key::F(4), b"\x1bOS"),
(Key::F(5), b"\x1b[15~"),
(Key::F(10), b"\x1b[21~"),
(Key::F(12), b"\x1b[24~"),
(Key::Ctrl('c'), b"\x03"),
(Key::Ctrl('C'), b"\x03"),
(Key::Ctrl('['), b"\x1b"),
(Key::Ctrl(' '), b"\x00"),
(Key::Ctrl('?'), b"\x7f"),
(Key::Alt('x'), b"\x1bx"),
];
for (key, bytes) in table {
assert_eq!(key.encode(), *bytes, "wrong encoding for {key:?}");
}
}
#[test]
#[should_panic(expected = "only F1-F12")]
fn f13_panics() {
let _ = Key::F(13).encode();
}
#[test]
#[should_panic(expected = "no control-code mapping")]
fn ctrl_digit_panics() {
let _ = Key::Ctrl('1').encode();
}
}