use super::{
intern::resolve_sym,
keymap::KeyEvent,
value::{Value, ValueKind, VecLikeType},
};
const CHAR_META: i64 = 0x8000000;
const CHAR_CTL: i64 = 0x4000000;
const CHAR_SHIFT: i64 = 0x2000000;
const CHAR_HYPER: i64 = 0x1000000;
const CHAR_SUPER: i64 = 0x0800000;
const CHAR_ALT: i64 = 0x0400000;
const CHAR_MODIFIER_MASK: i64 =
CHAR_META | CHAR_CTL | CHAR_SHIFT | CHAR_HYPER | CHAR_SUPER | CHAR_ALT;
#[derive(Clone, Debug)]
pub(crate) enum KeyDesignatorError {
WrongType(Value),
Parse(String),
}
#[derive(Clone, Copy, Default)]
struct Modifiers {
ctrl: bool,
meta: bool,
shift: bool,
super_: bool,
hyper: bool,
alt: bool,
}
impl Modifiers {
fn any(self) -> bool {
self.ctrl || self.meta || self.shift || self.super_ || self.hyper || self.alt
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum EncodedEvent {
Char(char),
Int(i64),
Symbol(String),
}
pub(crate) fn parse_kbd_string(desc: &str) -> Result<Value, String> {
let trimmed = desc.trim();
if trimmed.is_empty() {
return Ok(Value::string(""));
}
let mut encoded = Vec::new();
for token in trimmed.split_whitespace() {
parse_token(token, &mut encoded)?;
}
if encoded.iter().all(|e| matches!(e, EncodedEvent::Char(_))) {
let s: String = encoded
.into_iter()
.map(|event| match event {
EncodedEvent::Char(c) => c,
_ => unreachable!("guarded by all(Char)"),
})
.collect();
return Ok(Value::string(s));
}
let values = encoded
.into_iter()
.map(|event| match event {
EncodedEvent::Char(c) => Value::fixnum(c as i64),
EncodedEvent::Int(n) => Value::fixnum(n),
EncodedEvent::Symbol(name) => Value::symbol(name),
})
.collect();
Ok(Value::vector(values))
}
pub(crate) fn key_events_from_designator(
designator: &Value,
) -> Result<Vec<KeyEvent>, KeyDesignatorError> {
match designator.kind() {
ValueKind::String => {
let s = designator.as_str().unwrap().to_owned();
Ok(s.chars()
.map(|ch| {
let code_u32 = ch as u32;
let raw_byte = if (0xE300..=0xE3FF).contains(&code_u32) {
Some((code_u32 - 0xE300) as u8)
} else {
None
};
if let Some(byte) = raw_byte {
if byte >= 0x80 {
let base_byte = byte - 0x80;
let base = char::from_u32(base_byte as u32).unwrap_or(ch);
KeyEvent::Char {
code: base,
ctrl: false,
meta: true,
shift: false,
super_: false,
hyper: false,
alt: false,
}
} else {
let base = char::from_u32(byte as u32).unwrap_or(ch);
KeyEvent::Char {
code: base,
ctrl: false,
meta: false,
shift: false,
super_: false,
hyper: false,
alt: false,
}
}
} else if (0x80..=0xFF).contains(&code_u32) {
let base = char::from_u32(code_u32 - 0x80).unwrap_or(ch);
KeyEvent::Char {
code: base,
ctrl: false,
meta: true,
shift: false,
super_: false,
hyper: false,
alt: false,
}
} else {
KeyEvent::Char {
code: ch,
ctrl: false,
meta: false,
shift: false,
super_: false,
hyper: false,
alt: false,
}
}
})
.collect())
}
ValueKind::Veclike(VecLikeType::Vector) => {
decode_encoded_key_events(designator).map_err(KeyDesignatorError::Parse)
}
other => Err(KeyDesignatorError::WrongType(*designator)),
}
}
fn decode_encoded_key_events(encoded: &Value) -> Result<Vec<KeyEvent>, String> {
match encoded.kind() {
ValueKind::String => {
let s = encoded.as_str().unwrap().to_owned();
Ok(s.chars()
.map(|ch| {
let code_u32 = ch as u32;
let raw_byte = if (0xE300..=0xE3FF).contains(&code_u32) {
Some((code_u32 - 0xE300) as u8)
} else {
None
};
if let Some(byte) = raw_byte {
if byte >= 0x80 {
let base = (byte - 0x80) as u32;
let base_char = char::from_u32(base).unwrap_or(ch);
KeyEvent::Char {
code: base_char,
ctrl: false,
meta: true,
shift: false,
super_: false,
hyper: false,
alt: false,
}
} else {
let base_char = char::from_u32(byte as u32).unwrap_or(ch);
KeyEvent::Char {
code: base_char,
ctrl: false,
meta: false,
shift: false,
super_: false,
hyper: false,
alt: false,
}
}
} else if (0x80..=0xFF).contains(&code_u32) {
let base = code_u32 - 0x80;
let base_char = char::from_u32(base).unwrap_or(ch);
KeyEvent::Char {
code: base_char,
ctrl: false,
meta: true,
shift: false,
super_: false,
hyper: false,
alt: false,
}
} else {
KeyEvent::Char {
code: ch,
ctrl: false,
meta: false,
shift: false,
super_: false,
hyper: false,
alt: false,
}
}
})
.collect())
}
ValueKind::Veclike(VecLikeType::Vector) => {
let items = encoded.as_vector_data().unwrap().clone();
items.iter().map(decode_vector_event).collect()
}
other => Err(format!(
"expected kbd-encoded string or vector, got {}",
encoded.type_name()
)),
}
}
fn decode_vector_event(item: &Value) -> Result<KeyEvent, String> {
match item.kind() {
ValueKind::Fixnum(n) => decode_int_event(n),
ValueKind::Symbol(id) => decode_symbol_event(resolve_sym(id)),
ValueKind::Nil => decode_symbol_event("nil"),
ValueKind::T => decode_symbol_event("t"),
ValueKind::Cons => decode_event_modifier_list(item),
other => Err(format!(
"invalid key vector element type: {}",
item.type_name()
)),
}
}
fn decode_event_modifier_list(list: &Value) -> Result<KeyEvent, String> {
let mut mods = Modifiers::default();
let mut cursor = *list;
loop {
match cursor.kind() {
ValueKind::Cons => {
let pair_car = cursor.cons_car();
let pair_cdr = cursor.cons_cdr();
match pair_car.kind() {
ValueKind::Symbol(id) => {
let name = resolve_sym(id);
match name {
"control" => mods.ctrl = true,
"meta" => mods.meta = true,
"shift" => mods.shift = true,
"super" => mods.super_ = true,
"hyper" => mods.hyper = true,
"alt" => mods.alt = true,
_ => {
if pair_cdr.is_nil() {
return Ok(apply_mods_to_event(
decode_symbol_event(name)?,
mods,
));
}
return Err(format!("unknown modifier in event list: {name}"));
}
}
cursor = pair_cdr;
}
ValueKind::Fixnum(n) => {
let base = decode_int_event(n)?;
return Ok(apply_mods_to_event(base, mods));
}
other => {
return Err(format!(
"invalid base event in modifier list: {}",
pair_car.type_name()
));
}
}
}
ValueKind::Nil => {
return Err("empty event modifier list".to_string());
}
ValueKind::Fixnum(n) => {
let base = decode_int_event(n)?;
return Ok(apply_mods_to_event(base, mods));
}
ValueKind::Symbol(id) => {
return Ok(apply_mods_to_event(
decode_symbol_event(resolve_sym(id))?,
mods,
));
}
other => {
return Err(format!(
"invalid base event in modifier list: {}",
cursor.type_name()
));
}
}
}
}
fn apply_mods_to_event(event: KeyEvent, mods: Modifiers) -> KeyEvent {
match event {
KeyEvent::Char {
code,
ctrl,
meta,
shift,
super_,
hyper,
alt,
} => KeyEvent::Char {
code,
ctrl: ctrl || mods.ctrl,
meta: meta || mods.meta,
shift: shift || mods.shift,
super_: super_ || mods.super_,
hyper: hyper || mods.hyper,
alt: alt || mods.alt,
},
KeyEvent::Function {
name,
ctrl,
meta,
shift,
super_,
hyper,
alt,
} => KeyEvent::Function {
name,
ctrl: ctrl || mods.ctrl,
meta: meta || mods.meta,
shift: shift || mods.shift,
super_: super_ || mods.super_,
hyper: hyper || mods.hyper,
alt: alt || mods.alt,
},
}
}
fn decode_int_event(code: i64) -> Result<KeyEvent, String> {
let mods = code & CHAR_MODIFIER_MASK;
let base = code & !CHAR_MODIFIER_MASK;
if !(0..=0x10FFFF).contains(&base) {
return Err(format!("invalid key event code: {code}"));
}
let ch =
char::from_u32(base as u32).ok_or_else(|| format!("invalid key event code: {code}"))?;
Ok(KeyEvent::Char {
code: ch,
ctrl: (mods & CHAR_CTL) != 0,
meta: (mods & CHAR_META) != 0,
shift: (mods & CHAR_SHIFT) != 0,
super_: (mods & CHAR_SUPER) != 0,
hyper: (mods & CHAR_HYPER) != 0,
alt: (mods & CHAR_ALT) != 0,
})
}
fn decode_symbol_event(symbol: &str) -> Result<KeyEvent, String> {
let (mods, _prefix, remainder) = parse_modifiers(symbol);
if remainder.is_empty() {
return Err("invalid empty key symbol".to_string());
}
Ok(KeyEvent::Function {
name: remainder.to_string(),
ctrl: mods.ctrl,
meta: mods.meta,
shift: mods.shift,
super_: mods.super_,
hyper: mods.hyper,
alt: mods.alt,
})
}
fn parse_token(token: &str, out: &mut Vec<EncodedEvent>) -> Result<(), String> {
let (mods, prefix, remainder) = parse_modifiers(token);
if let Some(name) = parse_angle_symbol(remainder) {
out.push(EncodedEvent::Symbol(format!("{prefix}{name}")));
return Ok(());
}
if let Some(ch) = named_char_token(remainder) {
out.push(encode_char(ch, mods, false));
return Ok(());
}
if let Some(ch) = single_char(remainder) {
out.push(encode_char(ch, mods, true));
return Ok(());
}
if mods.any() {
return Err(format!(
"{prefix} must prefix a single character, not {remainder}"
));
}
out.extend(remainder.chars().map(EncodedEvent::Char));
Ok(())
}
fn parse_modifiers(mut token: &str) -> (Modifiers, String, &str) {
let mut mods = Modifiers::default();
let mut prefix = String::new();
loop {
if let Some(rest) = token.strip_prefix("C-") {
if rest.is_empty() {
break;
}
mods.ctrl = true;
prefix.push_str("C-");
token = rest;
continue;
}
if let Some(rest) = token.strip_prefix("M-") {
if rest.is_empty() {
break;
}
mods.meta = true;
prefix.push_str("M-");
token = rest;
continue;
}
if let Some(rest) = token.strip_prefix("S-") {
if rest.is_empty() {
break;
}
mods.shift = true;
prefix.push_str("S-");
token = rest;
continue;
}
if let Some(rest) = token.strip_prefix("s-") {
if rest.is_empty() {
break;
}
mods.super_ = true;
prefix.push_str("s-");
token = rest;
continue;
}
if let Some(rest) = token.strip_prefix("H-") {
if rest.is_empty() {
break;
}
mods.hyper = true;
prefix.push_str("H-");
token = rest;
continue;
}
if let Some(rest) = token.strip_prefix("A-") {
if rest.is_empty() {
break;
}
mods.alt = true;
prefix.push_str("A-");
token = rest;
continue;
}
break;
}
(mods, prefix, token)
}
fn parse_angle_symbol(token: &str) -> Option<&str> {
let inner = token.strip_prefix('<')?.strip_suffix('>')?;
if inner.is_empty() { None } else { Some(inner) }
}
fn named_char_token(token: &str) -> Option<char> {
match token {
"RET" | "return" => Some('\r'),
"TAB" | "tab" => Some('\t'),
"SPC" | "space" => Some(' '),
"ESC" | "escape" => Some('\u{1b}'),
"DEL" | "delete" => Some('\u{7f}'),
_ => None,
}
}
fn single_char(token: &str) -> Option<char> {
let mut chars = token.chars();
let ch = chars.next()?;
if chars.next().is_none() {
Some(ch)
} else {
None
}
}
fn encode_char(ch: char, mods: Modifiers, allow_ctrl_resolution: bool) -> EncodedEvent {
if !mods.any() {
return EncodedEvent::Char(ch);
}
let mut base = ch as i64;
let mut ctrl = mods.ctrl;
if ctrl && allow_ctrl_resolution {
if let Some(resolved) = resolve_control_char(ch) {
base = resolved;
ctrl = false;
}
}
if !mods.meta && !mods.shift && !mods.super_ && !mods.hyper && !mods.alt && !ctrl {
if let Some(resolved) = char::from_u32(base as u32) {
return EncodedEvent::Char(resolved);
}
return EncodedEvent::Int(base);
}
let mut code = base;
if mods.meta {
code |= CHAR_META;
}
if ctrl {
code |= CHAR_CTL;
}
if mods.shift {
code |= CHAR_SHIFT;
}
if mods.super_ {
code |= CHAR_SUPER;
}
if mods.hyper {
code |= CHAR_HYPER;
}
if mods.alt {
code |= CHAR_ALT;
}
EncodedEvent::Int(code)
}
fn resolve_control_char(ch: char) -> Option<i64> {
if ch.is_ascii_alphabetic() {
return Some(((ch.to_ascii_uppercase() as u8) & 0x1F) as i64);
}
if ('@'..='_').contains(&ch) && ch != '?' {
return Some(((ch as u8) & 0x1F) as i64);
}
None
}
#[cfg(test)]
#[path = "kbd_test.rs"]
mod tests;