use super::model::{MenuEntry, MenuItemSpec, MenuSection};
use muda::accelerator::{Accelerator, Code, Modifiers};
use par_term_keybindings::KeybindingRegistry;
use par_term_keybindings::parser::{KeyCombo, ParsedKey};
use std::collections::HashSet;
use winit::keyboard::NamedKey;
pub fn apply_registry_accelerators(sections: &mut [MenuSection], registry: &KeybindingRegistry) {
let mut seen: HashSet<String> = HashSet::new();
for section in sections {
apply_entries(&mut section.entries, registry, &mut seen);
}
}
fn apply_entries(
entries: &mut [MenuEntry],
registry: &KeybindingRegistry,
seen: &mut HashSet<String>,
) {
for entry in entries {
match entry {
MenuEntry::Item(spec) => apply_item(spec, registry, seen),
MenuEntry::Submenu(sub) => apply_entries(&mut sub.entries, registry, seen),
_ => {}
}
}
}
fn apply_item(spec: &mut MenuItemSpec, registry: &KeybindingRegistry, seen: &mut HashSet<String>) {
let Some(action) = spec.action.keybinding_action() else {
return;
};
if spec.alias || !seen.insert(action.to_string()) {
spec.accelerator = None;
return;
}
spec.accelerator = registry.chord_for_action(&action).and_then(|combo| {
let accelerator = accelerator_from_combo(&combo);
if accelerator.is_none() {
log::debug!(
"menu: the chord bound to {action} has no menu key equivalent; the menu shows none"
);
}
accelerator
});
}
#[cfg(test)]
pub(crate) fn accelerator_from_chord(chord: &str) -> Option<Accelerator> {
accelerator_from_combo(&par_term_keybindings::parser::parse_key_combo(chord).ok()?)
}
pub(crate) fn accelerator_from_combo(combo: &KeyCombo) -> Option<Accelerator> {
let combo = combo.clone().platform_normalized();
let mut mods = Modifiers::empty();
if combo.modifiers.ctrl {
mods |= Modifiers::CONTROL;
}
if combo.modifiers.alt {
mods |= Modifiers::ALT;
}
if combo.modifiers.shift {
mods |= Modifiers::SHIFT;
}
if combo.modifiers.super_key {
mods |= Modifiers::META;
}
let code = match combo.key {
ParsedKey::Character(c) => char_code(c)?,
ParsedKey::Named(named) => named_code(named)?,
ParsedKey::Physical(_) => return None,
};
Some(Accelerator::new(mods, code))
}
fn char_code(c: char) -> Option<Code> {
Some(match c.to_ascii_uppercase() {
'0' => Code::Digit0,
'1' => Code::Digit1,
'2' => Code::Digit2,
'3' => Code::Digit3,
'4' => Code::Digit4,
'5' => Code::Digit5,
'6' => Code::Digit6,
'7' => Code::Digit7,
'8' => Code::Digit8,
'9' => Code::Digit9,
'A' => Code::KeyA,
'B' => Code::KeyB,
'C' => Code::KeyC,
'D' => Code::KeyD,
'E' => Code::KeyE,
'F' => Code::KeyF,
'G' => Code::KeyG,
'H' => Code::KeyH,
'I' => Code::KeyI,
'J' => Code::KeyJ,
'K' => Code::KeyK,
'L' => Code::KeyL,
'M' => Code::KeyM,
'N' => Code::KeyN,
'O' => Code::KeyO,
'P' => Code::KeyP,
'Q' => Code::KeyQ,
'R' => Code::KeyR,
'S' => Code::KeyS,
'T' => Code::KeyT,
'U' => Code::KeyU,
'V' => Code::KeyV,
'W' => Code::KeyW,
'X' => Code::KeyX,
'Y' => Code::KeyY,
'Z' => Code::KeyZ,
'[' => Code::BracketLeft,
']' => Code::BracketRight,
'=' => Code::Equal,
'-' => Code::Minus,
',' => Code::Comma,
'.' => Code::Period,
_ => return None,
})
}
fn named_code(named: NamedKey) -> Option<Code> {
Some(match named {
NamedKey::F1 => Code::F1,
NamedKey::F2 => Code::F2,
NamedKey::F3 => Code::F3,
NamedKey::F4 => Code::F4,
NamedKey::F5 => Code::F5,
NamedKey::F6 => Code::F6,
NamedKey::F7 => Code::F7,
NamedKey::F8 => Code::F8,
NamedKey::F9 => Code::F9,
NamedKey::F10 => Code::F10,
NamedKey::F11 => Code::F11,
NamedKey::F12 => Code::F12,
NamedKey::ArrowLeft => Code::ArrowLeft,
NamedKey::ArrowRight => Code::ArrowRight,
NamedKey::ArrowUp => Code::ArrowUp,
NamedKey::ArrowDown => Code::ArrowDown,
NamedKey::Space => Code::Space,
NamedKey::Enter => Code::Enter,
NamedKey::Tab => Code::Tab,
NamedKey::Home => Code::Home,
NamedKey::End => Code::End,
NamedKey::PageUp => Code::PageUp,
NamedKey::PageDown => Code::PageDown,
NamedKey::Insert => Code::Insert,
NamedKey::Delete => Code::Delete,
NamedKey::Backspace => Code::Backspace,
_ => return None,
})
}
#[cfg(test)]
#[path = "registry_accel_tests.rs"]
mod tests;