Merge pull request #2 from fiddlerwoaroof/command-algebra

refactor(cmd): command algebra with Binding::Sequence and unified primitives
This commit is contained in:
Ed Langley
2026-04-07 00:11:46 -07:00
committed by Edward Langley
2 changed files with 442 additions and 397 deletions

File diff suppressed because it is too large Load Diff

View File

@ -72,6 +72,8 @@ pub enum Binding {
}, },
/// A prefix sub-keymap (Emacs-style). /// A prefix sub-keymap (Emacs-style).
Prefix(Arc<Keymap>), Prefix(Arc<Keymap>),
/// A sequence of commands executed in order, concatenating their effects.
Sequence(Vec<(&'static str, Vec<String>)>),
} }
/// 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).
@ -121,6 +123,17 @@ impl Keymap {
.insert(KeyPattern::Key(key, mods), Binding::Prefix(sub)); .insert(KeyPattern::Key(key, mods), Binding::Prefix(sub));
} }
/// Bind a key to a sequence of commands (executed in order).
pub fn bind_seq(
&mut self,
key: KeyCode,
mods: KeyModifiers,
steps: Vec<(&'static str, Vec<String>)>,
) {
self.bindings
.insert(KeyPattern::Key(key, mods), Binding::Sequence(steps));
}
/// Bind a catch-all for any Char key. /// Bind a catch-all for any Char key.
pub fn bind_any_char(&mut self, name: &'static str, args: Vec<String>) { pub fn bind_any_char(&mut self, name: &'static str, args: Vec<String>) {
self.bindings self.bindings
@ -173,6 +186,14 @@ impl Keymap {
Some(cmd.execute(ctx)) Some(cmd.execute(ctx))
} }
Binding::Prefix(sub) => Some(vec![Box::new(SetTransientKeymap(sub.clone()))]), Binding::Prefix(sub) => Some(vec![Box::new(SetTransientKeymap(sub.clone()))]),
Binding::Sequence(steps) => {
let mut effects: Vec<Box<dyn Effect>> = Vec::new();
for (name, args) in steps {
let cmd = registry.interactive(name, args, ctx).ok()?;
effects.extend(cmd.execute(ctx));
}
Some(effects)
}
} }
} }
} }
@ -360,7 +381,11 @@ impl KeymapSet {
// Drill into aggregated cell / view history / add row // Drill into aggregated cell / view history / add row
normal.bind(KeyCode::Char('>'), none, "drill-into-cell"); normal.bind(KeyCode::Char('>'), none, "drill-into-cell");
normal.bind(KeyCode::Char('<'), none, "view-back"); normal.bind(KeyCode::Char('<'), none, "view-back");
normal.bind(KeyCode::Char('o'), none, "open-record-row"); normal.bind_seq(
KeyCode::Char('o'),
none,
vec![("add-record-row", vec![]), ("enter-edit-at-cursor", vec![])],
);
// Records mode toggle and prune toggle // Records mode toggle and prune toggle
normal.bind(KeyCode::Char('R'), none, "toggle-records-mode"); normal.bind(KeyCode::Char('R'), none, "toggle-records-mode");
@ -384,7 +409,11 @@ impl KeymapSet {
normal.bind_prefix(KeyCode::Char('y'), none, Arc::new(y_map)); normal.bind_prefix(KeyCode::Char('y'), none, Arc::new(y_map));
let mut z_map = Keymap::new(); let mut z_map = Keymap::new();
z_map.bind(KeyCode::Char('Z'), none, "save-and-quit"); z_map.bind_seq(
KeyCode::Char('Z'),
none,
vec![("save", vec![]), ("force-quit", vec![])],
);
normal.bind_prefix(KeyCode::Char('Z'), none, Arc::new(z_map)); normal.bind_prefix(KeyCode::Char('Z'), none, Arc::new(z_map));
set.insert(ModeKey::Normal, Arc::new(normal)); set.insert(ModeKey::Normal, Arc::new(normal));
@ -664,8 +693,8 @@ impl KeymapSet {
let mut sm = Keymap::new(); let mut sm = Keymap::new();
sm.bind(KeyCode::Esc, none, "exit-search-mode"); sm.bind(KeyCode::Esc, none, "exit-search-mode");
sm.bind(KeyCode::Enter, none, "exit-search-mode"); sm.bind(KeyCode::Enter, none, "exit-search-mode");
sm.bind(KeyCode::Backspace, none, "search-pop-char"); sm.bind_args(KeyCode::Backspace, none, "pop-char", vec!["search".into()]);
sm.bind_any_char("search-append-char", vec![]); sm.bind_any_char("append-char", vec!["search".into()]);
set.insert(ModeKey::SearchMode, Arc::new(sm)); set.insert(ModeKey::SearchMode, Arc::new(sm));
// ── Import wizard ──────────────────────────────────────────────── // ── Import wizard ────────────────────────────────────────────────