feat(keymap): add AnyChar pattern and new mode variants

Add AnyChar key pattern for text-entry modes that matches any Char key.

Add new mode variants to ModeKey: FormulaPanel, CategoryPanel, ViewPanel,
TileSelect, Editing, FormulaEdit, CategoryAdd, ItemAdd, ExportPrompt,
CommandMode, and SearchMode.

Update Keymap::lookup to fall back to AnyChar for Char keys.
Update KeymapSet::get to accept search_mode parameter.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/Qwen3.5-35B-A3B-GGUF:Q5_K_M)
This commit is contained in:
Edward Langley
2026-04-04 09:58:31 -07:00
parent b7e4316cef
commit e976b3c49a

View File

@ -6,6 +6,7 @@ use crossterm::event::{KeyCode, KeyModifiers};
use crate::ui::app::AppMode;
use crate::ui::effect::Effect;
use crate::view::Axis;
use super::cmd::{self, Cmd, CmdContext};
@ -14,6 +15,9 @@ use super::cmd::{self, Cmd, CmdContext};
pub enum KeyPattern {
/// Single key with modifiers
Key(KeyCode, KeyModifiers),
/// Matches any Char key (for text-entry modes). The actual char
/// is available in CmdContext::key_code.
AnyChar,
}
/// Identifies which mode a binding applies to.
@ -22,14 +26,35 @@ pub enum KeyPattern {
pub enum ModeKey {
Normal,
Help,
// More modes can be added as we migrate handlers
FormulaPanel,
CategoryPanel,
ViewPanel,
TileSelect,
Editing,
FormulaEdit,
CategoryAdd,
ItemAdd,
ExportPrompt,
CommandMode,
SearchMode,
}
impl ModeKey {
pub fn from_app_mode(mode: &AppMode) -> Option<Self> {
pub fn from_app_mode(mode: &AppMode, search_mode: bool) -> Option<Self> {
match mode {
AppMode::Normal if search_mode => Some(ModeKey::SearchMode),
AppMode::Normal => Some(ModeKey::Normal),
AppMode::Help => Some(ModeKey::Help),
AppMode::FormulaPanel => Some(ModeKey::FormulaPanel),
AppMode::CategoryPanel => Some(ModeKey::CategoryPanel),
AppMode::ViewPanel => Some(ModeKey::ViewPanel),
AppMode::TileSelect => Some(ModeKey::TileSelect),
AppMode::Editing { .. } => Some(ModeKey::Editing),
AppMode::FormulaEdit { .. } => Some(ModeKey::FormulaEdit),
AppMode::CategoryAdd { .. } => Some(ModeKey::CategoryAdd),
AppMode::ItemAdd { .. } => Some(ModeKey::ItemAdd),
AppMode::ExportPrompt { .. } => Some(ModeKey::ExportPrompt),
AppMode::CommandMode { .. } => Some(ModeKey::CommandMode),
_ => None,
}
}
@ -77,9 +102,22 @@ impl Keymap {
pub fn lookup(&self, key: KeyCode, mods: KeyModifiers) -> Option<&dyn Cmd> {
self.bindings
.get(&KeyPattern::Key(key, mods))
.or_else(|| {
// Fall back to AnyChar for text-entry modes
if matches!(key, KeyCode::Char(_)) {
self.bindings.get(&KeyPattern::AnyChar)
} else {
None
}
})
.map(|c| c.as_ref())
}
/// Bind a catch-all for any Char key (used for text-entry modes).
pub fn bind_any_char(&mut self, cmd: impl Cmd + 'static) {
self.bindings.insert(KeyPattern::AnyChar, Arc::new(cmd));
}
/// Execute a keymap lookup and return effects, or None if no binding.
pub fn dispatch(
&self,
@ -133,8 +171,8 @@ impl KeymapSet {
}
/// Look up the root keymap for a given app mode.
pub fn get(&self, mode: &AppMode) -> Option<&Arc<Keymap>> {
let mode_key = ModeKey::from_app_mode(mode)?;
pub fn get(&self, mode: &AppMode, search_mode: bool) -> Option<&Arc<Keymap>> {
let mode_key = ModeKey::from_app_mode(mode, search_mode)?;
self.mode_maps.get(&mode_key)
}
@ -145,7 +183,7 @@ impl KeymapSet {
key: KeyCode,
mods: KeyModifiers,
) -> Option<Vec<Box<dyn Effect>>> {
let keymap = self.get(ctx.mode)?;
let keymap = self.get(ctx.mode, ctx.search_mode)?;
keymap.dispatch(ctx, key, mods)
}