refactor(command): rename commands and extract panel/axis parsers

Rename several commands for consistency:
- save -> save-as (SaveAsCmd)
- save-cmd -> save (SaveCmd)
- enter-search -> search (EnterSearchMode)
- enter-edit -> enter-edit-mode (EnterEditMode)
- exit-search -> exit-search-mode (ExitSearchMode)

Add new commands:
- search-or-category-add
- search-append-char
- search-pop-char
- toggle-panel-and-focus
- toggle-panel-visibility
- command-mode-backspace

Extract parse_panel() and parse_axis() helper functions to replace
repeated match statements. Update documentation comments to reflect
quasi-lisp syntax instead of Forth-style.

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 10:57:37 -07:00
parent 830869d91c
commit a45390b7a9
3 changed files with 56 additions and 43 deletions

View File

@ -1786,8 +1786,8 @@ effect_cmd!(
effect_cmd!(
SaveAsCmd,
"save",
|args: &[String]| require_args("save", args, 1),
"save-as",
|args: &[String]| require_args("save-as", args, 1),
|args: &Vec<String>, _ctx: &CmdContext| -> Vec<Box<dyn Effect>> {
vec![Box::new(effect::SaveAs(std::path::PathBuf::from(
&args[0],
@ -1836,11 +1836,12 @@ effect_cmd!(
}
);
/// Build the default command registry with all parseable commands.
/// Build the default command registry with all commands.
/// Registry names MUST match the `Cmd::name()` return value.
pub fn default_registry() -> CmdRegistry {
let mut r = CmdRegistry::new();
// Model mutations (effect_cmd! wrappers)
// ── Model mutations (effect_cmd! wrappers) ───────────────────────────
r.register("add-category", AddCategoryCmd::parse);
r.register("add-item", AddItemCmd::parse);
r.register("add-item-in-group", AddItemInGroupCmd::parse);
@ -1856,12 +1857,12 @@ pub fn default_registry() -> CmdRegistry {
r.register("toggle-group", ToggleGroupCmd::parse);
r.register("hide-item", HideItemCmd::parse);
r.register("show-item", ShowItemCmd::parse);
r.register("save", SaveAsCmd::parse);
r.register("save-as", SaveAsCmd::parse);
r.register("load", LoadModelCmd::parse);
r.register("export-csv", ExportCsvCmd::parse);
r.register("import-json", ImportJsonCmd::parse);
// Navigation (zero-arg or simple-arg commands that already exist as Cmd structs)
// ── Navigation ───────────────────────────────────────────────────────
r.register("move-selection", |args| {
require_args("move-selection", args, 2)?;
let dr = args[0].parse::<i32>().map_err(|e| e.to_string())?;
@ -1879,22 +1880,22 @@ pub fn default_registry() -> CmdRegistry {
});
r.register("enter-advance", |_| Ok(Box::new(EnterAdvance)));
// Cell operations
// ── Cell operations ──────────────────────────────────────────────────
r.register("yank", |_| Ok(Box::new(YankCell)));
r.register("paste", |_| Ok(Box::new(PasteCell)));
r.register("clear-selected-cell", |_| Ok(Box::new(ClearSelectedCell)));
// View operations
// ── View / page ──────────────────────────────────────────────────────
r.register("transpose", |_| Ok(Box::new(TransposeAxes)));
r.register("page-next", |_| Ok(Box::new(PageNext)));
r.register("page-prev", |_| Ok(Box::new(PagePrev)));
// Mode changes
// ── Mode changes ─────────────────────────────────────────────────────
r.register("force-quit", |_| Ok(Box::new(ForceQuit)));
r.register("save-and-quit", |_| Ok(Box::new(SaveAndQuit)));
r.register("save-cmd", |_| Ok(Box::new(SaveCmd)));
r.register("enter-search", |_| Ok(Box::new(EnterSearchMode)));
r.register("enter-edit", |_| Ok(Box::new(EnterEditMode)));
r.register("save", |_| Ok(Box::new(SaveCmd)));
r.register("search", |_| Ok(Box::new(EnterSearchMode)));
r.register("enter-edit-mode", |_| Ok(Box::new(EnterEditMode)));
r.register("enter-export-prompt", |_| Ok(Box::new(EnterExportPrompt)));
r.register("enter-formula-edit", |_| Ok(Box::new(EnterFormulaEdit)));
r.register("enter-tile-select", |_| Ok(Box::new(EnterTileSelect)));
@ -1913,33 +1914,31 @@ pub fn default_registry() -> CmdRegistry {
Ok(Box::new(EnterMode(mode)))
});
// Search
// ── Search ───────────────────────────────────────────────────────────
r.register("search-navigate", |args| {
let forward = args.first().map(|s| s != "backward").unwrap_or(true);
Ok(Box::new(SearchNavigate(forward)))
});
r.register("exit-search", |_| Ok(Box::new(ExitSearchMode)));
r.register("search-or-category-add", |_| Ok(Box::new(SearchOrCategoryAdd)));
r.register("exit-search-mode", |_| Ok(Box::new(ExitSearchMode)));
r.register("search-append-char", |_| Ok(Box::new(SearchAppendChar)));
r.register("search-pop-char", |_| Ok(Box::new(SearchPopChar)));
// Panel operations
r.register("toggle-panel", |args| {
require_args("toggle-panel", args, 1)?;
let panel = match args[0].as_str() {
"formula" => effect::Panel::Formula,
"category" => effect::Panel::Category,
"view" => effect::Panel::View,
other => return Err(format!("Unknown panel: {other}")),
};
// ── Panel operations ─────────────────────────────────────────────────
r.register("toggle-panel-and-focus", |args| {
require_args("toggle-panel-and-focus", args, 1)?;
let panel = parse_panel(&args[0])?;
Ok(Box::new(TogglePanelAndFocus(panel)))
});
r.register("toggle-panel-visibility", |args| {
require_args("toggle-panel-visibility", args, 1)?;
let panel = parse_panel(&args[0])?;
Ok(Box::new(TogglePanelVisibility(panel)))
});
r.register("cycle-panel-focus", |_| Ok(Box::new(CyclePanelFocus)));
r.register("move-panel-cursor", |args| {
require_args("move-panel-cursor", args, 2)?;
let panel = match args[0].as_str() {
"formula" => effect::Panel::Formula,
"category" => effect::Panel::Category,
"view" => effect::Panel::View,
other => return Err(format!("Unknown panel: {other}")),
};
let panel = parse_panel(&args[0])?;
let delta = args[1].parse::<i32>().map_err(|e| e.to_string())?;
Ok(Box::new(MovePanelCursor { panel, delta }))
});
@ -1950,7 +1949,7 @@ pub fn default_registry() -> CmdRegistry {
r.register("create-and-switch-view", |_| Ok(Box::new(CreateAndSwitchView)));
r.register("delete-view-at-cursor", |_| Ok(Box::new(DeleteViewAtCursor)));
// Tile select
// ── Tile select ──────────────────────────────────────────────────────
r.register("move-tile-cursor", |args| {
require_args("move-tile-cursor", args, 1)?;
let delta = args[0].parse::<i32>().map_err(|e| e.to_string())?;
@ -1959,22 +1958,16 @@ pub fn default_registry() -> CmdRegistry {
r.register("cycle-axis-for-tile", |_| Ok(Box::new(CycleAxisForTile)));
r.register("set-axis-for-tile", |args| {
require_args("set-axis-for-tile", args, 1)?;
let axis = match args[0].to_lowercase().as_str() {
"row" => Axis::Row,
"column" | "col" => Axis::Column,
"page" => Axis::Page,
"none" => Axis::None,
other => return Err(format!("Unknown axis: {other}")),
};
let axis = parse_axis(&args[0])?;
Ok(Box::new(SetAxisForTile(axis)))
});
// Grid operations
// ── Grid operations ──────────────────────────────────────────────────
r.register("toggle-group-under-cursor", |_| Ok(Box::new(ToggleGroupUnderCursor)));
r.register("toggle-col-group-under-cursor", |_| Ok(Box::new(ToggleColGroupUnderCursor)));
r.register("hide-selected-row-item", |_| Ok(Box::new(HideSelectedRowItem)));
// Text buffer
// ── Text buffer ──────────────────────────────────────────────────────
r.register("append-char", |args| {
require_args("append-char", args, 1)?;
Ok(Box::new(AppendChar { buffer: args[0].clone() }))
@ -1983,8 +1976,9 @@ pub fn default_registry() -> CmdRegistry {
require_args("pop-char", args, 1)?;
Ok(Box::new(PopChar { buffer: args[0].clone() }))
});
r.register("command-mode-backspace", |_| Ok(Box::new(CommandModeBackspace)));
// Commit commands
// ── Commit ───────────────────────────────────────────────────────────
r.register("commit-cell-edit", |_| Ok(Box::new(CommitCellEdit)));
r.register("commit-formula", |_| Ok(Box::new(CommitFormula)));
r.register("commit-category-add", |_| Ok(Box::new(CommitCategoryAdd)));
@ -1992,12 +1986,31 @@ pub fn default_registry() -> CmdRegistry {
r.register("commit-export", |_| Ok(Box::new(CommitExport)));
r.register("execute-command", |_| Ok(Box::new(ExecuteCommand)));
// Wizard
// ── Wizard ───────────────────────────────────────────────────────────
r.register("handle-wizard-key", |_| Ok(Box::new(HandleWizardKey)));
r
}
fn parse_panel(s: &str) -> Result<Panel, String> {
match s {
"formula" => Ok(Panel::Formula),
"category" => Ok(Panel::Category),
"view" => Ok(Panel::View),
other => Err(format!("Unknown panel: {other}")),
}
}
fn parse_axis(s: &str) -> Result<Axis, String> {
match s.to_lowercase().as_str() {
"row" => Ok(Axis::Row),
"column" | "col" => Ok(Axis::Column),
"page" => Ok(Axis::Page),
"none" => Ok(Axis::None),
other => Err(format!("Unknown axis: {other}")),
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -2,7 +2,7 @@
//! replayed, scripted, and tested without the TUI.
//!
//! Commands are trait objects (`dyn Cmd`) that produce effects (`dyn Effect`).
//! The headless CLI (--cmd / --script) parses Forth-style text into effects
//! The headless CLI (--cmd / --script) parses quasi-lisp text into effects
//! and applies them directly.
pub mod cmd;

View File

@ -1,4 +1,4 @@
//! Forth-style prefix command parser.
//! Quasi-lisp prefix command parser.
//!
//! Syntax: `word arg1 arg2 ...`
//! Multiple commands on one line separated by `.`