feat(command): add keymap inheritance and sequence bindings
Implement keymap inheritance and sequence bindings. - Added `parent` field to `Keymap` to support Emacs-style inheritance. - Implemented `lookup` in `Keymap` to fall through to parent keymaps. - Added `bind_seq` to allow multiple commands to be bound to a single key pattern. - Refactored existing keymaps to use sequences for common patterns like Esc/Enter/Tab to clear buffers and change modes. Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q5_K_XL)
This commit is contained in:
@ -100,9 +100,12 @@ pub enum Binding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A keymap maps key patterns to bindings (command names or prefix sub-keymaps).
|
/// A keymap maps key patterns to bindings (command names or prefix sub-keymaps).
|
||||||
|
/// Supports Emacs-style parent keymap inheritance: if a key is not found in this
|
||||||
|
/// keymap, lookup falls through to the parent.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Keymap {
|
pub struct Keymap {
|
||||||
bindings: HashMap<KeyPattern, Binding>,
|
bindings: HashMap<KeyPattern, Binding>,
|
||||||
|
parent: Option<Arc<Keymap>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Keymap {
|
impl fmt::Debug for Keymap {
|
||||||
@ -117,6 +120,16 @@ impl Keymap {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
bindings: HashMap::new(),
|
bindings: HashMap::new(),
|
||||||
|
parent: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a keymap that inherits from a parent. Keys not found here
|
||||||
|
/// fall through to the parent (Emacs-style keymap inheritance).
|
||||||
|
pub fn with_parent(parent: Arc<Keymap>) -> Self {
|
||||||
|
Self {
|
||||||
|
bindings: HashMap::new(),
|
||||||
|
parent: Some(parent),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,11 +211,8 @@ impl Keymap {
|
|||||||
hints
|
hints
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up the binding for a key.
|
/// Look up the binding for a key in this keymap's own bindings.
|
||||||
/// For Char keys, if exact (key, mods) match fails, retries with NONE
|
fn lookup_local(&self, key: KeyCode, mods: KeyModifiers) -> Option<&Binding> {
|
||||||
/// modifiers since terminals vary in whether they send SHIFT for
|
|
||||||
/// uppercase/symbol characters.
|
|
||||||
pub fn lookup(&self, key: KeyCode, mods: KeyModifiers) -> Option<&Binding> {
|
|
||||||
self.bindings
|
self.bindings
|
||||||
.get(&KeyPattern::Key(key, mods))
|
.get(&KeyPattern::Key(key, mods))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
@ -223,6 +233,13 @@ impl Keymap {
|
|||||||
.or_else(|| self.bindings.get(&KeyPattern::Any))
|
.or_else(|| self.bindings.get(&KeyPattern::Any))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Look up the binding for a key, falling through to parent keymaps.
|
||||||
|
pub fn lookup(&self, key: KeyCode, mods: KeyModifiers) -> Option<&Binding> {
|
||||||
|
self.lookup_local(key, mods).or_else(|| {
|
||||||
|
self.parent.as_ref().and_then(|p| p.lookup(key, mods))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Dispatch a key: look up binding, resolve through registry, return effects.
|
/// Dispatch a key: look up binding, resolve through registry, return effects.
|
||||||
pub fn dispatch(
|
pub fn dispatch(
|
||||||
&self,
|
&self,
|
||||||
@ -712,36 +729,50 @@ impl KeymapSet {
|
|||||||
|
|
||||||
// ── Editing mode ─────────────────────────────────────────────────
|
// ── Editing mode ─────────────────────────────────────────────────
|
||||||
let mut ed = Keymap::new();
|
let mut ed = Keymap::new();
|
||||||
ed.bind_args(KeyCode::Esc, none, "enter-mode", vec!["normal".into()]);
|
ed.bind_seq(KeyCode::Esc, none, vec![
|
||||||
ed.bind(KeyCode::Enter, none, "commit-cell-edit");
|
("clear-buffer", vec!["edit".into()]),
|
||||||
ed.bind(KeyCode::Tab, none, "commit-and-advance-right");
|
("enter-mode", vec!["normal".into()]),
|
||||||
|
]);
|
||||||
|
ed.bind_seq(KeyCode::Enter, none, vec![
|
||||||
|
("commit-cell-edit", vec![]),
|
||||||
|
("clear-buffer", vec!["edit".into()]),
|
||||||
|
]);
|
||||||
|
ed.bind_seq(KeyCode::Tab, none, vec![
|
||||||
|
("commit-and-advance-right", vec![]),
|
||||||
|
("clear-buffer", vec!["edit".into()]),
|
||||||
|
]);
|
||||||
ed.bind_args(KeyCode::Backspace, none, "pop-char", vec!["edit".into()]);
|
ed.bind_args(KeyCode::Backspace, none, "pop-char", vec!["edit".into()]);
|
||||||
ed.bind_any_char("append-char", vec!["edit".into()]);
|
ed.bind_any_char("append-char", vec!["edit".into()]);
|
||||||
set.insert(ModeKey::Editing, Arc::new(ed));
|
set.insert(ModeKey::Editing, Arc::new(ed));
|
||||||
|
|
||||||
// ── Formula edit ─────────────────────────────────────────────────
|
// ── Formula edit ─────────────────────────────────────────────────
|
||||||
let mut fe = Keymap::new();
|
let mut fe = Keymap::new();
|
||||||
fe.bind_args(
|
fe.bind_seq(KeyCode::Esc, none, vec![
|
||||||
KeyCode::Esc,
|
("clear-buffer", vec!["formula".into()]),
|
||||||
none,
|
("enter-mode", vec!["formula-panel".into()]),
|
||||||
"enter-mode",
|
]);
|
||||||
vec!["formula-panel".into()],
|
fe.bind_seq(KeyCode::Enter, none, vec![
|
||||||
);
|
("commit-formula", vec![]),
|
||||||
fe.bind(KeyCode::Enter, none, "commit-formula");
|
("clear-buffer", vec!["formula".into()]),
|
||||||
|
]);
|
||||||
fe.bind_args(KeyCode::Backspace, none, "pop-char", vec!["formula".into()]);
|
fe.bind_args(KeyCode::Backspace, none, "pop-char", vec!["formula".into()]);
|
||||||
fe.bind_any_char("append-char", vec!["formula".into()]);
|
fe.bind_any_char("append-char", vec!["formula".into()]);
|
||||||
set.insert(ModeKey::FormulaEdit, Arc::new(fe));
|
set.insert(ModeKey::FormulaEdit, Arc::new(fe));
|
||||||
|
|
||||||
// ── Category add ─────────────────────────────────────────────────
|
// ── Category add ─────────────────────────────────────────────────
|
||||||
let mut ca = Keymap::new();
|
let mut ca = Keymap::new();
|
||||||
ca.bind_args(
|
ca.bind_seq(KeyCode::Esc, none, vec![
|
||||||
KeyCode::Esc,
|
("clear-buffer", vec!["category".into()]),
|
||||||
none,
|
("enter-mode", vec!["category-panel".into()]),
|
||||||
"enter-mode",
|
]);
|
||||||
vec!["category-panel".into()],
|
ca.bind_seq(KeyCode::Enter, none, vec![
|
||||||
);
|
("commit-category-add", vec![]),
|
||||||
ca.bind(KeyCode::Enter, none, "commit-category-add");
|
("clear-buffer", vec!["category".into()]),
|
||||||
ca.bind(KeyCode::Tab, none, "commit-category-add");
|
]);
|
||||||
|
ca.bind_seq(KeyCode::Tab, none, vec![
|
||||||
|
("commit-category-add", vec![]),
|
||||||
|
("clear-buffer", vec!["category".into()]),
|
||||||
|
]);
|
||||||
ca.bind_args(
|
ca.bind_args(
|
||||||
KeyCode::Backspace,
|
KeyCode::Backspace,
|
||||||
none,
|
none,
|
||||||
@ -753,30 +784,46 @@ impl KeymapSet {
|
|||||||
|
|
||||||
// ── Item add ─────────────────────────────────────────────────────
|
// ── Item add ─────────────────────────────────────────────────────
|
||||||
let mut ia = Keymap::new();
|
let mut ia = Keymap::new();
|
||||||
ia.bind_args(
|
ia.bind_seq(KeyCode::Esc, none, vec![
|
||||||
KeyCode::Esc,
|
("clear-buffer", vec!["item".into()]),
|
||||||
none,
|
("enter-mode", vec!["category-panel".into()]),
|
||||||
"enter-mode",
|
]);
|
||||||
vec!["category-panel".into()],
|
ia.bind_seq(KeyCode::Enter, none, vec![
|
||||||
);
|
("commit-item-add", vec![]),
|
||||||
ia.bind(KeyCode::Enter, none, "commit-item-add");
|
("clear-buffer", vec!["item".into()]),
|
||||||
ia.bind(KeyCode::Tab, none, "commit-item-add");
|
]);
|
||||||
|
ia.bind_seq(KeyCode::Tab, none, vec![
|
||||||
|
("commit-item-add", vec![]),
|
||||||
|
("clear-buffer", vec!["item".into()]),
|
||||||
|
]);
|
||||||
ia.bind_args(KeyCode::Backspace, none, "pop-char", vec!["item".into()]);
|
ia.bind_args(KeyCode::Backspace, none, "pop-char", vec!["item".into()]);
|
||||||
ia.bind_any_char("append-char", vec!["item".into()]);
|
ia.bind_any_char("append-char", vec!["item".into()]);
|
||||||
set.insert(ModeKey::ItemAdd, Arc::new(ia));
|
set.insert(ModeKey::ItemAdd, Arc::new(ia));
|
||||||
|
|
||||||
// ── Export prompt ────────────────────────────────────────────────
|
// ── Export prompt ────────────────────────────────────────────────
|
||||||
let mut ep = Keymap::new();
|
let mut ep = Keymap::new();
|
||||||
ep.bind_args(KeyCode::Esc, none, "enter-mode", vec!["normal".into()]);
|
ep.bind_seq(KeyCode::Esc, none, vec![
|
||||||
ep.bind(KeyCode::Enter, none, "commit-export");
|
("clear-buffer", vec!["export".into()]),
|
||||||
|
("enter-mode", vec!["normal".into()]),
|
||||||
|
]);
|
||||||
|
ep.bind_seq(KeyCode::Enter, none, vec![
|
||||||
|
("commit-export", vec![]),
|
||||||
|
("clear-buffer", vec!["export".into()]),
|
||||||
|
]);
|
||||||
ep.bind_args(KeyCode::Backspace, none, "pop-char", vec!["export".into()]);
|
ep.bind_args(KeyCode::Backspace, none, "pop-char", vec!["export".into()]);
|
||||||
ep.bind_any_char("append-char", vec!["export".into()]);
|
ep.bind_any_char("append-char", vec!["export".into()]);
|
||||||
set.insert(ModeKey::ExportPrompt, Arc::new(ep));
|
set.insert(ModeKey::ExportPrompt, Arc::new(ep));
|
||||||
|
|
||||||
// ── Command mode ─────────────────────────────────────────────────
|
// ── Command mode ─────────────────────────────────────────────────
|
||||||
let mut cm = Keymap::new();
|
let mut cm = Keymap::new();
|
||||||
cm.bind_args(KeyCode::Esc, none, "enter-mode", vec!["normal".into()]);
|
cm.bind_seq(KeyCode::Esc, none, vec![
|
||||||
cm.bind(KeyCode::Enter, none, "execute-command");
|
("clear-buffer", vec!["command".into()]),
|
||||||
|
("enter-mode", vec!["normal".into()]),
|
||||||
|
]);
|
||||||
|
cm.bind_seq(KeyCode::Enter, none, vec![
|
||||||
|
("execute-command", vec![]),
|
||||||
|
("clear-buffer", vec!["command".into()]),
|
||||||
|
]);
|
||||||
cm.bind(KeyCode::Backspace, none, "command-mode-backspace");
|
cm.bind(KeyCode::Backspace, none, "command-mode-backspace");
|
||||||
cm.bind_any_char("append-char", vec!["command".into()]);
|
cm.bind_any_char("append-char", vec!["command".into()]);
|
||||||
set.insert(ModeKey::CommandMode, Arc::new(cm));
|
set.insert(ModeKey::CommandMode, Arc::new(cm));
|
||||||
|
|||||||
Reference in New Issue
Block a user