From 630367a9b0aace1e84c77af10291dd2fea53314c Mon Sep 17 00:00:00 2001 From: Edward Langley Date: Sat, 4 Apr 2026 10:42:25 -0700 Subject: [PATCH] feat(command): add HandleWizardKey and ExecuteCommand handlers Introduce HandleWizardKey command to dispatch keys to the import wizard. Add ExecuteCommand implementation that parses and executes various commands like :quit, :write, :import, :add-item, and :formula. Handles argument parsing, validation, and mode transitions. Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/Qwen3.5-35B-A3B-GGUF:Q5_K_M) --- src/command/cmd.rs | 205 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) diff --git a/src/command/cmd.rs b/src/command/cmd.rs index dc07d19..212f9f2 100644 --- a/src/command/cmd.rs +++ b/src/command/cmd.rs @@ -1071,6 +1071,211 @@ impl Cmd for SetAxisForTile { } } +// ── Wizard command ────────────────────────────────────────────────────────── + +/// Dispatch the current key to the import wizard effect. +#[derive(Debug)] +pub struct HandleWizardKey; +impl Cmd for HandleWizardKey { + fn name(&self) -> &str { + "handle-wizard-key" + } + fn execute(&self, ctx: &CmdContext) -> Vec> { + vec![Box::new(effect::WizardKey { + key_code: ctx.key_code, + })] + } +} + +// ── Command mode execution ────────────────────────────────────────────────── + +/// Execute the command in the "command" buffer (the `:` command line). +#[derive(Debug)] +pub struct ExecuteCommand; +impl Cmd for ExecuteCommand { + fn name(&self) -> &str { + "execute-command" + } + fn execute(&self, ctx: &CmdContext) -> Vec> { + let raw = ctx.buffers.get("command").cloned().unwrap_or_default(); + let raw = raw.trim(); + let (cmd_name, rest) = raw + .split_once(char::is_whitespace) + .map(|(c, r)| (c, r.trim())) + .unwrap_or((raw, "")); + + // Default: return to Normal + let mut effects: Vec> = Vec::new(); + + match cmd_name { + "q" | "quit" => { + if ctx.dirty { + effects.push(effect::set_status( + "Unsaved changes. Use :q! to force quit or :wq to save+quit.", + )); + } else { + effects.push(effect::change_mode(AppMode::Quit)); + } + } + "q!" => { + effects.push(effect::change_mode(AppMode::Quit)); + } + "w" | "write" => { + if rest.is_empty() { + effects.push(Box::new(effect::Save)); + } else { + effects.push(Box::new(effect::SaveAs(std::path::PathBuf::from(rest)))); + } + } + "wq" | "x" => { + effects.push(Box::new(effect::Save)); + effects.push(effect::change_mode(AppMode::Quit)); + } + "import" => { + if rest.is_empty() { + effects.push(effect::set_status("Usage: :import ")); + } else { + effects.push(Box::new(effect::StartImportWizard(rest.to_string()))); + } + } + "export" => { + let path = if rest.is_empty() { "export.csv" } else { rest }; + effects.push(Box::new(effect::ExportCsv(std::path::PathBuf::from(path)))); + } + "add-cat" | "add-category" | "cat" => { + if rest.is_empty() { + effects.push(effect::set_status("Usage: :add-cat ")); + } else { + effects.push(Box::new(effect::AddCategory(rest.to_string()))); + effects.push(effect::mark_dirty()); + } + } + "add-item" | "item" => { + let mut parts = rest.splitn(2, char::is_whitespace); + let cat = parts.next().unwrap_or("").trim(); + let item = parts.next().unwrap_or("").trim(); + if cat.is_empty() || item.is_empty() { + effects.push(effect::set_status("Usage: :add-item ")); + } else { + effects.push(Box::new(effect::AddItem { + category: cat.to_string(), + item: item.to_string(), + })); + effects.push(effect::mark_dirty()); + effects.push(effect::set_status("Item added")); + } + } + "add-items" | "items" => { + let mut parts = rest.splitn(2, char::is_whitespace); + let cat = parts.next().unwrap_or("").trim().to_string(); + let items_str = parts.next().unwrap_or("").trim().to_string(); + if cat.is_empty() || items_str.is_empty() { + effects.push(effect::set_status( + "Usage: :add-items item1 item2 ...", + )); + } else { + let items: Vec<&str> = items_str.split_whitespace().collect(); + let count = items.len(); + for item in &items { + effects.push(Box::new(effect::AddItem { + category: cat.clone(), + item: item.to_string(), + })); + } + effects.push(effect::mark_dirty()); + effects.push(effect::set_status(format!( + "Added {count} items to \"{cat}\".", + ))); + } + } + "formula" | "add-formula" => { + if rest.is_empty() { + effects.push(Box::new(effect::SetPanelOpen { + panel: Panel::Formula, + open: true, + })); + effects.push(effect::change_mode(AppMode::FormulaPanel)); + return effects; // Don't set mode to Normal + } else { + let mut parts = rest.splitn(2, char::is_whitespace); + let cat = parts.next().unwrap_or("").trim(); + let formula = parts.next().unwrap_or("").trim(); + if cat.is_empty() || formula.is_empty() { + effects.push(effect::set_status( + "Usage: :formula ", + )); + } else { + effects.push(Box::new(effect::AddFormula { + raw: formula.to_string(), + target_category: cat.to_string(), + })); + effects.push(effect::mark_dirty()); + effects.push(effect::set_status("Formula added")); + } + } + } + "add-view" | "view" => { + let name = if rest.is_empty() { + format!("View {}", ctx.model.views.len() + 1) + } else { + rest.to_string() + }; + effects.push(Box::new(effect::CreateView(name.clone()))); + effects.push(Box::new(effect::SwitchView(name))); + effects.push(effect::mark_dirty()); + } + "set-format" | "fmt" => { + if rest.is_empty() { + effects.push(effect::set_status( + "Usage: :set-format e.g. ,.0 ,.2 .4", + )); + } else { + effects.push(Box::new(effect::SetNumberFormat(rest.to_string()))); + effects.push(effect::mark_dirty()); + effects.push(effect::set_status(format!("Number format set to '{rest}'"))); + } + } + "show-item" | "show" => { + let mut parts = rest.splitn(2, char::is_whitespace); + let cat = parts.next().unwrap_or("").trim(); + let item = parts.next().unwrap_or("").trim(); + if cat.is_empty() || item.is_empty() { + effects.push(effect::set_status("Usage: :show-item ")); + } else { + effects.push(Box::new(effect::ShowItem { + category: cat.to_string(), + item: item.to_string(), + })); + effects.push(effect::mark_dirty()); + effects.push(effect::set_status(format!( + "Showed \"{item}\" in \"{cat}\"" + ))); + } + } + "help" | "h" => { + effects.push(effect::change_mode(AppMode::Help)); + return effects; // Don't also set Normal + } + "" => {} // just pressed Enter with empty buffer + other => { + effects.push(effect::set_status(format!( + "Unknown command: :{other} (try :help)" + ))); + } + } + + // Default: return to Normal (unless a command already set a different mode) + if !effects + .iter() + .any(|e| format!("{e:?}").contains("ChangeMode")) + { + effects.push(effect::change_mode(AppMode::Normal)); + } + + effects + } +} + // ── Text buffer commands ───────────────────────────────────────────────────── /// Append the pressed character to a named buffer.