refactor(cmd): introduce command algebra with Binding::Sequence and unified primitives
Add Binding::Sequence to keymap for composing commands, then use it and parameterization to eliminate redundant command structs: - Unify MoveSelection/JumpToEdge/ScrollRows/PageScroll into Move - Merge ToggleGroupUnderCursor + ToggleColGroupUnderCursor → ToggleGroupAtCursor - Merge CommitCellEdit + CommitAndAdvanceRight → CommitAndAdvance - Merge CycleAxisForTile + SetAxisForTile → TileAxisOp - Merge ViewBackCmd + ViewForwardCmd → ViewNavigate - Delete SearchAppendChar/SearchPopChar (reuse AppendChar/PopChar with "search") - Replace SaveAndQuit/OpenRecordRow with keymap sequences - Extract commit_add_from_buffer helper for CommitCategoryAdd/CommitItemAdd - Add algebraic law tests (idempotence, involution, associativity) https://claude.ai/code/session_01Y9X6VKyZAW3xo1nfThDRYU
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@ -72,6 +72,8 @@ pub enum Binding {
|
||||
},
|
||||
/// A prefix sub-keymap (Emacs-style).
|
||||
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).
|
||||
@ -121,6 +123,17 @@ impl Keymap {
|
||||
.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.
|
||||
pub fn bind_any_char(&mut self, name: &'static str, args: Vec<String>) {
|
||||
self.bindings
|
||||
@ -173,6 +186,14 @@ impl Keymap {
|
||||
Some(cmd.execute(ctx))
|
||||
}
|
||||
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,14 @@ impl KeymapSet {
|
||||
// Drill into aggregated cell / view history / add row
|
||||
normal.bind(KeyCode::Char('>'), none, "drill-into-cell");
|
||||
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
|
||||
normal.bind(KeyCode::Char('R'), none, "toggle-records-mode");
|
||||
@ -384,7 +412,11 @@ impl KeymapSet {
|
||||
normal.bind_prefix(KeyCode::Char('y'), none, Arc::new(y_map));
|
||||
|
||||
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));
|
||||
|
||||
set.insert(ModeKey::Normal, Arc::new(normal));
|
||||
@ -664,8 +696,13 @@ impl KeymapSet {
|
||||
let mut sm = Keymap::new();
|
||||
sm.bind(KeyCode::Esc, none, "exit-search-mode");
|
||||
sm.bind(KeyCode::Enter, none, "exit-search-mode");
|
||||
sm.bind(KeyCode::Backspace, none, "search-pop-char");
|
||||
sm.bind_any_char("search-append-char", vec![]);
|
||||
sm.bind_args(
|
||||
KeyCode::Backspace,
|
||||
none,
|
||||
"pop-char",
|
||||
vec!["search".into()],
|
||||
);
|
||||
sm.bind_any_char("append-char", vec!["search".into()]);
|
||||
set.insert(ModeKey::SearchMode, Arc::new(sm));
|
||||
|
||||
// ── Import wizard ────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user