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