use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use super::{Key, KeyInput, Modifiers};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnsupportedKey {
code: KeyCode,
}
impl UnsupportedKey {
#[must_use]
pub fn code(&self) -> KeyCode {
self.code
}
}
impl core::fmt::Display for UnsupportedKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "unsupported key code: {:?}", self.code)
}
}
impl std::error::Error for UnsupportedKey {}
fn modifiers_of(km: KeyModifiers) -> Modifiers {
let mut m = Modifiers::NONE;
if km.contains(KeyModifiers::CONTROL) {
m |= Modifiers::CTRL;
}
if km.contains(KeyModifiers::ALT) {
m |= Modifiers::ALT;
}
if km.contains(KeyModifiers::SUPER) {
m |= Modifiers::SUPER;
}
if km.contains(KeyModifiers::SHIFT) {
m |= Modifiers::SHIFT;
}
m
}
impl TryFrom<KeyEvent> for KeyInput {
type Error = UnsupportedKey;
fn try_from(event: KeyEvent) -> Result<Self, Self::Error> {
let mut mods = modifiers_of(event.modifiers);
let key = match event.code {
KeyCode::Char(c) => Key::Char(c),
KeyCode::BackTab => {
mods |= Modifiers::SHIFT;
Key::Tab
}
KeyCode::F(n) => Key::F(n),
KeyCode::Enter => Key::Enter,
KeyCode::Esc => Key::Esc,
KeyCode::Tab => Key::Tab,
KeyCode::Backspace => Key::Backspace,
KeyCode::Delete => Key::Delete,
KeyCode::Insert => Key::Insert,
KeyCode::Home => Key::Home,
KeyCode::End => Key::End,
KeyCode::PageUp => Key::PageUp,
KeyCode::PageDown => Key::PageDown,
KeyCode::Up => Key::Up,
KeyCode::Down => Key::Down,
KeyCode::Left => Key::Left,
KeyCode::Right => Key::Right,
other => return Err(UnsupportedKey { code: other }),
};
Ok(KeyInput::normalized(key, mods))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shift_is_dropped_for_resolved_glyph() {
let event = KeyEvent::new(KeyCode::Char('A'), KeyModifiers::SHIFT);
let input = KeyInput::try_from(event).unwrap();
assert_eq!(input.key(), Key::Char('A'));
assert_eq!(input.modifiers(), Modifiers::NONE);
}
#[test]
fn glyph_is_trusted_not_recased() {
let event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::SHIFT);
let input = KeyInput::try_from(event).unwrap();
assert_eq!(input.key(), Key::Char('a'));
assert_eq!(input.modifiers(), Modifiers::NONE);
}
#[test]
fn ctrl_char_keeps_ctrl() {
let event = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::CONTROL);
let input = KeyInput::try_from(event).unwrap();
assert_eq!(input.key(), Key::Char('q'));
assert_eq!(input.modifiers(), Modifiers::CTRL);
}
#[test]
fn ctrl_shift_char_keeps_shift_as_distinct_chord() {
let event = KeyEvent::new(
KeyCode::Char('s'),
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
);
let input = KeyInput::try_from(event).unwrap();
assert_eq!(input.key(), Key::Char('s'));
assert_eq!(input.modifiers(), Modifiers::CTRL | Modifiers::SHIFT);
let ctrl_s =
KeyInput::try_from(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL)).unwrap();
assert_ne!(input, ctrl_s);
}
#[test]
fn alt_and_super_pass_through_on_char() {
let alt = KeyInput::try_from(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::ALT)).unwrap();
assert_eq!(alt.modifiers(), Modifiers::ALT);
let sup =
KeyInput::try_from(KeyEvent::new(KeyCode::Char('1'), KeyModifiers::SUPER)).unwrap();
assert_eq!(sup.key(), Key::Char('1'));
assert_eq!(sup.modifiers(), Modifiers::SUPER);
}
#[test]
fn non_ascii_char_passes_through_with_shift_dropped() {
let event = KeyEvent::new(KeyCode::Char('あ'), KeyModifiers::SHIFT);
let input = KeyInput::try_from(event).unwrap();
assert_eq!(input.key(), Key::Char('あ'));
assert_eq!(input.modifiers(), Modifiers::NONE);
}
#[test]
fn backtab_and_shift_tab_converge_to_same_input() {
let from_backtab =
KeyInput::try_from(KeyEvent::new(KeyCode::BackTab, KeyModifiers::NONE)).unwrap();
let from_shift_tab =
KeyInput::try_from(KeyEvent::new(KeyCode::Tab, KeyModifiers::SHIFT)).unwrap();
assert_eq!(from_backtab, from_shift_tab);
}
#[test]
fn backtab_normalizes_to_shift_tab() {
let event = KeyEvent::new(KeyCode::BackTab, KeyModifiers::NONE);
let input = KeyInput::try_from(event).unwrap();
assert_eq!(input.key(), Key::Tab);
assert_eq!(input.modifiers(), Modifiers::SHIFT);
}
#[test]
fn shift_kept_for_non_char_keys() {
let event = KeyEvent::new(KeyCode::Up, KeyModifiers::SHIFT);
let input = KeyInput::try_from(event).unwrap();
assert_eq!(input.key(), Key::Up);
assert_eq!(input.modifiers(), Modifiers::SHIFT);
}
#[test]
fn unsupported_key_is_reported_not_guessed() {
let event = KeyEvent::new(KeyCode::CapsLock, KeyModifiers::NONE);
let err = KeyInput::try_from(event).unwrap_err();
assert_eq!(err.code(), KeyCode::CapsLock);
}
}