#[derive(Debug, Clone, PartialEq)]
pub enum KeyAction {
Char(char),
Ctrl(u8),
AltChar(char),
Enter,
Backspace,
Delete,
Insert,
Tab,
BackTab,
Escape,
ArrowUp,
ArrowDown,
ArrowLeft,
ArrowRight,
Home,
End,
PageUp,
PageDown,
Function(u8),
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackspaceMode {
Delete,
CtrlH,
}
impl BackspaceMode {
pub fn from_config(value: &str) -> Self {
if value.eq_ignore_ascii_case("ctrl_h") || value.eq_ignore_ascii_case("ctrl-h") {
Self::CtrlH
} else {
Self::Delete
}
}
}
pub fn action_to_bytes(
action: &KeyAction,
app_cursor: bool,
backspace: BackspaceMode,
) -> Option<Vec<u8>> {
match action {
KeyAction::Char(c) => {
let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
Some(s.as_bytes().to_vec())
}
KeyAction::Ctrl(code) => Some(vec![*code]),
KeyAction::AltChar(c) => {
let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
let mut result = vec![0x1b];
result.extend_from_slice(s.as_bytes());
Some(result)
}
KeyAction::Enter => Some(vec![b'\r']),
KeyAction::Backspace => Some(match backspace {
BackspaceMode::Delete => vec![0x7f],
BackspaceMode::CtrlH => vec![0x08],
}),
KeyAction::Delete => Some(b"\x1b[3~".to_vec()),
KeyAction::Insert => Some(b"\x1b[2~".to_vec()),
KeyAction::Tab => Some(vec![b'\t']),
KeyAction::BackTab => Some(b"\x1b[Z".to_vec()),
KeyAction::Escape => Some(vec![0x1b]),
KeyAction::ArrowUp => Some(if app_cursor {
b"\x1bOA".to_vec()
} else {
b"\x1b[A".to_vec()
}),
KeyAction::ArrowDown => Some(if app_cursor {
b"\x1bOB".to_vec()
} else {
b"\x1b[B".to_vec()
}),
KeyAction::ArrowLeft => Some(if app_cursor {
b"\x1bOD".to_vec()
} else {
b"\x1b[D".to_vec()
}),
KeyAction::ArrowRight => Some(if app_cursor {
b"\x1bOC".to_vec()
} else {
b"\x1b[C".to_vec()
}),
KeyAction::Home => Some(b"\x1b[H".to_vec()),
KeyAction::End => Some(b"\x1b[F".to_vec()),
KeyAction::PageUp => Some(b"\x1b[5~".to_vec()),
KeyAction::PageDown => Some(b"\x1b[6~".to_vec()),
KeyAction::Function(n) => function_key_bytes(*n),
KeyAction::None => None,
}
}
fn function_key_bytes(n: u8) -> Option<Vec<u8>> {
Some(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(),
_ => return None,
})
}
use winit::event::{ElementState, KeyEvent};
use winit::keyboard::{Key, KeyCode, ModifiersState, PhysicalKey};
pub fn winit_to_action(event: &KeyEvent, mods: &ModifiersState, option_as_meta: bool) -> KeyAction {
if event.state != ElementState::Pressed {
return KeyAction::None;
}
let ctrl = mods.control_key();
let alt = mods.alt_key();
if mods.super_key() {
return KeyAction::None;
}
match event.physical_key {
PhysicalKey::Code(KeyCode::Enter) => return KeyAction::Enter,
PhysicalKey::Code(KeyCode::Backspace) => return KeyAction::Backspace,
PhysicalKey::Code(KeyCode::Delete) => return KeyAction::Delete,
PhysicalKey::Code(KeyCode::Insert) => return KeyAction::Insert,
PhysicalKey::Code(KeyCode::Tab) if mods.shift_key() => return KeyAction::BackTab,
PhysicalKey::Code(KeyCode::Tab) => return KeyAction::Tab,
PhysicalKey::Code(KeyCode::Escape) => return KeyAction::Escape,
PhysicalKey::Code(KeyCode::ArrowUp) => return KeyAction::ArrowUp,
PhysicalKey::Code(KeyCode::ArrowDown) => return KeyAction::ArrowDown,
PhysicalKey::Code(KeyCode::ArrowLeft) => return KeyAction::ArrowLeft,
PhysicalKey::Code(KeyCode::ArrowRight) => return KeyAction::ArrowRight,
PhysicalKey::Code(KeyCode::Home) => return KeyAction::Home,
PhysicalKey::Code(KeyCode::End) => return KeyAction::End,
PhysicalKey::Code(KeyCode::PageUp) => return KeyAction::PageUp,
PhysicalKey::Code(KeyCode::PageDown) => return KeyAction::PageDown,
PhysicalKey::Code(KeyCode::F1) => return KeyAction::Function(1),
PhysicalKey::Code(KeyCode::F2) => return KeyAction::Function(2),
PhysicalKey::Code(KeyCode::F3) => return KeyAction::Function(3),
PhysicalKey::Code(KeyCode::F4) => return KeyAction::Function(4),
PhysicalKey::Code(KeyCode::F5) => return KeyAction::Function(5),
PhysicalKey::Code(KeyCode::F6) => return KeyAction::Function(6),
PhysicalKey::Code(KeyCode::F7) => return KeyAction::Function(7),
PhysicalKey::Code(KeyCode::F8) => return KeyAction::Function(8),
PhysicalKey::Code(KeyCode::F9) => return KeyAction::Function(9),
PhysicalKey::Code(KeyCode::F10) => return KeyAction::Function(10),
PhysicalKey::Code(KeyCode::F11) => return KeyAction::Function(11),
PhysicalKey::Code(KeyCode::F12) => return KeyAction::Function(12),
_ => {}
}
if ctrl
&& !alt
&& matches!(
&event.logical_key,
Key::Named(winit::keyboard::NamedKey::Space)
)
{
return KeyAction::Ctrl(0x00);
}
if ctrl
&& !alt
&& let Key::Character(ch) = &event.logical_key
&& let Some(byte) = ch.bytes().next()
&& byte.is_ascii_alphabetic()
{
let code = byte.to_ascii_lowercase() & 0x1f;
return KeyAction::Ctrl(code);
}
if option_as_meta
&& alt
&& !ctrl
&& let Some(text) = &event.text
&& let Some(c) = text.chars().next()
{
return KeyAction::AltChar(c);
}
if !ctrl
&& let Some(text) = &event.text
&& let Some(c) = text.chars().next()
{
return KeyAction::Char(c);
}
if !ctrl
&& let Key::Character(text) = &event.logical_key
&& let Some(c) = text.chars().next()
{
return KeyAction::Char(c);
}
if !ctrl
&& !alt
&& let PhysicalKey::Code(code) = event.physical_key
&& let Some(c) = physical_ascii_fallback(code, mods.shift_key())
{
return KeyAction::Char(c);
}
KeyAction::None
}
fn physical_ascii_fallback(code: KeyCode, shift: bool) -> Option<char> {
let c = match code {
KeyCode::Space => ' ',
KeyCode::KeyA => 'a',
KeyCode::KeyB => 'b',
KeyCode::KeyC => 'c',
KeyCode::KeyD => 'd',
KeyCode::KeyE => 'e',
KeyCode::KeyF => 'f',
KeyCode::KeyG => 'g',
KeyCode::KeyH => 'h',
KeyCode::KeyI => 'i',
KeyCode::KeyJ => 'j',
KeyCode::KeyK => 'k',
KeyCode::KeyL => 'l',
KeyCode::KeyM => 'm',
KeyCode::KeyN => 'n',
KeyCode::KeyO => 'o',
KeyCode::KeyP => 'p',
KeyCode::KeyQ => 'q',
KeyCode::KeyR => 'r',
KeyCode::KeyS => 's',
KeyCode::KeyT => 't',
KeyCode::KeyU => 'u',
KeyCode::KeyV => 'v',
KeyCode::KeyW => 'w',
KeyCode::KeyX => 'x',
KeyCode::KeyY => 'y',
KeyCode::KeyZ => 'z',
KeyCode::Digit0 => {
return Some(if shift { ')' } else { '0' });
}
KeyCode::Digit1 => {
return Some(if shift { '!' } else { '1' });
}
KeyCode::Digit2 => {
return Some(if shift { '@' } else { '2' });
}
KeyCode::Digit3 => {
return Some(if shift { '#' } else { '3' });
}
KeyCode::Digit4 => {
return Some(if shift { '$' } else { '4' });
}
KeyCode::Digit5 => {
return Some(if shift { '%' } else { '5' });
}
KeyCode::Digit6 => {
return Some(if shift { '^' } else { '6' });
}
KeyCode::Digit7 => {
return Some(if shift { '&' } else { '7' });
}
KeyCode::Digit8 => {
return Some(if shift { '*' } else { '8' });
}
KeyCode::Digit9 => {
return Some(if shift { '(' } else { '9' });
}
KeyCode::Minus => {
return Some(if shift { '_' } else { '-' });
}
KeyCode::Equal => {
return Some(if shift { '+' } else { '=' });
}
KeyCode::BracketLeft => {
return Some(if shift { '{' } else { '[' });
}
KeyCode::BracketRight => {
return Some(if shift { '}' } else { ']' });
}
KeyCode::Backslash => {
return Some(if shift { '|' } else { '\\' });
}
KeyCode::Semicolon => {
return Some(if shift { ':' } else { ';' });
}
KeyCode::Quote => {
return Some(if shift { '"' } else { '\'' });
}
KeyCode::Backquote => {
return Some(if shift { '~' } else { '`' });
}
KeyCode::Comma => {
return Some(if shift { '<' } else { ',' });
}
KeyCode::Period => {
return Some(if shift { '>' } else { '.' });
}
KeyCode::Slash => {
return Some(if shift { '?' } else { '/' });
}
_ => return None,
};
Some(if shift { c.to_ascii_uppercase() } else { c })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_enter() {
assert_eq!(
action_to_bytes(&KeyAction::Enter, false, BackspaceMode::Delete),
Some(vec![b'\r'])
);
}
#[test]
fn test_backspace() {
assert_eq!(
action_to_bytes(&KeyAction::Backspace, false, BackspaceMode::Delete),
Some(vec![0x7f])
);
assert_eq!(
action_to_bytes(&KeyAction::Backspace, false, BackspaceMode::CtrlH),
Some(vec![0x08])
);
}
#[test]
fn test_delete() {
assert_eq!(
action_to_bytes(&KeyAction::Delete, false, BackspaceMode::Delete),
Some(b"\x1b[3~".to_vec())
);
assert_eq!(
action_to_bytes(&KeyAction::Insert, false, BackspaceMode::Delete),
Some(b"\x1b[2~".to_vec())
);
assert_eq!(
action_to_bytes(&KeyAction::BackTab, false, BackspaceMode::Delete),
Some(b"\x1b[Z".to_vec())
);
}
#[test]
fn test_arrows_normal() {
assert_eq!(
action_to_bytes(&KeyAction::ArrowUp, false, BackspaceMode::Delete),
Some(b"\x1b[A".to_vec())
);
assert_eq!(
action_to_bytes(&KeyAction::ArrowDown, false, BackspaceMode::Delete),
Some(b"\x1b[B".to_vec())
);
assert_eq!(
action_to_bytes(&KeyAction::ArrowLeft, false, BackspaceMode::Delete),
Some(b"\x1b[D".to_vec())
);
assert_eq!(
action_to_bytes(&KeyAction::ArrowRight, false, BackspaceMode::Delete),
Some(b"\x1b[C".to_vec())
);
}
#[test]
fn test_arrows_app_cursor() {
assert_eq!(
action_to_bytes(&KeyAction::ArrowUp, true, BackspaceMode::Delete),
Some(b"\x1bOA".to_vec())
);
assert_eq!(
action_to_bytes(&KeyAction::ArrowDown, true, BackspaceMode::Delete),
Some(b"\x1bOB".to_vec())
);
assert_eq!(
action_to_bytes(&KeyAction::ArrowLeft, true, BackspaceMode::Delete),
Some(b"\x1bOD".to_vec())
);
assert_eq!(
action_to_bytes(&KeyAction::ArrowRight, true, BackspaceMode::Delete),
Some(b"\x1bOC".to_vec())
);
}
#[test]
fn test_control_codes() {
for byte in 0x01..=0x1a {
assert_eq!(
action_to_bytes(&KeyAction::Ctrl(byte), false, BackspaceMode::Delete),
Some(vec![byte])
);
}
}
#[test]
fn test_alt_char() {
assert_eq!(
action_to_bytes(&KeyAction::AltChar('a'), false, BackspaceMode::Delete),
Some(vec![0x1b, b'a'])
);
assert_eq!(
action_to_bytes(&KeyAction::AltChar('Z'), false, BackspaceMode::Delete),
Some(vec![0x1b, b'Z'])
);
}
#[test]
fn test_function_keys() {
assert_eq!(
action_to_bytes(&KeyAction::Function(1), false, BackspaceMode::Delete),
Some(b"\x1bOP".to_vec())
);
assert_eq!(
action_to_bytes(&KeyAction::Function(12), false, BackspaceMode::Delete),
Some(b"\x1b[24~".to_vec())
);
assert_eq!(
action_to_bytes(&KeyAction::Function(13), false, BackspaceMode::Delete),
None
);
}
#[test]
fn test_app_cursor_non_arrows() {
assert_eq!(
action_to_bytes(&KeyAction::Enter, false, BackspaceMode::Delete),
action_to_bytes(&KeyAction::Enter, true, BackspaceMode::Delete)
);
assert_eq!(
action_to_bytes(&KeyAction::Home, false, BackspaceMode::Delete),
action_to_bytes(&KeyAction::Home, true, BackspaceMode::Delete)
);
}
}