refactor(command): switch to prototype-based command registration

Refactor command registry to use prototype-based registration instead of
string-based names. This makes the API more consistent and type-safe.

Changes:
- Changed Cmd::name() to return &'static str instead of &str
- Updated CmdRegistry::register, register_pure, and register_nullary to accept
  prototype command instances instead of string names
- Added NamedCmd helper struct for cases where command is built by closure
- Updated all command implementations to return static string literals
- Modified EnterMode::execute to clear buffers when entering text-entry modes

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 18:37:43 -07:00
parent e2ff9cf98e
commit 89fdb27d6c

View File

@ -3,7 +3,7 @@ use std::fmt::Debug;
use crossterm::event::KeyCode; use crossterm::event::KeyCode;
use crate::model::cell::CellValue; use crate::model::cell::{CellKey, CellValue};
use crate::model::Model; use crate::model::Model;
use crate::ui::app::AppMode; use crate::ui::app::AppMode;
use crate::ui::effect::{self, Effect, Panel}; use crate::ui::effect::{self, Effect, Panel};
@ -46,7 +46,7 @@ pub trait Cmd: Debug + Send + Sync {
/// The canonical name of this command (matches its registry key). /// The canonical name of this command (matches its registry key).
/// Used by the parser tests and for introspection. /// Used by the parser tests and for introspection.
#[allow(dead_code)] #[allow(dead_code)]
fn name(&self) -> &str; fn name(&self) -> &'static str;
} }
/// Factory that constructs a Cmd from text arguments (headless/script). /// Factory that constructs a Cmd from text arguments (headless/script).
@ -81,27 +81,41 @@ impl CmdRegistry {
} }
/// Register a command with both a text parser and an interactive constructor. /// Register a command with both a text parser and an interactive constructor.
pub fn register(&mut self, name: &'static str, parse: ParseFn, interactive: InteractiveFn) { /// The name is derived from a prototype command instance.
pub fn register(
&mut self,
prototype: &dyn Cmd,
parse: ParseFn,
interactive: InteractiveFn,
) {
self.entries.push(CmdEntry { self.entries.push(CmdEntry {
name, name: prototype.name(),
parse: Box::new(parse), parse: Box::new(parse),
interactive: Box::new(interactive), interactive: Box::new(interactive),
}); });
} }
/// Register a command that takes no context — same constructor for both paths. /// Register a command that doesn't need interactive context.
pub fn register_pure(&mut self, name: &'static str, parse: ParseFn) { /// When called interactively with args, delegates to parse.
/// When called interactively without args, returns an error.
pub fn register_pure(&mut self, prototype: &dyn Cmd, parse: ParseFn) {
self.entries.push(CmdEntry { self.entries.push(CmdEntry {
name, name: prototype.name(),
parse: Box::new(parse), parse: Box::new(parse),
interactive: Box::new(|_args, _ctx| { interactive: Box::new(move |args, _ctx| {
Err("not available interactively without args".into()) if args.is_empty() {
Err("this command requires arguments".into())
} else {
parse(args)
}
}), }),
}); });
} }
/// Register a zero-arg command (same instance for parse and interactive). /// Register a zero-arg command (same instance for parse and interactive).
pub fn register_nullary(&mut self, name: &'static str, f: fn() -> Box<dyn Cmd>) { /// The name is derived by calling `f()` once.
pub fn register_nullary(&mut self, f: fn() -> Box<dyn Cmd>) {
let name = f().name();
self.entries.push(CmdEntry { self.entries.push(CmdEntry {
name, name,
parse: Box::new(move |_| Ok(f())), parse: Box::new(move |_| Ok(f())),
@ -142,6 +156,19 @@ impl CmdRegistry {
} }
} }
/// Dummy prototype used only for name extraction in registry calls
/// where the real command struct is built by a closure.
#[derive(Debug)]
struct NamedCmd(&'static str);
impl Cmd for NamedCmd {
fn name(&self) -> &'static str {
self.0
}
fn execute(&self, _: &CmdContext) -> Vec<Box<dyn Effect>> {
vec![]
}
}
fn require_args(word: &str, args: &[String], n: usize) -> Result<(), String> { fn require_args(word: &str, args: &[String], n: usize) -> Result<(), String> {
if args.len() < n { if args.len() < n {
Err(format!( Err(format!(
@ -170,7 +197,7 @@ fn parse_cell_key_from_args(args: &[String]) -> crate::model::cell::CellKey {
// fills position/bounds from context; the parser accepts them as args. // fills position/bounds from context; the parser accepts them as args.
/// Shared viewport state for navigation commands. /// Shared viewport state for navigation commands.
#[derive(Debug, Clone)] #[derive(Debug, Clone, Default)]
pub struct CursorState { pub struct CursorState {
pub row: usize, pub row: usize,
pub col: usize, pub col: usize,
@ -232,7 +259,7 @@ pub struct MoveSelection {
} }
impl Cmd for MoveSelection { impl Cmd for MoveSelection {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"move-selection" "move-selection"
} }
@ -250,7 +277,7 @@ pub struct JumpToFirstRow {
pub col: usize, pub col: usize,
} }
impl Cmd for JumpToFirstRow { impl Cmd for JumpToFirstRow {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"jump-first-row" "jump-first-row"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -268,7 +295,7 @@ pub struct JumpToLastRow {
pub row_offset: usize, pub row_offset: usize,
} }
impl Cmd for JumpToLastRow { impl Cmd for JumpToLastRow {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"jump-last-row" "jump-last-row"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -286,7 +313,7 @@ pub struct JumpToFirstCol {
pub row: usize, pub row: usize,
} }
impl Cmd for JumpToFirstCol { impl Cmd for JumpToFirstCol {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"jump-first-col" "jump-first-col"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -304,7 +331,7 @@ pub struct JumpToLastCol {
pub col_offset: usize, pub col_offset: usize,
} }
impl Cmd for JumpToLastCol { impl Cmd for JumpToLastCol {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"jump-last-col" "jump-last-col"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -323,7 +350,7 @@ pub struct ScrollRows {
pub cursor: CursorState, pub cursor: CursorState,
} }
impl Cmd for ScrollRows { impl Cmd for ScrollRows {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"scroll-rows" "scroll-rows"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -350,18 +377,35 @@ impl Cmd for ScrollRows {
#[derive(Debug)] #[derive(Debug)]
pub struct EnterMode(pub AppMode); pub struct EnterMode(pub AppMode);
impl Cmd for EnterMode { impl Cmd for EnterMode {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"enter-mode" "enter-mode"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
vec![effect::change_mode(self.0.clone())] let mut effects: Vec<Box<dyn Effect>> = Vec::new();
// Clear the corresponding buffer when entering a text-entry mode
let buffer_name = match &self.0 {
AppMode::CommandMode { .. } => Some("command"),
AppMode::Editing { .. } => Some("edit"),
AppMode::FormulaEdit { .. } => Some("formula"),
AppMode::CategoryAdd { .. } => Some("category"),
AppMode::ExportPrompt { .. } => Some("export"),
_ => None,
};
if let Some(name) = buffer_name {
effects.push(Box::new(effect::SetBuffer {
name: name.to_string(),
value: String::new(),
}));
}
effects.push(effect::change_mode(self.0.clone()));
effects
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub struct ForceQuit; pub struct ForceQuit;
impl Cmd for ForceQuit { impl Cmd for ForceQuit {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"force-quit" "force-quit"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -372,7 +416,7 @@ impl Cmd for ForceQuit {
#[derive(Debug)] #[derive(Debug)]
pub struct SaveAndQuit; pub struct SaveAndQuit;
impl Cmd for SaveAndQuit { impl Cmd for SaveAndQuit {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"save-and-quit" "save-and-quit"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -390,7 +434,7 @@ pub struct ClearCellCommand {
pub key: crate::model::cell::CellKey, pub key: crate::model::cell::CellKey,
} }
impl Cmd for ClearCellCommand { impl Cmd for ClearCellCommand {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"clear-cell" "clear-cell"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -407,7 +451,7 @@ pub struct YankCell {
pub key: crate::model::cell::CellKey, pub key: crate::model::cell::CellKey,
} }
impl Cmd for YankCell { impl Cmd for YankCell {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"yank" "yank"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -426,7 +470,7 @@ pub struct PasteCell {
pub key: crate::model::cell::CellKey, pub key: crate::model::cell::CellKey,
} }
impl Cmd for PasteCell { impl Cmd for PasteCell {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"paste" "paste"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -446,7 +490,7 @@ impl Cmd for PasteCell {
#[derive(Debug)] #[derive(Debug)]
pub struct TransposeAxes; pub struct TransposeAxes;
impl Cmd for TransposeAxes { impl Cmd for TransposeAxes {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"transpose" "transpose"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -457,7 +501,7 @@ impl Cmd for TransposeAxes {
#[derive(Debug)] #[derive(Debug)]
pub struct SaveCmd; pub struct SaveCmd;
impl Cmd for SaveCmd { impl Cmd for SaveCmd {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"save" "save"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -470,7 +514,7 @@ impl Cmd for SaveCmd {
#[derive(Debug)] #[derive(Debug)]
pub struct EnterSearchMode; pub struct EnterSearchMode;
impl Cmd for EnterSearchMode { impl Cmd for EnterSearchMode {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"search" "search"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -490,7 +534,7 @@ pub struct TogglePanelAndFocus {
pub currently_open: bool, pub currently_open: bool,
} }
impl Cmd for TogglePanelAndFocus { impl Cmd for TogglePanelAndFocus {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"toggle-panel-and-focus" "toggle-panel-and-focus"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -518,7 +562,7 @@ pub struct TogglePanelVisibility {
pub currently_open: bool, pub currently_open: bool,
} }
impl Cmd for TogglePanelVisibility { impl Cmd for TogglePanelVisibility {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"toggle-panel-visibility" "toggle-panel-visibility"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -537,7 +581,7 @@ pub struct CyclePanelFocus {
pub view_open: bool, pub view_open: bool,
} }
impl Cmd for CyclePanelFocus { impl Cmd for CyclePanelFocus {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"cycle-panel-focus" "cycle-panel-focus"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -561,7 +605,7 @@ pub struct EnterEditMode {
pub initial_value: String, pub initial_value: String,
} }
impl Cmd for EnterEditMode { impl Cmd for EnterEditMode {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"enter-edit-mode" "enter-edit-mode"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -583,7 +627,7 @@ pub struct EnterAdvance {
pub cursor: CursorState, pub cursor: CursorState,
} }
impl Cmd for EnterAdvance { impl Cmd for EnterAdvance {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"enter-advance" "enter-advance"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -605,7 +649,7 @@ impl Cmd for EnterAdvance {
#[derive(Debug)] #[derive(Debug)]
pub struct EnterExportPrompt; pub struct EnterExportPrompt;
impl Cmd for EnterExportPrompt { impl Cmd for EnterExportPrompt {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"enter-export-prompt" "enter-export-prompt"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -621,7 +665,7 @@ impl Cmd for EnterExportPrompt {
#[derive(Debug)] #[derive(Debug)]
pub struct SearchNavigate(pub bool); pub struct SearchNavigate(pub bool);
impl Cmd for SearchNavigate { impl Cmd for SearchNavigate {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"search-navigate" "search-navigate"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -703,7 +747,7 @@ impl Cmd for SearchNavigate {
#[derive(Debug)] #[derive(Debug)]
pub struct SearchOrCategoryAdd; pub struct SearchOrCategoryAdd;
impl Cmd for SearchOrCategoryAdd { impl Cmd for SearchOrCategoryAdd {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"search-or-category-add" "search-or-category-add"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -729,7 +773,7 @@ impl Cmd for SearchOrCategoryAdd {
#[derive(Debug)] #[derive(Debug)]
pub struct PageNext; pub struct PageNext;
impl Cmd for PageNext { impl Cmd for PageNext {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"page-next" "page-next"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -766,7 +810,7 @@ impl Cmd for PageNext {
#[derive(Debug)] #[derive(Debug)]
pub struct PagePrev; pub struct PagePrev;
impl Cmd for PagePrev { impl Cmd for PagePrev {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"page-prev" "page-prev"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -840,7 +884,7 @@ fn page_cat_data(ctx: &CmdContext) -> Vec<(String, Vec<String>, usize)> {
#[derive(Debug)] #[derive(Debug)]
pub struct ToggleGroupUnderCursor; pub struct ToggleGroupUnderCursor;
impl Cmd for ToggleGroupUnderCursor { impl Cmd for ToggleGroupUnderCursor {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"toggle-group-under-cursor" "toggle-group-under-cursor"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -863,7 +907,7 @@ impl Cmd for ToggleGroupUnderCursor {
#[derive(Debug)] #[derive(Debug)]
pub struct ToggleColGroupUnderCursor; pub struct ToggleColGroupUnderCursor;
impl Cmd for ToggleColGroupUnderCursor { impl Cmd for ToggleColGroupUnderCursor {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"toggle-col-group-under-cursor" "toggle-col-group-under-cursor"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -889,7 +933,7 @@ impl Cmd for ToggleColGroupUnderCursor {
#[derive(Debug)] #[derive(Debug)]
pub struct HideSelectedRowItem; pub struct HideSelectedRowItem;
impl Cmd for HideSelectedRowItem { impl Cmd for HideSelectedRowItem {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"hide-selected-row-item" "hide-selected-row-item"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -927,7 +971,7 @@ impl Cmd for HideSelectedRowItem {
#[derive(Debug)] #[derive(Debug)]
pub struct EnterTileSelect; pub struct EnterTileSelect;
impl Cmd for EnterTileSelect { impl Cmd for EnterTileSelect {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"enter-tile-select" "enter-tile-select"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -954,7 +998,7 @@ pub struct MovePanelCursor {
pub max: usize, pub max: usize,
} }
impl Cmd for MovePanelCursor { impl Cmd for MovePanelCursor {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"move-panel-cursor" "move-panel-cursor"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -982,7 +1026,7 @@ impl Cmd for MovePanelCursor {
#[derive(Debug)] #[derive(Debug)]
pub struct EnterFormulaEdit; pub struct EnterFormulaEdit;
impl Cmd for EnterFormulaEdit { impl Cmd for EnterFormulaEdit {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"enter-formula-edit" "enter-formula-edit"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -996,7 +1040,7 @@ impl Cmd for EnterFormulaEdit {
#[derive(Debug)] #[derive(Debug)]
pub struct DeleteFormulaAtCursor; pub struct DeleteFormulaAtCursor;
impl Cmd for DeleteFormulaAtCursor { impl Cmd for DeleteFormulaAtCursor {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"delete-formula-at-cursor" "delete-formula-at-cursor"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1030,7 +1074,7 @@ impl Cmd for DeleteFormulaAtCursor {
#[derive(Debug)] #[derive(Debug)]
pub struct CycleAxisAtCursor; pub struct CycleAxisAtCursor;
impl Cmd for CycleAxisAtCursor { impl Cmd for CycleAxisAtCursor {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"cycle-axis-at-cursor" "cycle-axis-at-cursor"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1047,7 +1091,7 @@ impl Cmd for CycleAxisAtCursor {
#[derive(Debug)] #[derive(Debug)]
pub struct OpenItemAddAtCursor; pub struct OpenItemAddAtCursor;
impl Cmd for OpenItemAddAtCursor { impl Cmd for OpenItemAddAtCursor {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"open-item-add-at-cursor" "open-item-add-at-cursor"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1071,7 +1115,7 @@ impl Cmd for OpenItemAddAtCursor {
#[derive(Debug)] #[derive(Debug)]
pub struct SwitchViewAtCursor; pub struct SwitchViewAtCursor;
impl Cmd for SwitchViewAtCursor { impl Cmd for SwitchViewAtCursor {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"switch-view-at-cursor" "switch-view-at-cursor"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1091,7 +1135,7 @@ impl Cmd for SwitchViewAtCursor {
#[derive(Debug)] #[derive(Debug)]
pub struct CreateAndSwitchView; pub struct CreateAndSwitchView;
impl Cmd for CreateAndSwitchView { impl Cmd for CreateAndSwitchView {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"create-and-switch-view" "create-and-switch-view"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1109,7 +1153,7 @@ impl Cmd for CreateAndSwitchView {
#[derive(Debug)] #[derive(Debug)]
pub struct DeleteViewAtCursor; pub struct DeleteViewAtCursor;
impl Cmd for DeleteViewAtCursor { impl Cmd for DeleteViewAtCursor {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"delete-view-at-cursor" "delete-view-at-cursor"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1138,7 +1182,7 @@ impl Cmd for DeleteViewAtCursor {
#[derive(Debug)] #[derive(Debug)]
pub struct MoveTileCursor(pub i32); pub struct MoveTileCursor(pub i32);
impl Cmd for MoveTileCursor { impl Cmd for MoveTileCursor {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"move-tile-cursor" "move-tile-cursor"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1155,7 +1199,7 @@ impl Cmd for MoveTileCursor {
#[derive(Debug)] #[derive(Debug)]
pub struct CycleAxisForTile; pub struct CycleAxisForTile;
impl Cmd for CycleAxisForTile { impl Cmd for CycleAxisForTile {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"cycle-axis-for-tile" "cycle-axis-for-tile"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1176,7 +1220,7 @@ impl Cmd for CycleAxisForTile {
#[derive(Debug)] #[derive(Debug)]
pub struct SetAxisForTile(pub Axis); pub struct SetAxisForTile(pub Axis);
impl Cmd for SetAxisForTile { impl Cmd for SetAxisForTile {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"set-axis-for-tile" "set-axis-for-tile"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1202,7 +1246,7 @@ impl Cmd for SetAxisForTile {
#[derive(Debug)] #[derive(Debug)]
pub struct HandleWizardKey; pub struct HandleWizardKey;
impl Cmd for HandleWizardKey { impl Cmd for HandleWizardKey {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"handle-wizard-key" "handle-wizard-key"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1218,7 +1262,7 @@ impl Cmd for HandleWizardKey {
#[derive(Debug)] #[derive(Debug)]
pub struct ExecuteCommand; pub struct ExecuteCommand;
impl Cmd for ExecuteCommand { impl Cmd for ExecuteCommand {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"execute-command" "execute-command"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1418,7 +1462,7 @@ pub struct AppendChar {
pub buffer: String, pub buffer: String,
} }
impl Cmd for AppendChar { impl Cmd for AppendChar {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"append-char" "append-char"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1441,7 +1485,7 @@ pub struct PopChar {
pub buffer: String, pub buffer: String,
} }
impl Cmd for PopChar { impl Cmd for PopChar {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"pop-char" "pop-char"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1463,7 +1507,7 @@ pub struct CommitCellEdit {
pub value: String, pub value: String,
} }
impl Cmd for CommitCellEdit { impl Cmd for CommitCellEdit {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"commit-cell-edit" "commit-cell-edit"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1497,7 +1541,7 @@ impl Cmd for CommitCellEdit {
#[derive(Debug)] #[derive(Debug)]
pub struct CommitFormula; pub struct CommitFormula;
impl Cmd for CommitFormula { impl Cmd for CommitFormula {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"commit-formula" "commit-formula"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1528,7 +1572,7 @@ impl Cmd for CommitFormula {
#[derive(Debug)] #[derive(Debug)]
pub struct CommitCategoryAdd; pub struct CommitCategoryAdd;
impl Cmd for CommitCategoryAdd { impl Cmd for CommitCategoryAdd {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"commit-category-add" "commit-category-add"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1553,7 +1597,7 @@ impl Cmd for CommitCategoryAdd {
#[derive(Debug)] #[derive(Debug)]
pub struct CommitItemAdd; pub struct CommitItemAdd;
impl Cmd for CommitItemAdd { impl Cmd for CommitItemAdd {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"commit-item-add" "commit-item-add"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1587,7 +1631,7 @@ impl Cmd for CommitItemAdd {
#[derive(Debug)] #[derive(Debug)]
pub struct CommitExport; pub struct CommitExport;
impl Cmd for CommitExport { impl Cmd for CommitExport {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"commit-export" "commit-export"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1603,7 +1647,7 @@ impl Cmd for CommitExport {
#[derive(Debug)] #[derive(Debug)]
pub struct ExitSearchMode; pub struct ExitSearchMode;
impl Cmd for ExitSearchMode { impl Cmd for ExitSearchMode {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"exit-search-mode" "exit-search-mode"
} }
fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1615,7 +1659,7 @@ impl Cmd for ExitSearchMode {
#[derive(Debug)] #[derive(Debug)]
pub struct SearchAppendChar; pub struct SearchAppendChar;
impl Cmd for SearchAppendChar { impl Cmd for SearchAppendChar {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"search-append-char" "search-append-char"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1633,7 +1677,7 @@ impl Cmd for SearchAppendChar {
#[derive(Debug)] #[derive(Debug)]
pub struct SearchPopChar; pub struct SearchPopChar;
impl Cmd for SearchPopChar { impl Cmd for SearchPopChar {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"search-pop-char" "search-pop-char"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1647,7 +1691,7 @@ impl Cmd for SearchPopChar {
#[derive(Debug)] #[derive(Debug)]
pub struct CommandModeBackspace; pub struct CommandModeBackspace;
impl Cmd for CommandModeBackspace { impl Cmd for CommandModeBackspace {
fn name(&self) -> &str { fn name(&self) -> &'static str {
"command-mode-backspace" "command-mode-backspace"
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1674,7 +1718,7 @@ macro_rules! effect_cmd {
#[derive(Debug)] #[derive(Debug)]
pub struct $name(pub Vec<String>); pub struct $name(pub Vec<String>);
impl Cmd for $name { impl Cmd for $name {
fn name(&self) -> &str { fn name(&self) -> &'static str {
$cmd_name $cmd_name
} }
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> { fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {
@ -1929,12 +1973,12 @@ pub fn default_registry() -> CmdRegistry {
let mut r = CmdRegistry::new(); let mut r = CmdRegistry::new();
// ── Model mutations (effect_cmd! wrappers) ─────────────────────────── // ── Model mutations (effect_cmd! wrappers) ───────────────────────────
r.register_pure("add-category", AddCategoryCmd::parse); r.register_pure(&AddCategoryCmd(vec![]), AddCategoryCmd::parse);
r.register_pure("add-item", AddItemCmd::parse); r.register_pure(&AddItemCmd(vec![]), AddItemCmd::parse);
r.register_pure("add-item-in-group", AddItemInGroupCmd::parse); r.register_pure(&AddItemInGroupCmd(vec![]), AddItemInGroupCmd::parse);
r.register_pure("set-cell", SetCellCmd::parse); r.register_pure(&SetCellCmd(vec![]), SetCellCmd::parse);
r.register( r.register(
"clear-cell", &ClearCellCommand { key: CellKey::new(vec![]) },
|args| { |args| {
if args.is_empty() { if args.is_empty() {
return Err("clear-cell requires at least one Cat/Item coordinate".into()); return Err("clear-cell requires at least one Cat/Item coordinate".into());
@ -1948,24 +1992,24 @@ pub fn default_registry() -> CmdRegistry {
Ok(Box::new(ClearCellCommand { key })) Ok(Box::new(ClearCellCommand { key }))
}, },
); );
r.register_pure("add-formula", AddFormulaCmd::parse); r.register_pure(&AddFormulaCmd(vec![]), AddFormulaCmd::parse);
r.register_pure("remove-formula", RemoveFormulaCmd::parse); r.register_pure(&RemoveFormulaCmd(vec![]), RemoveFormulaCmd::parse);
r.register_pure("create-view", CreateViewCmd::parse); r.register_pure(&CreateViewCmd(vec![]), CreateViewCmd::parse);
r.register_pure("delete-view", DeleteViewCmd::parse); r.register_pure(&DeleteViewCmd(vec![]), DeleteViewCmd::parse);
r.register_pure("switch-view", SwitchViewCmd::parse); r.register_pure(&SwitchViewCmd(vec![]), SwitchViewCmd::parse);
r.register_pure("set-axis", SetAxisCmd::parse); r.register_pure(&SetAxisCmd(vec![]), SetAxisCmd::parse);
r.register_pure("set-page", SetPageCmd::parse); r.register_pure(&SetPageCmd(vec![]), SetPageCmd::parse);
r.register_pure("toggle-group", ToggleGroupCmd::parse); r.register_pure(&ToggleGroupCmd(vec![]), ToggleGroupCmd::parse);
r.register_pure("hide-item", HideItemCmd::parse); r.register_pure(&HideItemCmd(vec![]), HideItemCmd::parse);
r.register_pure("show-item", ShowItemCmd::parse); r.register_pure(&ShowItemCmd(vec![]), ShowItemCmd::parse);
r.register_pure("save-as", SaveAsCmd::parse); r.register_pure(&SaveAsCmd(vec![]), SaveAsCmd::parse);
r.register_pure("load", LoadModelCmd::parse); r.register_pure(&LoadModelCmd(vec![]), LoadModelCmd::parse);
r.register_pure("export-csv", ExportCsvCmd::parse); r.register_pure(&ExportCsvCmd(vec![]), ExportCsvCmd::parse);
r.register_pure("import-json", ImportJsonCmd::parse); r.register_pure(&ImportJsonCmd(vec![]), ImportJsonCmd::parse);
// ── Navigation ─────────────────────────────────────────────────────── // ── Navigation ───────────────────────────────────────────────────────
r.register( r.register(
"move-selection", &MoveSelection { dr: 0, dc: 0, cursor: CursorState::default() },
|args| { |args| {
require_args("move-selection", args, 2)?; require_args("move-selection", args, 2)?;
let dr = args[0].parse::<i32>().map_err(|e| e.to_string())?; let dr = args[0].parse::<i32>().map_err(|e| e.to_string())?;
@ -1995,7 +2039,7 @@ pub fn default_registry() -> CmdRegistry {
}, },
); );
r.register( r.register(
"jump-first-row", &JumpToFirstRow { col: 0 },
|_| Ok(Box::new(JumpToFirstRow { col: 0 })), |_| Ok(Box::new(JumpToFirstRow { col: 0 })),
|_, ctx| { |_, ctx| {
Ok(Box::new(JumpToFirstRow { Ok(Box::new(JumpToFirstRow {
@ -2004,7 +2048,7 @@ pub fn default_registry() -> CmdRegistry {
}, },
); );
r.register( r.register(
"jump-last-row", &JumpToLastRow { col: 0, row_count: 0, row_offset: 0 },
|_| { |_| {
Ok(Box::new(JumpToLastRow { Ok(Box::new(JumpToLastRow {
col: 0, col: 0,
@ -2021,7 +2065,7 @@ pub fn default_registry() -> CmdRegistry {
}, },
); );
r.register( r.register(
"jump-first-col", &JumpToFirstCol { row: 0 },
|_| Ok(Box::new(JumpToFirstCol { row: 0 })), |_| Ok(Box::new(JumpToFirstCol { row: 0 })),
|_, ctx| { |_, ctx| {
Ok(Box::new(JumpToFirstCol { Ok(Box::new(JumpToFirstCol {
@ -2030,7 +2074,7 @@ pub fn default_registry() -> CmdRegistry {
}, },
); );
r.register( r.register(
"jump-last-col", &JumpToLastCol { row: 0, col_count: 0, col_offset: 0 },
|_| { |_| {
Ok(Box::new(JumpToLastCol { Ok(Box::new(JumpToLastCol {
row: 0, row: 0,
@ -2047,7 +2091,7 @@ pub fn default_registry() -> CmdRegistry {
}, },
); );
r.register( r.register(
"scroll-rows", &ScrollRows { delta: 0, cursor: CursorState::default() },
|args| { |args| {
require_args("scroll-rows", args, 1)?; require_args("scroll-rows", args, 1)?;
let n = args[0].parse::<i32>().map_err(|e| e.to_string())?; let n = args[0].parse::<i32>().map_err(|e| e.to_string())?;
@ -2073,7 +2117,7 @@ pub fn default_registry() -> CmdRegistry {
}, },
); );
r.register( r.register(
"enter-advance", &EnterAdvance { cursor: CursorState::default() },
|_| { |_| {
Ok(Box::new(EnterAdvance { Ok(Box::new(EnterAdvance {
cursor: CursorState { cursor: CursorState {
@ -2095,7 +2139,7 @@ pub fn default_registry() -> CmdRegistry {
// ── Cell operations ────────────────────────────────────────────────── // ── Cell operations ──────────────────────────────────────────────────
r.register( r.register(
"yank", &YankCell { key: CellKey::new(vec![]) },
|args| { |args| {
if args.is_empty() { if args.is_empty() {
return Err("yank requires at least one Cat/Item coordinate".into()); return Err("yank requires at least one Cat/Item coordinate".into());
@ -2110,7 +2154,7 @@ pub fn default_registry() -> CmdRegistry {
}, },
); );
r.register( r.register(
"paste", &PasteCell { key: CellKey::new(vec![]) },
|args| { |args| {
if args.is_empty() { if args.is_empty() {
return Err("paste requires at least one Cat/Item coordinate".into()); return Err("paste requires at least one Cat/Item coordinate".into());
@ -2127,17 +2171,17 @@ pub fn default_registry() -> CmdRegistry {
// clear-cell is registered above (unified: ctx.cell_key or explicit coords) // clear-cell is registered above (unified: ctx.cell_key or explicit coords)
// ── View / page ────────────────────────────────────────────────────── // ── View / page ──────────────────────────────────────────────────────
r.register_nullary("transpose", || Box::new(TransposeAxes)); r.register_nullary(|| Box::new(TransposeAxes));
r.register_nullary("page-next", || Box::new(PageNext)); r.register_nullary(|| Box::new(PageNext));
r.register_nullary("page-prev", || Box::new(PagePrev)); r.register_nullary(|| Box::new(PagePrev));
// ── Mode changes ───────────────────────────────────────────────────── // ── Mode changes ─────────────────────────────────────────────────────
r.register_nullary("force-quit", || Box::new(ForceQuit)); r.register_nullary(|| Box::new(ForceQuit));
r.register_nullary("save-and-quit", || Box::new(SaveAndQuit)); r.register_nullary(|| Box::new(SaveAndQuit));
r.register_nullary("save", || Box::new(SaveCmd)); r.register_nullary(|| Box::new(SaveCmd));
r.register_nullary("search", || Box::new(EnterSearchMode)); r.register_nullary(|| Box::new(EnterSearchMode));
r.register( r.register(
"enter-edit-mode", &EnterEditMode { initial_value: String::new() },
|args| { |args| {
let val = args.first().cloned().unwrap_or_default(); let val = args.first().cloned().unwrap_or_default();
Ok(Box::new(EnterEditMode { initial_value: val })) Ok(Box::new(EnterEditMode { initial_value: val }))
@ -2154,10 +2198,10 @@ pub fn default_registry() -> CmdRegistry {
})) }))
}, },
); );
r.register_nullary("enter-export-prompt", || Box::new(EnterExportPrompt)); r.register_nullary(|| Box::new(EnterExportPrompt));
r.register_nullary("enter-formula-edit", || Box::new(EnterFormulaEdit)); r.register_nullary(|| Box::new(EnterFormulaEdit));
r.register_nullary("enter-tile-select", || Box::new(EnterTileSelect)); r.register_nullary(|| Box::new(EnterTileSelect));
r.register_pure("enter-mode", |args| { r.register_pure(&NamedCmd("enter-mode"), |args| {
require_args("enter-mode", args, 1)?; require_args("enter-mode", args, 1)?;
let mode = match args[0].as_str() { let mode = match args[0].as_str() {
"normal" => AppMode::Normal, "normal" => AppMode::Normal,
@ -2187,18 +2231,18 @@ pub fn default_registry() -> CmdRegistry {
}); });
// ── Search ─────────────────────────────────────────────────────────── // ── Search ───────────────────────────────────────────────────────────
r.register_pure("search-navigate", |args| { r.register_pure(&NamedCmd("search-navigate"), |args| {
let forward = args.first().map(|s| s != "backward").unwrap_or(true); let forward = args.first().map(|s| s != "backward").unwrap_or(true);
Ok(Box::new(SearchNavigate(forward))) Ok(Box::new(SearchNavigate(forward)))
}); });
r.register_nullary("search-or-category-add", || Box::new(SearchOrCategoryAdd)); r.register_nullary(|| Box::new(SearchOrCategoryAdd));
r.register_nullary("exit-search-mode", || Box::new(ExitSearchMode)); r.register_nullary(|| Box::new(ExitSearchMode));
r.register_nullary("search-append-char", || Box::new(SearchAppendChar)); r.register_nullary(|| Box::new(SearchAppendChar));
r.register_nullary("search-pop-char", || Box::new(SearchPopChar)); r.register_nullary(|| Box::new(SearchPopChar));
// ── Panel operations ───────────────────────────────────────────────── // ── Panel operations ─────────────────────────────────────────────────
r.register( r.register(
"toggle-panel-and-focus", &TogglePanelAndFocus { panel: Panel::Formula, currently_open: false },
|args| { |args| {
require_args("toggle-panel-and-focus", args, 1)?; require_args("toggle-panel-and-focus", args, 1)?;
let panel = parse_panel(&args[0])?; let panel = parse_panel(&args[0])?;
@ -2222,7 +2266,7 @@ pub fn default_registry() -> CmdRegistry {
}, },
); );
r.register( r.register(
"toggle-panel-visibility", &TogglePanelVisibility { panel: Panel::Formula, currently_open: false },
|args| { |args| {
require_args("toggle-panel-visibility", args, 1)?; require_args("toggle-panel-visibility", args, 1)?;
let panel = parse_panel(&args[0])?; let panel = parse_panel(&args[0])?;
@ -2246,7 +2290,7 @@ pub fn default_registry() -> CmdRegistry {
}, },
); );
r.register( r.register(
"cycle-panel-focus", &CyclePanelFocus { formula_open: false, category_open: false, view_open: false },
|_| { |_| {
Ok(Box::new(CyclePanelFocus { Ok(Box::new(CyclePanelFocus {
formula_open: false, formula_open: false,
@ -2263,7 +2307,7 @@ pub fn default_registry() -> CmdRegistry {
}, },
); );
r.register( r.register(
"move-panel-cursor", &MovePanelCursor { panel: Panel::Formula, delta: 0, current: 0, max: 0 },
|args| { |args| {
require_args("move-panel-cursor", args, 2)?; require_args("move-panel-cursor", args, 2)?;
let panel = parse_panel(&args[0])?; let panel = parse_panel(&args[0])?;
@ -2292,55 +2336,55 @@ pub fn default_registry() -> CmdRegistry {
})) }))
}, },
); );
r.register_nullary("delete-formula-at-cursor", || { r.register_nullary(|| {
Box::new(DeleteFormulaAtCursor) Box::new(DeleteFormulaAtCursor)
}); });
r.register_nullary("cycle-axis-at-cursor", || Box::new(CycleAxisAtCursor)); r.register_nullary(|| Box::new(CycleAxisAtCursor));
r.register_nullary("open-item-add-at-cursor", || Box::new(OpenItemAddAtCursor)); r.register_nullary(|| Box::new(OpenItemAddAtCursor));
r.register_nullary("switch-view-at-cursor", || Box::new(SwitchViewAtCursor)); r.register_nullary(|| Box::new(SwitchViewAtCursor));
r.register_nullary("create-and-switch-view", || Box::new(CreateAndSwitchView)); r.register_nullary(|| Box::new(CreateAndSwitchView));
r.register_nullary("delete-view-at-cursor", || Box::new(DeleteViewAtCursor)); r.register_nullary(|| Box::new(DeleteViewAtCursor));
// ── Tile select ────────────────────────────────────────────────────── // ── Tile select ──────────────────────────────────────────────────────
r.register_pure("move-tile-cursor", |args| { r.register_pure(&NamedCmd("move-tile-cursor"), |args| {
require_args("move-tile-cursor", args, 1)?; require_args("move-tile-cursor", args, 1)?;
let delta = args[0].parse::<i32>().map_err(|e| e.to_string())?; let delta = args[0].parse::<i32>().map_err(|e| e.to_string())?;
Ok(Box::new(MoveTileCursor(delta))) Ok(Box::new(MoveTileCursor(delta)))
}); });
r.register_nullary("cycle-axis-for-tile", || Box::new(CycleAxisForTile)); r.register_nullary(|| Box::new(CycleAxisForTile));
r.register_pure("set-axis-for-tile", |args| { r.register_pure(&NamedCmd("set-axis-for-tile"), |args| {
require_args("set-axis-for-tile", args, 1)?; require_args("set-axis-for-tile", args, 1)?;
let axis = parse_axis(&args[0])?; let axis = parse_axis(&args[0])?;
Ok(Box::new(SetAxisForTile(axis))) Ok(Box::new(SetAxisForTile(axis)))
}); });
// ── Grid operations ────────────────────────────────────────────────── // ── Grid operations ──────────────────────────────────────────────────
r.register_nullary("toggle-group-under-cursor", || { r.register_nullary(|| {
Box::new(ToggleGroupUnderCursor) Box::new(ToggleGroupUnderCursor)
}); });
r.register_nullary("toggle-col-group-under-cursor", || { r.register_nullary(|| {
Box::new(ToggleColGroupUnderCursor) Box::new(ToggleColGroupUnderCursor)
}); });
r.register_nullary("hide-selected-row-item", || Box::new(HideSelectedRowItem)); r.register_nullary(|| Box::new(HideSelectedRowItem));
// ── Text buffer ────────────────────────────────────────────────────── // ── Text buffer ──────────────────────────────────────────────────────
r.register_pure("append-char", |args| { r.register_pure(&NamedCmd("append-char"), |args| {
require_args("append-char", args, 1)?; require_args("append-char", args, 1)?;
Ok(Box::new(AppendChar { Ok(Box::new(AppendChar {
buffer: args[0].clone(), buffer: args[0].clone(),
})) }))
}); });
r.register_pure("pop-char", |args| { r.register_pure(&NamedCmd("pop-char"), |args| {
require_args("pop-char", args, 1)?; require_args("pop-char", args, 1)?;
Ok(Box::new(PopChar { Ok(Box::new(PopChar {
buffer: args[0].clone(), buffer: args[0].clone(),
})) }))
}); });
r.register_nullary("command-mode-backspace", || Box::new(CommandModeBackspace)); r.register_nullary(|| Box::new(CommandModeBackspace));
// ── Commit ─────────────────────────────────────────────────────────── // ── Commit ───────────────────────────────────────────────────────────
r.register( r.register(
"commit-cell-edit", &CommitCellEdit { key: CellKey::new(vec![]), value: String::new() },
|args| { |args| {
// parse: commit-cell-edit <value> <Cat/Item>... // parse: commit-cell-edit <value> <Cat/Item>...
if args.len() < 2 { if args.len() < 2 {
@ -2357,14 +2401,14 @@ pub fn default_registry() -> CmdRegistry {
Ok(Box::new(CommitCellEdit { key, value })) Ok(Box::new(CommitCellEdit { key, value }))
}, },
); );
r.register_nullary("commit-formula", || Box::new(CommitFormula)); r.register_nullary(|| Box::new(CommitFormula));
r.register_nullary("commit-category-add", || Box::new(CommitCategoryAdd)); r.register_nullary(|| Box::new(CommitCategoryAdd));
r.register_nullary("commit-item-add", || Box::new(CommitItemAdd)); r.register_nullary(|| Box::new(CommitItemAdd));
r.register_nullary("commit-export", || Box::new(CommitExport)); r.register_nullary(|| Box::new(CommitExport));
r.register_nullary("execute-command", || Box::new(ExecuteCommand)); r.register_nullary(|| Box::new(ExecuteCommand));
// ── Wizard ─────────────────────────────────────────────────────────── // ── Wizard ───────────────────────────────────────────────────────────
r.register_nullary("handle-wizard-key", || Box::new(HandleWizardKey)); r.register_nullary(|| Box::new(HandleWizardKey));
r r
} }