test(tui): add runtime keymap resolver characterization suite

Introduce the TUI runtime keymap resolver and keybinding matching helpers
with a dedicated unit-test suite. This commit is additive only: it adds
resolution logic, conflict validation, parser coverage, and documented macros
without wiring input handlers to the new runtime map yet.
This commit is contained in:
Josh McKinney
2026-02-05 23:02:25 -08:00
parent c267dd07bb
commit dc1d5a0831
3 changed files with 1203 additions and 1 deletions

View File

@@ -15,7 +15,7 @@ const ALT_PREFIX: &str = "alt + ";
const CTRL_PREFIX: &str = "ctrl + ";
const SHIFT_PREFIX: &str = "shift + ";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) struct KeyBinding {
key: KeyCode,
modifiers: KeyModifiers,
@@ -31,6 +31,22 @@ impl KeyBinding {
&& self.modifiers == event.modifiers
&& (event.kind == KeyEventKind::Press || event.kind == KeyEventKind::Repeat)
}
pub(crate) const fn parts(&self) -> (KeyCode, KeyModifiers) {
(self.key, self.modifiers)
}
}
/// Matching helpers for one action's keybinding set.
pub(crate) trait KeyBindingListExt {
/// True when any binding in this set matches `event`.
fn is_pressed(&self, event: KeyEvent) -> bool;
}
impl KeyBindingListExt for [KeyBinding] {
fn is_pressed(&self, event: KeyEvent) -> bool {
self.iter().any(|binding| binding.is_press(event))
}
}
pub(crate) const fn plain(key: KeyCode) -> KeyBinding {
@@ -110,3 +126,60 @@ pub(crate) fn is_altgr(mods: KeyModifiers) -> bool {
pub(crate) fn is_altgr(_mods: KeyModifiers) -> bool {
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_press_accepts_press_and_repeat_but_rejects_release() {
let binding = ctrl(KeyCode::Char('k'));
let press = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::CONTROL);
let repeat = KeyEvent {
kind: KeyEventKind::Repeat,
..press
};
let release = KeyEvent {
kind: KeyEventKind::Release,
..press
};
let wrong_modifiers = KeyEvent::new(KeyCode::Char('k'), KeyModifiers::NONE);
assert!(binding.is_press(press));
assert!(binding.is_press(repeat));
assert!(!binding.is_press(release));
assert!(!binding.is_press(wrong_modifiers));
}
#[test]
fn keybinding_list_ext_matches_any_binding() {
let bindings = [plain(KeyCode::Char('a')), ctrl(KeyCode::Char('b'))];
assert!(bindings.is_pressed(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE)));
assert!(bindings.is_pressed(KeyEvent::new(KeyCode::Char('b'), KeyModifiers::CONTROL)));
assert!(!bindings.is_pressed(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE)));
}
#[test]
fn ctrl_alt_sets_both_modifiers() {
assert_eq!(
ctrl_alt(KeyCode::Char('v')).parts(),
(
KeyCode::Char('v'),
KeyModifiers::CONTROL | KeyModifiers::ALT
)
);
}
#[test]
fn has_ctrl_or_alt_checks_supported_modifier_combinations() {
assert!(!has_ctrl_or_alt(KeyModifiers::NONE));
assert!(has_ctrl_or_alt(KeyModifiers::CONTROL));
assert!(has_ctrl_or_alt(KeyModifiers::ALT));
#[cfg(windows)]
assert!(!has_ctrl_or_alt(KeyModifiers::CONTROL | KeyModifiers::ALT));
#[cfg(not(windows))]
assert!(has_ctrl_or_alt(KeyModifiers::CONTROL | KeyModifiers::ALT));
}
}

1128
codex-rs/tui/src/keymap.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -80,6 +80,7 @@ mod get_git_diff;
mod history_cell;
pub mod insert_history;
mod key_hint;
mod keymap;
pub mod live_wrap;
mod markdown;
mod markdown_render;