use crossterm::event::KeyCode;
const CYRILLIC: &[(char, char)] = &[
('й', 'q'),
('ц', 'w'),
('у', 'e'),
('к', 'r'),
('е', 't'),
('н', 'y'),
('г', 'u'),
('ш', 'i'),
('щ', 'o'),
('з', 'p'),
('ф', 'a'),
('ы', 's'),
('і', 's'),
('в', 'd'),
('а', 'f'),
('п', 'g'),
('р', 'h'),
('о', 'j'),
('л', 'k'),
('д', 'l'),
('я', 'z'),
('ч', 'x'),
('с', 'c'),
('м', 'v'),
('и', 'b'),
('т', 'n'),
('ь', 'm'),
];
const GREEK: &[(char, char)] = &[
('ς', 'w'),
('ε', 'e'),
('ρ', 'r'),
('τ', 't'),
('υ', 'y'),
('θ', 'u'),
('ι', 'i'),
('ο', 'o'),
('π', 'p'),
('α', 'a'),
('σ', 's'),
('δ', 'd'),
('φ', 'f'),
('γ', 'g'),
('η', 'h'),
('ξ', 'j'),
('κ', 'k'),
('λ', 'l'),
('ζ', 'z'),
('χ', 'x'),
('ψ', 'c'),
('ω', 'v'),
('β', 'b'),
('ν', 'n'),
('μ', 'm'),
];
const LAYOUTS: &[&[(char, char)]] = &[CYRILLIC, GREEK];
fn latin_at_position(c: char) -> char {
if c.is_ascii() {
return c;
}
let lower = c.to_lowercase().next().unwrap_or(c);
for table in LAYOUTS {
if let Some(&(_, latin)) = table.iter().find(|(src, _)| *src == lower) {
return if c.is_uppercase() {
latin.to_ascii_uppercase()
} else {
latin
};
}
}
c
}
pub fn normalize_hotkey(code: KeyCode) -> KeyCode {
match code {
KeyCode::Char(c) => KeyCode::Char(latin_at_position(c)),
other => other,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ascii_passes_through_unchanged() {
for c in ['q', 'c', 't', 'R', '+', '-', '=', ' ', '1'] {
assert_eq!(normalize_hotkey(KeyCode::Char(c)), KeyCode::Char(c));
}
}
#[test]
fn russian_letters_map_to_qwerty_positions() {
let cases = [
('й', 'q'),
('с', 'c'),
('е', 't'),
('в', 'd'),
('о', 'j'),
('з', 'p'),
('к', 'r'),
('н', 'y'),
('у', 'e'),
('ы', 's'),
('р', 'h'),
('м', 'v'),
('п', 'g'),
('ф', 'a'),
('ь', 'm'),
('ч', 'x'),
];
for (cyrillic, latin) in cases {
assert_eq!(
normalize_hotkey(KeyCode::Char(cyrillic)),
KeyCode::Char(latin),
"{cyrillic} should map to {latin}",
);
}
}
#[test]
fn ukrainian_i_maps_to_s_position() {
assert_eq!(normalize_hotkey(KeyCode::Char('і')), KeyCode::Char('s'));
}
#[test]
fn greek_letters_map_to_qwerty_positions() {
let cases = [
('ς', 'w'),
('ε', 'e'),
('τ', 't'),
('χ', 'x'),
('ψ', 'c'),
('δ', 'd'),
('α', 'a'),
('μ', 'm'),
];
for (greek, latin) in cases {
assert_eq!(
normalize_hotkey(KeyCode::Char(greek)),
KeyCode::Char(latin),
"{greek} should map to {latin}",
);
}
}
#[test]
fn case_is_preserved() {
assert_eq!(normalize_hotkey(KeyCode::Char('К')), KeyCode::Char('R'));
assert_eq!(normalize_hotkey(KeyCode::Char('Й')), KeyCode::Char('Q'));
assert_eq!(normalize_hotkey(KeyCode::Char('Ρ')), KeyCode::Char('R'));
}
#[test]
fn unknown_and_non_char_keys_are_untouched() {
assert_eq!(normalize_hotkey(KeyCode::Char('日')), KeyCode::Char('日'));
assert_eq!(normalize_hotkey(KeyCode::Tab), KeyCode::Tab);
assert_eq!(normalize_hotkey(KeyCode::Esc), KeyCode::Esc);
assert_eq!(normalize_hotkey(KeyCode::Enter), KeyCode::Enter);
}
}