refactor(app): remove legacy command execution code
Remove unused imports for legacy code that is no longer needed. Delete execute_command function that handled :q, :w, :import commands via direct AppMode matching. Delete handle_wizard_key function and associated fallback logic for modes not yet migrated to keymaps. These are now handled by the new keymap-based approach. Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/Qwen3.5-35B-A3B-GGUF:Q5_K_M)
This commit is contained in:
357
src/ui/app.rs
357
src/ui/app.rs
@ -1,18 +1,16 @@
|
||||
use anyhow::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::command::cmd::CmdContext;
|
||||
use crate::command::keymap::{Keymap, KeymapSet};
|
||||
use crate::command::{self, Command};
|
||||
use crate::import::wizard::{ImportWizard, WizardStep};
|
||||
use crate::model::cell::{CellKey, CellValue};
|
||||
use crate::import::wizard::ImportWizard;
|
||||
use crate::model::cell::CellValue;
|
||||
use crate::model::Model;
|
||||
use crate::persistence;
|
||||
use crate::view::GridLayout;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AppMode {
|
||||
@ -159,355 +157,6 @@ impl App {
|
||||
};
|
||||
if let Some(effects) = effects {
|
||||
self.apply_effects(effects);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Fallback: old-style handlers for modes not yet migrated to keymaps
|
||||
match &self.mode.clone() {
|
||||
AppMode::CommandMode { .. } => {
|
||||
// Enter key (execute_command) is still handled here.
|
||||
// Esc, Backspace, Char are handled by the keymap above.
|
||||
if key.code == KeyCode::Enter {
|
||||
let buf = if let AppMode::CommandMode { buffer } = &self.mode {
|
||||
buffer.clone()
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
// Also read from buffers map if the keymap was appending there
|
||||
let cmd_buf = self.buffers.get("command").cloned().unwrap_or(buf);
|
||||
self.execute_command(&cmd_buf)?;
|
||||
}
|
||||
}
|
||||
AppMode::ImportWizard => self.handle_wizard_key(key)?,
|
||||
_ => {} // All other modes handled by keymap
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_command(&mut self, raw: &str) -> Result<()> {
|
||||
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. Commands that open a new mode override this.
|
||||
self.mode = AppMode::Normal;
|
||||
|
||||
match cmd_name {
|
||||
"q" | "quit" => {
|
||||
if self.dirty {
|
||||
self.status_msg =
|
||||
"Unsaved changes. Use :q! to force quit or :wq to save+quit.".to_string();
|
||||
} else {
|
||||
self.mode = AppMode::Quit;
|
||||
}
|
||||
}
|
||||
"q!" => {
|
||||
self.mode = AppMode::Quit;
|
||||
}
|
||||
"w" | "write" => {
|
||||
if rest.is_empty() {
|
||||
self.save()?;
|
||||
} else {
|
||||
let path = PathBuf::from(rest);
|
||||
persistence::save(&self.model, &path)?;
|
||||
self.file_path = Some(path.clone());
|
||||
self.dirty = false;
|
||||
self.status_msg = format!("Saved to {}", path.display());
|
||||
}
|
||||
}
|
||||
"wq" | "x" => {
|
||||
self.save()?;
|
||||
self.mode = AppMode::Quit;
|
||||
}
|
||||
"import" => {
|
||||
if rest.is_empty() {
|
||||
self.status_msg = "Usage: :import <path.json>".to_string();
|
||||
} else {
|
||||
match std::fs::read_to_string(rest) {
|
||||
Ok(content) => match serde_json::from_str::<serde_json::Value>(&content) {
|
||||
Ok(json) => {
|
||||
self.wizard = Some(ImportWizard::new(json));
|
||||
self.mode = AppMode::ImportWizard;
|
||||
}
|
||||
Err(e) => {
|
||||
self.status_msg = format!("JSON parse error: {e}");
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
self.status_msg = format!("Cannot read file: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"export" => {
|
||||
let path = if rest.is_empty() { "export.csv" } else { rest };
|
||||
let view_name = self.model.active_view.clone();
|
||||
match persistence::export_csv(&self.model, &view_name, Path::new(path)) {
|
||||
Ok(_) => {
|
||||
self.status_msg = format!("Exported to {path}");
|
||||
}
|
||||
Err(e) => {
|
||||
self.status_msg = format!("Export error: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
"add-cat" | "add-category" | "cat" => {
|
||||
if rest.is_empty() {
|
||||
self.status_msg = "Usage: :add-cat <name>".to_string();
|
||||
} else {
|
||||
let result = command::dispatch(
|
||||
&mut self.model,
|
||||
&Command::AddCategory {
|
||||
name: rest.to_string(),
|
||||
},
|
||||
);
|
||||
self.status_msg = result.message.unwrap_or_default();
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
"add-item" | "item" => {
|
||||
// :add-item <category> <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() {
|
||||
self.status_msg = "Usage: :add-item <category> <item>".to_string();
|
||||
} else {
|
||||
let result = command::dispatch(
|
||||
&mut self.model,
|
||||
&Command::AddItem {
|
||||
category: cat.to_string(),
|
||||
item: item.to_string(),
|
||||
},
|
||||
);
|
||||
self.status_msg = result.message.unwrap_or_else(|| "Item added".to_string());
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
"add-items" | "items" => {
|
||||
// :add-items <category> item1 item2 item3 ...
|
||||
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() {
|
||||
self.status_msg = "Usage: :add-items <category> item1 item2 ...".to_string();
|
||||
} else {
|
||||
let items: Vec<&str> = items_str.split_whitespace().collect();
|
||||
let count = items.len();
|
||||
for item in &items {
|
||||
command::dispatch(
|
||||
&mut self.model,
|
||||
&Command::AddItem {
|
||||
category: cat.clone(),
|
||||
item: item.to_string(),
|
||||
},
|
||||
);
|
||||
}
|
||||
self.status_msg = format!("Added {count} items to \"{cat}\".");
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
"formula" | "add-formula" => {
|
||||
if rest.is_empty() {
|
||||
self.formula_panel_open = true;
|
||||
self.mode = AppMode::FormulaPanel;
|
||||
} else {
|
||||
// :formula <target_cat> <formula>
|
||||
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() {
|
||||
self.status_msg = "Usage: :formula <category> <Name = expr>".to_string();
|
||||
} else {
|
||||
let result = command::dispatch(
|
||||
&mut self.model,
|
||||
&Command::AddFormula {
|
||||
raw: formula.to_string(),
|
||||
target_category: cat.to_string(),
|
||||
},
|
||||
);
|
||||
self.status_msg = result
|
||||
.message
|
||||
.unwrap_or_else(|| "Formula added".to_string());
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
"add-view" | "view" => {
|
||||
let name = if rest.is_empty() {
|
||||
format!("View {}", self.model.views.len() + 1)
|
||||
} else {
|
||||
rest.to_string()
|
||||
};
|
||||
command::dispatch(&mut self.model, &Command::CreateView { name: name.clone() });
|
||||
let _ = command::dispatch(&mut self.model, &Command::SwitchView { name });
|
||||
self.dirty = true;
|
||||
}
|
||||
"set-format" | "fmt" => {
|
||||
// :set-format <format> e.g. ",.2" ",.0" ".2"
|
||||
// "," = comma separators; ".N" = N decimal places
|
||||
if rest.is_empty() {
|
||||
self.status_msg = "Usage: :set-format <fmt> e.g. ,.0 ,.2 .4".to_string();
|
||||
} else {
|
||||
self.model.active_view_mut().number_format = rest.to_string();
|
||||
self.status_msg = format!("Number format set to '{rest}'");
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
"show-item" | "show" => {
|
||||
// :show-item <category> <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() {
|
||||
self.status_msg = "Usage: :show-item <category> <item>".to_string();
|
||||
} else {
|
||||
let result = command::dispatch(
|
||||
&mut self.model,
|
||||
&Command::ShowItem {
|
||||
category: cat.to_string(),
|
||||
item: item.to_string(),
|
||||
},
|
||||
);
|
||||
self.status_msg = result
|
||||
.message
|
||||
.unwrap_or_else(|| format!("Showed \"{item}\" in \"{cat}\""));
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
"help" | "h" => {
|
||||
self.mode = AppMode::Help;
|
||||
}
|
||||
"" => {} // just pressed Enter with empty buffer
|
||||
other => {
|
||||
self.status_msg = format!("Unknown command: :{other} (try :help)");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_wizard_key(&mut self, key: KeyEvent) -> Result<()> {
|
||||
if let Some(wizard) = &mut self.wizard {
|
||||
match &wizard.step.clone() {
|
||||
WizardStep::Preview => match key.code {
|
||||
KeyCode::Enter | KeyCode::Char(' ') => wizard.advance(),
|
||||
KeyCode::Esc => {
|
||||
self.mode = AppMode::Normal;
|
||||
self.wizard = None;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
WizardStep::SelectArrayPath => match key.code {
|
||||
KeyCode::Up | KeyCode::Char('k') => wizard.move_cursor(-1),
|
||||
KeyCode::Down | KeyCode::Char('j') => wizard.move_cursor(1),
|
||||
KeyCode::Enter => wizard.confirm_path(),
|
||||
KeyCode::Esc => {
|
||||
self.mode = AppMode::Normal;
|
||||
self.wizard = None;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
WizardStep::ReviewProposals => match key.code {
|
||||
KeyCode::Up | KeyCode::Char('k') => wizard.move_cursor(-1),
|
||||
KeyCode::Down | KeyCode::Char('j') => wizard.move_cursor(1),
|
||||
KeyCode::Char(' ') => wizard.toggle_proposal(),
|
||||
KeyCode::Char('c') => wizard.cycle_proposal_kind(),
|
||||
KeyCode::Enter => wizard.advance(),
|
||||
KeyCode::Esc => {
|
||||
self.mode = AppMode::Normal;
|
||||
self.wizard = None;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
WizardStep::ConfigureDates => match key.code {
|
||||
KeyCode::Up | KeyCode::Char('k') => wizard.move_cursor(-1),
|
||||
KeyCode::Down | KeyCode::Char('j') => wizard.move_cursor(1),
|
||||
KeyCode::Char(' ') => wizard.toggle_date_component(),
|
||||
KeyCode::Enter => wizard.advance(),
|
||||
KeyCode::Esc => {
|
||||
self.mode = AppMode::Normal;
|
||||
self.wizard = None;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
WizardStep::DefineFormulas => {
|
||||
if wizard.formula_editing {
|
||||
match key.code {
|
||||
KeyCode::Enter => wizard.confirm_formula(),
|
||||
KeyCode::Esc => wizard.cancel_formula_edit(),
|
||||
KeyCode::Backspace => wizard.pop_formula_char(),
|
||||
KeyCode::Char(c) => wizard.push_formula_char(c),
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
match key.code {
|
||||
KeyCode::Char('n') => wizard.start_formula_edit(),
|
||||
KeyCode::Char('d') => wizard.delete_formula(),
|
||||
KeyCode::Up | KeyCode::Char('k') => wizard.move_cursor(-1),
|
||||
KeyCode::Down | KeyCode::Char('j') => wizard.move_cursor(1),
|
||||
KeyCode::Enter => wizard.advance(),
|
||||
KeyCode::Esc => {
|
||||
self.mode = AppMode::Normal;
|
||||
self.wizard = None;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
WizardStep::NameModel => match key.code {
|
||||
KeyCode::Char(c) => wizard.push_name_char(c),
|
||||
KeyCode::Backspace => wizard.pop_name_char(),
|
||||
KeyCode::Enter => match wizard.build_model() {
|
||||
Ok(mut model) => {
|
||||
model.normalize_view_state();
|
||||
self.model = model;
|
||||
self.formula_cursor = 0;
|
||||
self.dirty = true;
|
||||
self.status_msg =
|
||||
"Import successful! Press :w <path> to save.".to_string();
|
||||
self.mode = AppMode::Normal;
|
||||
self.wizard = None;
|
||||
}
|
||||
Err(e) => {
|
||||
if let Some(w) = &mut self.wizard {
|
||||
w.message = Some(format!("Error: {e}"));
|
||||
}
|
||||
}
|
||||
},
|
||||
KeyCode::Esc => {
|
||||
self.mode = AppMode::Normal;
|
||||
self.wizard = None;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
WizardStep::Done => {
|
||||
self.mode = AppMode::Normal;
|
||||
self.wizard = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ── Cell key resolution ──────────────────────────────────────────────────
|
||||
|
||||
pub fn selected_cell_key(&self) -> Option<CellKey> {
|
||||
let view = self.model.active_view();
|
||||
let (sel_row, sel_col) = view.selected;
|
||||
GridLayout::new(&self.model, view).cell_key(sel_row, sel_col)
|
||||
}
|
||||
|
||||
// ── Persistence ──────────────────────────────────────────────────────────
|
||||
|
||||
pub fn save(&mut self) -> Result<()> {
|
||||
if let Some(path) = &self.file_path.clone() {
|
||||
persistence::save(&self.model, path)?;
|
||||
self.dirty = false;
|
||||
self.status_msg = format!("Saved to {}", path.display());
|
||||
} else {
|
||||
self.status_msg = "No file path — use :w <path> to save.".to_string();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user