use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Action {
Up,
Down,
Left,
Right,
Toggle,
Activate,
Back,
Next,
Prev,
Help,
Quit,
ForceQuit,
}
pub(crate) fn resolve(key: &KeyEvent) -> Option<Action> {
if key.modifiers.contains(KeyModifiers::CONTROL) {
return match key.code {
KeyCode::Char('c') => Some(Action::ForceQuit),
_ => None,
};
}
match key.code {
KeyCode::Up | KeyCode::Char('k') => Some(Action::Up),
KeyCode::Down | KeyCode::Char('j') => Some(Action::Down),
KeyCode::Left | KeyCode::Char('h') => Some(Action::Left),
KeyCode::Right | KeyCode::Char('l') => Some(Action::Right),
KeyCode::Char(' ') => Some(Action::Toggle),
KeyCode::Enter => Some(Action::Activate),
KeyCode::Esc => Some(Action::Back),
KeyCode::BackTab => Some(Action::Prev),
KeyCode::Tab if key.modifiers.contains(KeyModifiers::SHIFT) => Some(Action::Prev),
KeyCode::Tab => Some(Action::Next),
KeyCode::Char('?') => Some(Action::Help),
KeyCode::Char('q') => Some(Action::Quit),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn press(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, KeyModifiers::empty())
}
#[test]
fn arrows_and_vim_aliases_resolve_to_the_same_movement() {
for (code, alias, action) in [
(KeyCode::Up, 'k', Action::Up),
(KeyCode::Down, 'j', Action::Down),
(KeyCode::Left, 'h', Action::Left),
(KeyCode::Right, 'l', Action::Right),
] {
assert_eq!(resolve(&press(code)), Some(action));
assert_eq!(resolve(&press(KeyCode::Char(alias))), Some(action));
}
}
#[test]
fn the_shared_app_keys_resolve() {
assert_eq!(resolve(&press(KeyCode::Char(' '))), Some(Action::Toggle));
assert_eq!(resolve(&press(KeyCode::Enter)), Some(Action::Activate));
assert_eq!(resolve(&press(KeyCode::Esc)), Some(Action::Back));
assert_eq!(resolve(&press(KeyCode::Tab)), Some(Action::Next));
assert_eq!(resolve(&press(KeyCode::BackTab)), Some(Action::Prev));
assert_eq!(resolve(&press(KeyCode::Char('?'))), Some(Action::Help));
assert_eq!(resolve(&press(KeyCode::Char('q'))), Some(Action::Quit));
}
#[test]
fn shift_tab_spelled_as_tab_plus_shift_means_previous() {
let key = KeyEvent::new(KeyCode::Tab, KeyModifiers::SHIFT);
assert_eq!(resolve(&key), Some(Action::Prev));
}
#[test]
fn question_mark_still_resolves_when_the_terminal_reports_shift() {
let key = KeyEvent::new(KeyCode::Char('?'), KeyModifiers::SHIFT);
assert_eq!(resolve(&key), Some(Action::Help));
}
#[test]
fn ctrl_c_force_quits_and_other_ctrl_chords_resolve_to_nothing() {
let ctrl_c = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL);
assert_eq!(resolve(&ctrl_c), Some(Action::ForceQuit));
let ctrl_k = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::CONTROL);
assert_eq!(resolve(&ctrl_k), None);
}
#[test]
fn unmapped_keys_resolve_to_nothing() {
assert_eq!(resolve(&press(KeyCode::Char('z'))), None);
assert_eq!(resolve(&press(KeyCode::F(5))), None);
assert_eq!(resolve(&press(KeyCode::Home)), None);
}
}