Files
ElyOffice/crates/ely-web-content/src/keys.rs
T
ZacharyZhang-NY b7fca0146d Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git),
ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
2026-10-08 21:20:08 -04:00

190 lines
4.9 KiB
Rust

//! GPUI key names as the codes Chromium reads.
use gpui::Modifiers;
/// Windows' virtual key, and macOS's hardware key.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Code {
pub windows: i32,
pub native: i32,
}
/// Letters, then digits, as macOS numbers its keys.
const MAC_LETTERS: [i32; 26] = [
0, 11, 8, 2, 14, 3, 5, 4, 34, 38, 40, 37, 46, 45, 31, 35, 12, 15, 1, 17, 32, 9, 13, 7, 16, 6,
];
const MAC_DIGITS: [i32; 10] = [29, 18, 19, 20, 21, 23, 22, 26, 28, 25];
const MAC_F: [i32; 12] = [122, 120, 99, 118, 96, 97, 98, 100, 101, 109, 103, 111];
/// Named keys: GPUI's name, Windows, macOS.
const NAMED: &[(&str, i32, i32)] = &[
("backspace", 0x08, 51),
("tab", 0x09, 48),
("enter", 0x0D, 36),
("escape", 0x1B, 53),
("space", 0x20, 49),
("pageup", 0x21, 116),
("pagedown", 0x22, 121),
("end", 0x23, 119),
("home", 0x24, 115),
("left", 0x25, 123),
("up", 0x26, 126),
("right", 0x27, 124),
("down", 0x28, 125),
("delete", 0x2E, 117),
(";", 0xBA, 41),
("=", 0xBB, 24),
(",", 0xBC, 43),
("-", 0xBD, 27),
(".", 0xBE, 47),
("/", 0xBF, 44),
("`", 0xC0, 50),
("[", 0xDB, 33),
("\\", 0xDC, 42),
("]", 0xDD, 30),
("'", 0xDE, 39),
];
pub fn code(key: &str) -> Option<Code> {
if let Some(&(_, windows, native)) = NAMED.iter().find(|(name, _, _)| *name == key) {
return Some(Code { windows, native });
}
let mut chars = key.chars();
let (Some(c), None) = (chars.next(), chars.next()) else {
let n: usize = key.strip_prefix('f')?.parse().ok()?;
let native = *MAC_F.get(n.checked_sub(1)?)?;
return Some(Code {
windows: 0x6F + n as i32,
native,
});
};
match c {
'a'..='z' => Some(Code {
windows: c.to_ascii_uppercase() as i32,
native: MAC_LETTERS[(c as u8 - b'a') as usize],
}),
'0'..='9' => Some(Code {
windows: c as i32,
native: MAC_DIGITS[(c as u8 - b'0') as usize],
}),
_ => None,
}
}
/// The character AppKit gives keys that type none.
#[cfg(target_os = "macos")]
pub fn character(key: &str) -> Option<u16> {
let n: Option<u16> = key.strip_prefix('f').and_then(|n| n.parse().ok());
Some(match key {
"backspace" => 0x7F,
"tab" => 0x09,
"enter" => 0x0D,
"escape" => 0x1B,
"up" => 0xF700,
"down" => 0xF701,
"left" => 0xF702,
"right" => 0xF703,
"delete" => 0xF728,
"home" => 0xF729,
"end" => 0xF72B,
"pageup" => 0xF72C,
"pagedown" => 0xF72D,
_ => match n {
Some(n @ 1..=12) => 0xF703 + n,
_ => return None,
},
})
}
/// The control character other systems type, if any.
#[cfg(not(target_os = "macos"))]
pub fn character(key: &str) -> Option<u16> {
Some(match key {
"backspace" => 0x08,
"tab" => 0x09,
"enter" => 0x0D,
"escape" => 0x1B,
_ => return None,
})
}
/// The chord for cut, copy, paste and select-all here.
pub fn edit_chord(modifiers: &Modifiers) -> bool {
let primary = if cfg!(target_os = "macos") {
modifiers.platform && !modifiers.control
} else {
modifiers.control && !modifiers.platform
};
primary && !modifiers.alt && !modifiers.shift
}
#[cfg(test)]
mod tests {
use super::{Code, code};
#[test]
fn keys_carry_both_codes() {
assert_eq!(
code("a"),
Some(Code {
windows: 0x41,
native: 0
})
);
assert_eq!(
code("z"),
Some(Code {
windows: 0x5A,
native: 6
})
);
assert_eq!(
code("0"),
Some(Code {
windows: 0x30,
native: 29
})
);
assert_eq!(
code("enter"),
Some(Code {
windows: 0x0D,
native: 36
})
);
assert_eq!(
code("f1"),
Some(Code {
windows: 0x70,
native: 122
})
);
assert_eq!(
code("f12"),
Some(Code {
windows: 0x7B,
native: 111
})
);
assert_eq!(
code("["),
Some(Code {
windows: 0xDB,
native: 33
})
);
assert_eq!(code("f13"), None);
assert_eq!(code("é"), None);
}
#[cfg(target_os = "macos")]
#[test]
fn keys_without_text_carry_appkit_characters() {
assert_eq!(super::character("backspace"), Some(0x7F));
assert_eq!(super::character("up"), Some(0xF700));
assert_eq!(super::character("f1"), Some(0xF704));
assert_eq!(super::character("f12"), Some(0xF70F));
assert_eq!(super::character("a"), None);
}
}