refactor(app): wire up keymap dispatch and remove old handlers
Update handle_key to pass key code and modifiers to cmd_context. Update keymap_set.dispatch to pass search_mode from App state. Remove old-style panel handlers (handle_edit_key, handle_formula_edit_key, handle_formula_panel_key, etc.) - approximately 500 lines. Update handle_command_mode_key to use buffers map for command execution. All other modes now handled via keymap dispatch. Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/Qwen3.5-35B-A3B-GGUF:Q5_K_M)
This commit is contained in:
579
src/ui/app.rs
579
src/ui/app.rs
@ -141,7 +141,7 @@ impl App {
|
|||||||
pub fn handle_key(&mut self, key: KeyEvent) -> Result<()> {
|
pub fn handle_key(&mut self, key: KeyEvent) -> Result<()> {
|
||||||
// Transient keymap (prefix key sequence) takes priority
|
// Transient keymap (prefix key sequence) takes priority
|
||||||
if let Some(transient) = self.transient_keymap.take() {
|
if let Some(transient) = self.transient_keymap.take() {
|
||||||
let ctx = self.cmd_context();
|
let ctx = self.cmd_context(key.code, key.modifiers);
|
||||||
if let Some(effects) = transient.dispatch(&ctx, key.code, key.modifiers) {
|
if let Some(effects) = transient.dispatch(&ctx, key.code, key.modifiers) {
|
||||||
drop(ctx);
|
drop(ctx);
|
||||||
self.apply_effects(effects);
|
self.apply_effects(effects);
|
||||||
@ -151,7 +151,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try mode keymap — if a binding matches, apply effects and return
|
// Try mode keymap — if a binding matches, apply effects and return
|
||||||
let ctx = self.cmd_context();
|
let ctx = self.cmd_context(key.code, key.modifiers);
|
||||||
if let Some(effects) = self.keymap_set.dispatch(&ctx, key.code, key.modifiers) {
|
if let Some(effects) = self.keymap_set.dispatch(&ctx, key.code, key.modifiers) {
|
||||||
drop(ctx);
|
drop(ctx);
|
||||||
self.apply_effects(effects);
|
self.apply_effects(effects);
|
||||||
@ -161,75 +161,22 @@ impl App {
|
|||||||
|
|
||||||
// Fallback: old-style handlers for modes not yet migrated to keymaps
|
// Fallback: old-style handlers for modes not yet migrated to keymaps
|
||||||
match &self.mode.clone() {
|
match &self.mode.clone() {
|
||||||
AppMode::Normal => {
|
AppMode::CommandMode { .. } => {
|
||||||
// Normal mode keys are handled by the keymap above.
|
// Enter key (execute_command) is still handled here.
|
||||||
// Only search sub-mode still uses the old pattern.
|
// Esc, Backspace, Char are handled by the keymap above.
|
||||||
if self.search_mode {
|
if key.code == KeyCode::Enter {
|
||||||
self.handle_search_key(key)?;
|
let buf = if let AppMode::CommandMode { buffer } = &self.mode {
|
||||||
}
|
buffer.clone()
|
||||||
}
|
|
||||||
AppMode::Editing { .. } => self.handle_edit_key(key)?,
|
|
||||||
AppMode::FormulaEdit { .. } => self.handle_formula_edit_key(key)?,
|
|
||||||
AppMode::FormulaPanel => self.handle_formula_panel_key(key)?,
|
|
||||||
AppMode::CategoryPanel => self.handle_category_panel_key(key)?,
|
|
||||||
AppMode::CategoryAdd { .. } => self.handle_category_add_key(key)?,
|
|
||||||
AppMode::ItemAdd { .. } => self.handle_item_add_key(key)?,
|
|
||||||
AppMode::ViewPanel => self.handle_view_panel_key(key)?,
|
|
||||||
AppMode::TileSelect { .. } => self.handle_tile_select_key(key)?,
|
|
||||||
AppMode::ExportPrompt { .. } => self.handle_export_key(key)?,
|
|
||||||
AppMode::CommandMode { .. } => self.handle_command_mode_key(key)?,
|
|
||||||
AppMode::ImportWizard => self.handle_wizard_key(key)?,
|
|
||||||
AppMode::Quit | AppMode::Help => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_search_key(&mut self, key: KeyEvent) -> Result<()> {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Esc | KeyCode::Enter => {
|
|
||||||
self.search_mode = false;
|
|
||||||
}
|
|
||||||
KeyCode::Char(c) => {
|
|
||||||
self.search_query.push(c);
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
self.search_query.pop();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Command mode ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
fn handle_command_mode_key(&mut self, key: KeyEvent) -> Result<()> {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Esc => {
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
let buf = if let AppMode::CommandMode { buffer } = &self.mode {
|
|
||||||
buffer.clone()
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
self.execute_command(&buf)?;
|
|
||||||
}
|
|
||||||
KeyCode::Char(c) => {
|
|
||||||
if let AppMode::CommandMode { buffer } = &mut self.mode {
|
|
||||||
buffer.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
if let AppMode::CommandMode { buffer } = &mut self.mode {
|
|
||||||
if buffer.is_empty() {
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
} else {
|
} else {
|
||||||
buffer.pop();
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -438,502 +385,6 @@ impl App {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Edit mode ────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
fn handle_edit_key(&mut self, key: KeyEvent) -> Result<()> {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Esc => {
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
let buf = if let AppMode::Editing { buffer } = &self.mode {
|
|
||||||
buffer.clone()
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
if let Some(key) = self.selected_cell_key() {
|
|
||||||
let coords = key.0.iter().map(|(c, v)| [c.clone(), v.clone()]).collect();
|
|
||||||
let cmd = if buf.is_empty() {
|
|
||||||
Command::ClearCell { coords }
|
|
||||||
} else if let Ok(n) = buf.parse::<f64>() {
|
|
||||||
Command::SetCell {
|
|
||||||
coords,
|
|
||||||
value: crate::command::types::CellValueArg::Number { number: n },
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Command::SetCell {
|
|
||||||
coords,
|
|
||||||
value: crate::command::types::CellValueArg::Text { text: buf.clone() },
|
|
||||||
}
|
|
||||||
};
|
|
||||||
command::dispatch(&mut self.model, &cmd);
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
// Advance cursor down after committing edit
|
|
||||||
let ctx = self.cmd_context();
|
|
||||||
let effects = crate::command::cmd::MoveSelection { dr: 1, dc: 0 }.execute(&ctx);
|
|
||||||
drop(ctx);
|
|
||||||
self.apply_effects(effects);
|
|
||||||
}
|
|
||||||
KeyCode::Char(c) => {
|
|
||||||
if let AppMode::Editing { buffer } = &mut self.mode {
|
|
||||||
buffer.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
if let AppMode::Editing { buffer } = &mut self.mode {
|
|
||||||
buffer.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Formula edit ─────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
fn handle_formula_edit_key(&mut self, key: KeyEvent) -> Result<()> {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Esc => {
|
|
||||||
self.mode = AppMode::FormulaPanel;
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
let buf = if let AppMode::FormulaEdit { buffer } = &self.mode {
|
|
||||||
buffer.clone()
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
let first_cat = self
|
|
||||||
.model
|
|
||||||
.category_names()
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.map(String::from);
|
|
||||||
if let Some(cat) = first_cat {
|
|
||||||
let result = command::dispatch(
|
|
||||||
&mut self.model,
|
|
||||||
&Command::AddFormula {
|
|
||||||
raw: buf,
|
|
||||||
target_category: cat,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.status_msg = result
|
|
||||||
.message
|
|
||||||
.unwrap_or_else(|| "Formula added".to_string());
|
|
||||||
self.dirty = true;
|
|
||||||
} else {
|
|
||||||
self.status_msg = "Add at least one category first.".to_string();
|
|
||||||
}
|
|
||||||
self.mode = AppMode::FormulaPanel;
|
|
||||||
}
|
|
||||||
KeyCode::Char(c) => {
|
|
||||||
if let AppMode::FormulaEdit { buffer } = &mut self.mode {
|
|
||||||
buffer.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
if let AppMode::FormulaEdit { buffer } = &mut self.mode {
|
|
||||||
buffer.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Panel key handlers ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
fn handle_formula_panel_key(&mut self, key: KeyEvent) -> Result<()> {
|
|
||||||
// Clamp cursor in case the formula list shrank since it was last set.
|
|
||||||
let flen = self.model.formulas().len();
|
|
||||||
if flen == 0 {
|
|
||||||
self.formula_cursor = 0;
|
|
||||||
} else {
|
|
||||||
self.formula_cursor = self.formula_cursor.min(flen - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Esc | KeyCode::Tab => {
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
KeyCode::Char('a') | KeyCode::Char('n') | KeyCode::Char('o') => {
|
|
||||||
self.mode = AppMode::FormulaEdit {
|
|
||||||
buffer: String::new(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
KeyCode::Char('d') | KeyCode::Delete => {
|
|
||||||
if self.formula_cursor < self.model.formulas().len() {
|
|
||||||
let f = &self.model.formulas()[self.formula_cursor];
|
|
||||||
let target = f.target.clone();
|
|
||||||
let target_category = f.target_category.clone();
|
|
||||||
command::dispatch(
|
|
||||||
&mut self.model,
|
|
||||||
&Command::RemoveFormula {
|
|
||||||
target,
|
|
||||||
target_category,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if self.formula_cursor > 0 {
|
|
||||||
self.formula_cursor -= 1;
|
|
||||||
}
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Up | KeyCode::Char('k') => {
|
|
||||||
if self.formula_cursor > 0 {
|
|
||||||
self.formula_cursor -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Down | KeyCode::Char('j') => {
|
|
||||||
if self.formula_cursor + 1 < self.model.formulas().len() {
|
|
||||||
self.formula_cursor += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_category_panel_key(&mut self, key: KeyEvent) -> Result<()> {
|
|
||||||
let cat_names: Vec<String> = self
|
|
||||||
.model
|
|
||||||
.category_names()
|
|
||||||
.into_iter()
|
|
||||||
.map(String::from)
|
|
||||||
.collect();
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Esc | KeyCode::Tab => {
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
KeyCode::Up | KeyCode::Char('k') => {
|
|
||||||
if self.cat_panel_cursor > 0 {
|
|
||||||
self.cat_panel_cursor -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Down | KeyCode::Char('j') => {
|
|
||||||
if self.cat_panel_cursor + 1 < cat_names.len() {
|
|
||||||
self.cat_panel_cursor += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Enter | KeyCode::Char(' ') => {
|
|
||||||
if let Some(cat_name) = cat_names.get(self.cat_panel_cursor) {
|
|
||||||
self.model.active_view_mut().cycle_axis(cat_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// n — add a new category
|
|
||||||
KeyCode::Char('n') => {
|
|
||||||
self.mode = AppMode::CategoryAdd {
|
|
||||||
buffer: String::new(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// a / o — open quick-add items mode for the selected category
|
|
||||||
KeyCode::Char('a') | KeyCode::Char('o') => {
|
|
||||||
if let Some(cat_name) = cat_names.get(self.cat_panel_cursor) {
|
|
||||||
self.mode = AppMode::ItemAdd {
|
|
||||||
category: cat_name.clone(),
|
|
||||||
buffer: String::new(),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
self.status_msg =
|
|
||||||
"No category selected. Press n to add a category first.".to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_category_add_key(&mut self, key: KeyEvent) -> Result<()> {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Esc => {
|
|
||||||
self.mode = AppMode::CategoryPanel;
|
|
||||||
self.status_msg = String::new();
|
|
||||||
}
|
|
||||||
KeyCode::Enter | KeyCode::Tab => {
|
|
||||||
let buf = if let AppMode::CategoryAdd { buffer } = &self.mode {
|
|
||||||
buffer.trim().to_string()
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
if !buf.is_empty() {
|
|
||||||
let result = command::dispatch(
|
|
||||||
&mut self.model,
|
|
||||||
&Command::AddCategory { name: buf.clone() },
|
|
||||||
);
|
|
||||||
if result.ok {
|
|
||||||
// Move cursor to the new category
|
|
||||||
self.cat_panel_cursor = self.model.categories.len().saturating_sub(1);
|
|
||||||
let count = self.model.categories.len();
|
|
||||||
self.status_msg = format!("Added category \"{buf}\" ({count} total). Enter to add more, Esc to finish.");
|
|
||||||
self.dirty = true;
|
|
||||||
} else {
|
|
||||||
self.status_msg = result.message.unwrap_or_default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Stay in CategoryAdd for the next entry
|
|
||||||
if let AppMode::CategoryAdd { ref mut buffer } = self.mode {
|
|
||||||
buffer.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Char(c) => {
|
|
||||||
if let AppMode::CategoryAdd { ref mut buffer } = self.mode {
|
|
||||||
buffer.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
if let AppMode::CategoryAdd { ref mut buffer } = self.mode {
|
|
||||||
buffer.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_item_add_key(&mut self, key: KeyEvent) -> Result<()> {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Esc => {
|
|
||||||
// Return to category panel
|
|
||||||
self.mode = AppMode::CategoryPanel;
|
|
||||||
self.status_msg = String::new();
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
let (cat, buf) = if let AppMode::ItemAdd { category, buffer } = &self.mode {
|
|
||||||
(category.clone(), buffer.trim().to_string())
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
if !buf.is_empty() {
|
|
||||||
let result = command::dispatch(
|
|
||||||
&mut self.model,
|
|
||||||
&Command::AddItem {
|
|
||||||
category: cat.clone(),
|
|
||||||
item: buf.clone(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if result.ok {
|
|
||||||
let count = self
|
|
||||||
.model
|
|
||||||
.category(&cat)
|
|
||||||
.map(|c| c.items.len())
|
|
||||||
.unwrap_or(0);
|
|
||||||
self.status_msg = format!(
|
|
||||||
"Added \"{buf}\" — {count} items. Enter to add more, Esc to finish."
|
|
||||||
);
|
|
||||||
self.dirty = true;
|
|
||||||
} else {
|
|
||||||
self.status_msg = result.message.unwrap_or_default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Clear buffer but stay in ItemAdd for next entry
|
|
||||||
if let AppMode::ItemAdd { ref mut buffer, .. } = self.mode {
|
|
||||||
buffer.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Tab => {
|
|
||||||
// Tab completes the current item and moves to next, same as Enter
|
|
||||||
return self.handle_item_add_key(crossterm::event::KeyEvent::new(
|
|
||||||
KeyCode::Enter,
|
|
||||||
crossterm::event::KeyModifiers::NONE,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
KeyCode::Char(c) => {
|
|
||||||
if let AppMode::ItemAdd { ref mut buffer, .. } = self.mode {
|
|
||||||
buffer.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
if let AppMode::ItemAdd { ref mut buffer, .. } = self.mode {
|
|
||||||
buffer.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_view_panel_key(&mut self, key: KeyEvent) -> Result<()> {
|
|
||||||
let view_names: Vec<String> = self.model.views.keys().cloned().collect();
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Esc | KeyCode::Tab => {
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
KeyCode::Up | KeyCode::Char('k') => {
|
|
||||||
if self.view_panel_cursor > 0 {
|
|
||||||
self.view_panel_cursor -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Down | KeyCode::Char('j') => {
|
|
||||||
if self.view_panel_cursor + 1 < view_names.len() {
|
|
||||||
self.view_panel_cursor += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
if let Some(name) = view_names.get(self.view_panel_cursor) {
|
|
||||||
command::dispatch(&mut self.model, &Command::SwitchView { name: name.clone() });
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Char('n') | KeyCode::Char('o') => {
|
|
||||||
let new_name = format!("View {}", self.model.views.len() + 1);
|
|
||||||
command::dispatch(
|
|
||||||
&mut self.model,
|
|
||||||
&Command::CreateView {
|
|
||||||
name: new_name.clone(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
command::dispatch(&mut self.model, &Command::SwitchView { name: new_name });
|
|
||||||
self.dirty = true;
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
KeyCode::Delete | KeyCode::Char('d') => {
|
|
||||||
if let Some(name) = view_names.get(self.view_panel_cursor) {
|
|
||||||
command::dispatch(&mut self.model, &Command::DeleteView { name: name.clone() });
|
|
||||||
if self.view_panel_cursor > 0 {
|
|
||||||
self.view_panel_cursor -= 1;
|
|
||||||
}
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_tile_select_key(&mut self, key: KeyEvent) -> Result<()> {
|
|
||||||
let cat_names: Vec<String> = self
|
|
||||||
.model
|
|
||||||
.category_names()
|
|
||||||
.into_iter()
|
|
||||||
.map(String::from)
|
|
||||||
.collect();
|
|
||||||
let cat_idx = if let AppMode::TileSelect { cat_idx } = self.mode {
|
|
||||||
cat_idx
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Esc | KeyCode::Tab => {
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
KeyCode::Left | KeyCode::Char('h') => {
|
|
||||||
if let AppMode::TileSelect { ref mut cat_idx } = self.mode {
|
|
||||||
if *cat_idx > 0 {
|
|
||||||
*cat_idx -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Right | KeyCode::Char('l') => {
|
|
||||||
if let AppMode::TileSelect { ref mut cat_idx } = self.mode {
|
|
||||||
if *cat_idx + 1 < cat_names.len() {
|
|
||||||
*cat_idx += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Enter | KeyCode::Char(' ') => {
|
|
||||||
if let Some(name) = cat_names.get(cat_idx) {
|
|
||||||
self.model.active_view_mut().cycle_axis(name);
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
KeyCode::Char('r') => {
|
|
||||||
if let Some(name) = cat_names.get(cat_idx) {
|
|
||||||
command::dispatch(
|
|
||||||
&mut self.model,
|
|
||||||
&Command::SetAxis {
|
|
||||||
category: name.clone(),
|
|
||||||
axis: Axis::Row,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
KeyCode::Char('c') => {
|
|
||||||
if let Some(name) = cat_names.get(cat_idx) {
|
|
||||||
command::dispatch(
|
|
||||||
&mut self.model,
|
|
||||||
&Command::SetAxis {
|
|
||||||
category: name.clone(),
|
|
||||||
axis: Axis::Column,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
KeyCode::Char('p') => {
|
|
||||||
if let Some(name) = cat_names.get(cat_idx) {
|
|
||||||
command::dispatch(
|
|
||||||
&mut self.model,
|
|
||||||
&Command::SetAxis {
|
|
||||||
category: name.clone(),
|
|
||||||
axis: Axis::Page,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
KeyCode::Char('n') => {
|
|
||||||
if let Some(name) = cat_names.get(cat_idx) {
|
|
||||||
command::dispatch(
|
|
||||||
&mut self.model,
|
|
||||||
&Command::SetAxis {
|
|
||||||
category: name.clone(),
|
|
||||||
axis: Axis::None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_export_key(&mut self, key: KeyEvent) -> Result<()> {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Esc => {
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
let buf = if let AppMode::ExportPrompt { buffer } = &self.mode {
|
|
||||||
buffer.clone()
|
|
||||||
} else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
let view_name = self.model.active_view.clone();
|
|
||||||
match persistence::export_csv(&self.model, &view_name, Path::new(&buf)) {
|
|
||||||
Ok(_) => {
|
|
||||||
self.status_msg = format!("Exported to {buf}");
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
self.status_msg = format!("Export error: {e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.mode = AppMode::Normal;
|
|
||||||
}
|
|
||||||
KeyCode::Char(c) => {
|
|
||||||
if let AppMode::ExportPrompt { buffer } = &mut self.mode {
|
|
||||||
buffer.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
if let AppMode::ExportPrompt { buffer } = &mut self.mode {
|
|
||||||
buffer.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_wizard_key(&mut self, key: KeyEvent) -> Result<()> {
|
fn handle_wizard_key(&mut self, key: KeyEvent) -> Result<()> {
|
||||||
if let Some(wizard) = &mut self.wizard {
|
if let Some(wizard) = &mut self.wizard {
|
||||||
|
|||||||
Reference in New Issue
Block a user