feat: add category tree with expand/collapse in category panel

Add a tree-based category panel that supports expand/collapse of categories.

Introduces CatTreeEntry and build_cat_tree to render categories as
a collapsible tree. The category panel now displays categories with
expand indicators (▶/▼) and shows items under expanded categories.

CmdContext gains cat_tree_entry(), cat_at_cursor(), and cat_tree_len()
methods to work with the tree. App tracks expanded_cats in a HashSet.

Keymap updates: Enter in category panel now triggers filter-to-item.

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 0c04d63542
commit 5fe553b57a
7 changed files with 170 additions and 81 deletions

View File

@ -91,6 +91,11 @@ pub struct App {
/// when filters would change. Pending edits are stored alongside and
/// applied to the model on commit/navigate-away.
pub drill_state: Option<DrillState>,
/// Terminal dimensions (updated on resize and at startup).
pub term_width: u16,
pub term_height: u16,
/// Categories expanded in the category panel tree view.
pub expanded_cats: std::collections::HashSet<String>,
/// Named text buffers for text-entry modes
pub buffers: HashMap<String, String>,
/// Transient keymap for Emacs-style prefix key sequences (g→gg, y→yy, etc.)
@ -121,6 +126,9 @@ impl App {
view_back_stack: Vec::new(),
view_forward_stack: Vec::new(),
drill_state: None,
term_width: crossterm::terminal::size().map(|(w, _)| w).unwrap_or(80),
term_height: crossterm::terminal::size().map(|(_, h)| h).unwrap_or(24),
expanded_cats: std::collections::HashSet::new(),
buffers: HashMap::new(),
transient_keymap: None,
keymap_set: KeymapSet::default_keymaps(),
@ -171,6 +179,14 @@ impl App {
} else {
None
},
// Approximate visible rows/cols from terminal size.
// Chrome: title(1) + border(2) + col_headers(n_col_levels) + separator(1)
// + tile_bar(1) + status_bar(1) = ~8 rows of chrome.
visible_rows: (self.term_height as usize).saturating_sub(8),
// Visible cols depends on column widths — use a rough estimate.
// The grid renderer does the precise calculation.
visible_cols: ((self.term_width as usize).saturating_sub(30) / 12).max(1),
expanded_cats: &self.expanded_cats,
key_code: key,
}
}