use crossterm::event::KeyCode;
use crossterm::event::KeyModifiers;
pub(crate) const fn canonical_code(code: KeyCode) -> KeyCode {
match code {
KeyCode::Char('+') => KeyCode::Char('='),
other => other,
}
}
pub(crate) fn canonical_event_code_and_mods(
code: KeyCode,
mods: KeyModifiers,
) -> (KeyCode, KeyModifiers) {
let (code, mods) = canonical_shifted_ascii(code, mods);
let code = canonical_code(code);
let mods = if matches!(code, KeyCode::Char('=' | '+')) {
mods - KeyModifiers::SHIFT
} else {
mods
};
(code, mods)
}
fn canonical_shifted_ascii(code: KeyCode, mods: KeyModifiers) -> (KeyCode, KeyModifiers) {
if !mods.contains(KeyModifiers::SHIFT) {
return (code, mods);
}
let KeyCode::Char(character) = code else {
return (code, mods);
};
let shifted = match character {
'`' | '~' => '~',
'1' | '!' => '!',
'2' | '@' => '@',
'3' | '#' => '#',
'4' | '$' => '$',
'5' | '%' => '%',
'6' | '^' => '^',
'7' | '&' => '&',
'8' | '*' => '*',
'9' | '(' => '(',
'0' | ')' => ')',
'-' | '_' => '_',
'=' | '+' => '+',
'[' | '{' => '{',
']' | '}' => '}',
'\\' | '|' => '|',
';' | ':' => ':',
'\'' | '"' => '"',
',' | '<' => '<',
'.' | '>' => '>',
'/' | '?' => '?',
_ => return (code, mods),
};
(KeyCode::Char(shifted), mods - KeyModifiers::SHIFT)
}
#[cfg(test)]
mod tests {
use crossterm::event::KeyCode;
use crossterm::event::KeyModifiers;
use super::canonical_event_code_and_mods;
#[test]
fn shifted_question_mark_matches_the_text_binding() {
for character in ['/', '?'] {
assert_eq!(
canonical_event_code_and_mods(KeyCode::Char(character), KeyModifiers::SHIFT),
(KeyCode::Char('?'), KeyModifiers::NONE)
);
}
}
#[test]
fn shifted_space_keeps_its_modifier() {
assert_eq!(
canonical_event_code_and_mods(KeyCode::Char(' '), KeyModifiers::SHIFT),
(KeyCode::Char(' '), KeyModifiers::SHIFT)
);
}
}