use gpui::KeyDownEvent;
use twrite_core::{KeyCode, KeyEvent, Modifiers};
pub fn translate_key_down(event: &KeyDownEvent) -> Option<KeyEvent> {
let keystroke = &event.keystroke;
Some(KeyEvent {
code: code_for(keystroke),
modifiers: Modifiers {
ctrl: keystroke.modifiers.control,
alt: keystroke.modifiers.alt,
shift: keystroke.modifiers.shift,
meta: keystroke.modifiers.platform,
},
})
}
fn code_for(keystroke: &gpui::Keystroke) -> KeyCode {
let mods = &keystroke.modifiers;
match keystroke.key.as_str() {
"space" => return KeyCode::Char(' '),
"left" | "arrowleft" => return KeyCode::Left,
"right" | "arrowright" => return KeyCode::Right,
"up" | "arrowup" => return KeyCode::Up,
"down" | "arrowdown" => return KeyCode::Down,
_ => {}
}
if let Some(text) = &keystroke.key_char
&& text.chars().count() == 1
&& !mods.control
&& !mods.platform
&& !mods.alt
{
return KeyCode::Char(text.chars().next().unwrap());
}
if keystroke.key.chars().count() == 1 {
return KeyCode::Char(keystroke.key.chars().next().unwrap().to_ascii_lowercase());
}
match keystroke.key.as_str() {
"enter" => KeyCode::Enter,
"tab" => KeyCode::Tab,
"escape" => KeyCode::Escape,
"backspace" => KeyCode::Backspace,
"delete" => KeyCode::Delete,
"home" => KeyCode::Home,
"end" => KeyCode::End,
"pageup" => KeyCode::PageUp,
"pagedown" => KeyCode::PageDown,
"insert" => KeyCode::Insert,
name => parse_function_key(name).unwrap_or(KeyCode::Unidentified),
}
}
fn parse_function_key(name: &str) -> Option<KeyCode> {
let lower = name.to_ascii_lowercase();
let digits = lower.strip_prefix('f')?;
if !(1..=2).contains(&digits.len()) || !digits.chars().all(|c| c.is_ascii_digit()) {
return None;
}
let n: u8 = digits.parse().ok()?;
(1..=24).contains(&n).then_some(KeyCode::F(n))
}
#[cfg(test)]
mod tests {
use super::*;
fn keystroke(key: &str, key_char: Option<&str>, modifiers: gpui::Modifiers) -> KeyDownEvent {
KeyDownEvent {
keystroke: gpui::Keystroke {
key: key.to_string(),
key_char: key_char.map(|s| s.to_string()),
modifiers,
},
is_held: false,
}
}
fn plain(key: &str, key_char: Option<&str>) -> KeyDownEvent {
keystroke(key, key_char, gpui::Modifiers::default())
}
#[test]
fn shift_letter_yields_capital_with_shift_held() {
let event = keystroke(
"a",
Some("A"),
gpui::Modifiers {
shift: true,
..Default::default()
},
);
let translated = translate_key_down(&event).unwrap();
assert_eq!(translated.code, KeyCode::Char('A'));
assert!(translated.modifiers.shift);
}
#[test]
fn plain_letter_passes_through() {
let translated = translate_key_down(&plain("a", Some("a"))).unwrap();
assert_eq!(translated.code, KeyCode::Char('a'));
assert!(!translated.modifiers.shift);
}
#[test]
fn shifted_symbol_uses_typed_character() {
let event = keystroke(
"/",
Some("?"),
gpui::Modifiers {
shift: true,
..Default::default()
},
);
assert_eq!(translate_key_down(&event).unwrap().code, KeyCode::Char('?'));
}
#[test]
fn command_combos_keep_physical_key_names() {
let event = keystroke(
"b",
Some("b"),
gpui::Modifiers {
control: true,
..Default::default()
},
);
let translated = translate_key_down(&event).unwrap();
assert_eq!(translated.code, KeyCode::Char('b'));
assert!(translated.modifiers.ctrl);
}
#[test]
fn alt_combos_keep_key_names() {
let alt = gpui::Modifiers {
alt: true,
..Default::default()
};
let translated = translate_key_down(&keystroke("c", Some("รง"), alt)).unwrap();
assert_eq!(translated.code, KeyCode::Char('c'));
assert!(translated.modifiers.alt);
}
#[test]
fn multichar_or_missing_key_char_falls_back_to_key() {
let translated = translate_key_down(&plain("a", Some("aeiou"))).unwrap();
assert_eq!(translated.code, KeyCode::Char('a'));
let translated = translate_key_down(&plain("s", None)).unwrap();
assert_eq!(translated.code, KeyCode::Char('s'));
}
#[test]
fn named_keys_and_aliases_map_to_variants() {
assert_eq!(
translate_key_down(&plain("enter", None)).unwrap().code,
KeyCode::Enter
);
assert_eq!(
translate_key_down(&plain("left", None)).unwrap().code,
KeyCode::Left
);
assert_eq!(
translate_key_down(&plain("space", None)).unwrap().code,
KeyCode::Char(' ')
);
assert_eq!(
translate_key_down(&plain("f3", None)).unwrap().code,
KeyCode::F(3)
);
assert_eq!(
translate_key_down(&plain("back", None)).unwrap().code,
KeyCode::Unidentified
);
}
}