feat: add new commands for records mode and category management

Add new commands for enhanced data entry and category management.

AddRecordRow: Adds a new record row in records mode with empty value.
TogglePruneEmpty: Toggles pruning of empty rows/columns in pivot mode.
ToggleRecordsMode: Switches between records and pivot layout.
DeleteCategoryAtCursor: Removes a category and all its cells.
ToggleCatExpand: Expands/collapses a category in the tree.
FilterToItem: Filters to show only items matching cursor position.

Model gains remove_category() and remove_item() to delete categories
and items along with all referencing cells and formulas.

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-06 15:09:57 -07:00
parent 5fe553b57a
commit 55cad99ae1
5 changed files with 383 additions and 4 deletions

View File

@ -107,6 +107,47 @@ impl Model {
Ok(id)
}
/// Remove a category and all cells that reference it.
pub fn remove_category(&mut self, name: &str) {
if !self.categories.contains_key(name) {
return;
}
self.categories.shift_remove(name);
// Remove from all views
for view in self.views.values_mut() {
view.on_category_removed(name);
}
// Remove cells that have a coord in this category
let to_remove: Vec<CellKey> = self
.data
.iter_cells()
.filter(|(k, _)| k.get(name).is_some())
.map(|(k, _)| k)
.collect();
for k in to_remove {
self.data.remove(&k);
}
// Remove formulas targeting this category
self.formulas
.retain(|f| f.target_category != name);
}
/// Remove an item from a category and all cells that reference it.
pub fn remove_item(&mut self, cat_name: &str, item_name: &str) {
if let Some(cat) = self.categories.get_mut(cat_name) {
cat.remove_item(item_name);
}
let to_remove: Vec<CellKey> = self
.data
.iter_cells()
.filter(|(k, _)| k.get(cat_name) == Some(item_name))
.map(|(k, _)| k)
.collect();
for k in to_remove {
self.data.remove(&k);
}
}
pub fn category_mut(&mut self, name: &str) -> Option<&mut Category> {
self.categories.get_mut(name)
}
@ -527,6 +568,31 @@ mod model_tests {
assert_eq!(m.get_cell(&k4), Some(&CellValue::Number(40.0)));
}
#[test]
fn remove_category_deletes_category_and_cells() {
let mut m = Model::new("Test");
m.add_category("Region").unwrap();
m.add_category("Product").unwrap();
m.category_mut("Region").unwrap().add_item("East");
m.category_mut("Product").unwrap().add_item("Shirts");
m.set_cell(
coord(&[("Region", "East"), ("Product", "Shirts")]),
CellValue::Number(42.0),
);
m.remove_category("Region");
assert!(m.category("Region").is_none());
// Cells referencing Region should be gone
assert_eq!(
m.data.iter_cells().count(),
0,
"all cells with Region coord should be removed"
);
// Views should no longer know about Region
// (axis_of would panic for unknown category, so check categories_on)
let v = m.active_view();
assert!(v.categories_on(crate::view::Axis::Row).is_empty());
}
#[test]
fn create_view_copies_category_structure() {
let mut m = Model::new("Test");