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

@ -47,10 +47,35 @@ pub struct CmdContext<'a> {
/// The display value at the cursor in records mode (including any
/// pending edit override). None for normal pivot views.
pub records_value: Option<String>,
/// How many data rows/cols fit on screen (for viewport scrolling).
/// Defaults to generous fallbacks when unknown.
pub visible_rows: usize,
pub visible_cols: usize,
/// Expanded categories in the tree panel
pub expanded_cats: &'a std::collections::HashSet<String>,
/// The key that triggered this command
pub key_code: KeyCode,
}
impl<'a> CmdContext<'a> {
/// Resolve the category panel tree entry at the current cursor.
pub fn cat_tree_entry(&self) -> Option<crate::ui::cat_tree::CatTreeEntry> {
let tree = crate::ui::cat_tree::build_cat_tree(self.model, self.expanded_cats);
tree.into_iter().nth(self.cat_panel_cursor)
}
/// The category name at the current tree cursor (whether on a
/// category header or an item).
pub fn cat_at_cursor(&self) -> Option<String> {
self.cat_tree_entry().map(|e| e.cat_name().to_string())
}
/// Total number of entries in the category tree.
pub fn cat_tree_len(&self) -> usize {
crate::ui::cat_tree::build_cat_tree(self.model, self.expanded_cats).len()
}
}
/// A command that reads state and produces effects.
pub trait Cmd: Debug + Send + Sync {
fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>>;
@ -217,6 +242,8 @@ pub struct CursorState {
pub col_count: usize,
pub row_offset: usize,
pub col_offset: usize,
pub visible_rows: usize,
pub visible_cols: usize,
}
impl CursorState {
@ -228,6 +255,8 @@ impl CursorState {
col_count: ctx.col_count,
row_offset: ctx.row_offset,
col_offset: ctx.col_offset,
visible_rows: ctx.visible_rows,
visible_cols: ctx.visible_cols,
}
}
}