use crossterm::event::{KeyCode as CtKeyCode, KeyEvent as CtKeyEvent, KeyModifiers as CtKeyMods};
use hjkl_keymap::{KeyCode, KeyEvent, KeyModifiers};
pub fn from_crossterm(ev: &CtKeyEvent) -> Option<KeyEvent> {
use crossterm::event::KeyEventKind;
if ev.kind == KeyEventKind::Release {
return None;
}
let code = ct_code_to_keymap(ev.code)?;
let mut modifiers = ct_mods_to_keymap(ev.modifiers);
if matches!(code, KeyCode::Char(_)) {
modifiers.remove(KeyModifiers::SHIFT);
}
Some(KeyEvent::new(code, modifiers))
}
fn ct_code_to_keymap(code: CtKeyCode) -> Option<KeyCode> {
Some(match code {
CtKeyCode::Char(c) => KeyCode::Char(c),
CtKeyCode::Enter => KeyCode::Enter,
CtKeyCode::Esc => KeyCode::Esc,
CtKeyCode::Tab => KeyCode::Tab,
CtKeyCode::BackTab => {
KeyCode::Tab
}
CtKeyCode::Backspace => KeyCode::Backspace,
CtKeyCode::Delete => KeyCode::Delete,
CtKeyCode::Insert => KeyCode::Insert,
CtKeyCode::Up => KeyCode::Up,
CtKeyCode::Down => KeyCode::Down,
CtKeyCode::Left => KeyCode::Left,
CtKeyCode::Right => KeyCode::Right,
CtKeyCode::Home => KeyCode::Home,
CtKeyCode::End => KeyCode::End,
CtKeyCode::PageUp => KeyCode::PageUp,
CtKeyCode::PageDown => KeyCode::PageDown,
CtKeyCode::F(n) => KeyCode::F(n),
CtKeyCode::Null
| CtKeyCode::CapsLock
| CtKeyCode::ScrollLock
| CtKeyCode::NumLock
| CtKeyCode::PrintScreen
| CtKeyCode::Pause
| CtKeyCode::Menu
| CtKeyCode::KeypadBegin
| CtKeyCode::Media(_)
| CtKeyCode::Modifier(_) => return None,
#[allow(unreachable_patterns)]
_ => return None,
})
}
fn ct_mods_to_keymap(mods: CtKeyMods) -> KeyModifiers {
let mut out = KeyModifiers::NONE;
if mods.contains(CtKeyMods::SHIFT) {
out |= KeyModifiers::SHIFT;
}
if mods.contains(CtKeyMods::CONTROL) {
out |= KeyModifiers::CTRL;
}
if mods.contains(CtKeyMods::ALT) {
out |= KeyModifiers::ALT;
}
out
}
pub fn to_crossterm(ev: &KeyEvent) -> CtKeyEvent {
let code = match ev.code {
KeyCode::Char(c) => CtKeyCode::Char(c),
KeyCode::Enter => CtKeyCode::Enter,
KeyCode::Esc => CtKeyCode::Esc,
KeyCode::Tab => CtKeyCode::Tab,
KeyCode::Backspace => CtKeyCode::Backspace,
KeyCode::Delete => CtKeyCode::Delete,
KeyCode::Insert => CtKeyCode::Insert,
KeyCode::Up => CtKeyCode::Up,
KeyCode::Down => CtKeyCode::Down,
KeyCode::Left => CtKeyCode::Left,
KeyCode::Right => CtKeyCode::Right,
KeyCode::Home => CtKeyCode::Home,
KeyCode::End => CtKeyCode::End,
KeyCode::PageUp => CtKeyCode::PageUp,
KeyCode::PageDown => CtKeyCode::PageDown,
KeyCode::F(n) => CtKeyCode::F(n),
};
let mut mods = CtKeyMods::NONE;
if ev.modifiers.contains(KeyModifiers::CTRL) {
mods |= CtKeyMods::CONTROL;
}
if ev.modifiers.contains(KeyModifiers::SHIFT) {
mods |= CtKeyMods::SHIFT;
}
if ev.modifiers.contains(KeyModifiers::ALT) {
mods |= CtKeyMods::ALT;
}
CtKeyEvent::new(code, mods)
}
#[cfg(test)]
mod tests {
use super::*;
use crossterm::event::{KeyCode as CK, KeyEvent as CKE, KeyEventKind, KeyModifiers as CM};
fn ct_key(code: CK, mods: CM) -> CKE {
CKE::new(code, mods)
}
#[test]
fn shift_stripped_for_uppercase_char() {
let ev = from_crossterm(&ct_key(CK::Char('B'), CM::SHIFT)).unwrap();
assert_eq!(ev, KeyEvent::new(KeyCode::Char('B'), KeyModifiers::NONE));
}
#[test]
fn shift_stripped_for_shifted_symbol() {
let ev = from_crossterm(&ct_key(CK::Char('<'), CM::SHIFT)).unwrap();
assert_eq!(ev, KeyEvent::new(KeyCode::Char('<'), KeyModifiers::NONE));
}
#[test]
fn ctrl_preserved_with_char() {
let ev = from_crossterm(&ct_key(CK::Char('w'), CM::CONTROL)).unwrap();
assert_eq!(ev, KeyEvent::new(KeyCode::Char('w'), KeyModifiers::CTRL));
}
#[test]
fn ctrl_shift_with_char_keeps_only_ctrl() {
let ev = from_crossterm(&ct_key(CK::Char('A'), CM::CONTROL | CM::SHIFT)).unwrap();
assert_eq!(ev, KeyEvent::new(KeyCode::Char('A'), KeyModifiers::CTRL));
}
#[test]
fn shift_preserved_for_tab() {
let ev = from_crossterm(&ct_key(CK::Tab, CM::SHIFT)).unwrap();
assert_eq!(ev, KeyEvent::new(KeyCode::Tab, KeyModifiers::SHIFT));
}
#[test]
fn shift_preserved_for_f_key() {
let ev = from_crossterm(&ct_key(CK::F(5), CM::SHIFT)).unwrap();
assert_eq!(ev, KeyEvent::new(KeyCode::F(5), KeyModifiers::SHIFT));
}
#[test]
fn release_returns_none() {
let mut k = ct_key(CK::Char('a'), CM::NONE);
k.kind = KeyEventKind::Release;
assert!(from_crossterm(&k).is_none());
}
}