refactor(ui): improve layout management and record sharing in App
Refactor App and DrillState to use Rc for sharing records, and introduce rebuild_layout() to manage GridLayout lifecycle. This ensures the layout is consistently updated when the model or drill state changes. - Use Rc<Vec<(CellKey, CellValue)>> for DrillState.records to allow cheap cloning - Add layout field to App - Implement rebuild_layout() in App to refresh the GridLayout - Update App::new and App::cmd_context to use the new layout management - Ensure layout is rebuilt after applying effects and handling key events Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q5_K_XL)
This commit is contained in:
@ -5,24 +5,25 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::command::cmd::CmdContext;
|
||||
use crate::command::keymap::{Keymap, KeymapSet};
|
||||
use crate::import::wizard::ImportWizard;
|
||||
use crate::model::cell::CellValue;
|
||||
use crate::model::Model;
|
||||
use crate::persistence;
|
||||
use crate::ui::grid::{compute_col_widths, compute_row_header_width, compute_visible_cols, parse_number_format};
|
||||
use crate::ui::grid::{
|
||||
compute_col_widths, compute_row_header_width, compute_visible_cols, parse_number_format,
|
||||
};
|
||||
use crate::view::GridLayout;
|
||||
|
||||
/// Drill-down state: frozen record snapshot + pending edits that have not
|
||||
/// yet been applied to the model.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DrillState {
|
||||
/// Frozen snapshot of records shown in the drill view.
|
||||
pub records: Vec<(
|
||||
crate::model::cell::CellKey,
|
||||
crate::model::cell::CellValue,
|
||||
)>,
|
||||
/// Frozen snapshot of records shown in the drill view (Rc for cheap cloning).
|
||||
pub records: Rc<Vec<(crate::model::cell::CellKey, crate::model::cell::CellValue)>>,
|
||||
/// Pending edits keyed by (record_idx, column_name) → new string value.
|
||||
/// column_name is either "Value" or a category name.
|
||||
pub pending_edits: std::collections::HashMap<(usize, String), String>,
|
||||
@ -101,11 +102,18 @@ pub struct App {
|
||||
pub buffers: HashMap<String, String>,
|
||||
/// Transient keymap for Emacs-style prefix key sequences (g→gg, y→yy, etc.)
|
||||
pub transient_keymap: Option<Arc<Keymap>>,
|
||||
/// Current grid layout, derived from model + view + drill_state.
|
||||
/// Rebuilt via `rebuild_layout()` after state changes.
|
||||
pub layout: GridLayout,
|
||||
keymap_set: KeymapSet,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(model: Model, file_path: Option<PathBuf>) -> Self {
|
||||
let layout = {
|
||||
let view = model.active_view();
|
||||
GridLayout::with_frozen_records(&model, view, None)
|
||||
};
|
||||
Self {
|
||||
model,
|
||||
file_path,
|
||||
@ -132,17 +140,26 @@ impl App {
|
||||
expanded_cats: std::collections::HashSet::new(),
|
||||
buffers: HashMap::new(),
|
||||
transient_keymap: None,
|
||||
layout,
|
||||
keymap_set: KeymapSet::default_keymaps(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Rebuild the grid layout from current model, view, and drill state.
|
||||
/// Note: `with_frozen_records` already handles pruning internally.
|
||||
pub fn rebuild_layout(&mut self) {
|
||||
let view = self.model.active_view();
|
||||
let frozen = self.drill_state.as_ref().map(|s| Rc::clone(&s.records));
|
||||
self.layout = GridLayout::with_frozen_records(&self.model, view, frozen);
|
||||
}
|
||||
|
||||
pub fn cmd_context(&self, key: KeyCode, _mods: KeyModifiers) -> CmdContext<'_> {
|
||||
let view = self.model.active_view();
|
||||
let frozen_records = self.drill_state.as_ref().map(|s| s.records.clone());
|
||||
let layout = GridLayout::with_frozen_records(&self.model, view, frozen_records);
|
||||
let layout = &self.layout;
|
||||
let (sel_row, sel_col) = view.selected;
|
||||
CmdContext {
|
||||
model: &self.model,
|
||||
layout,
|
||||
mode: &self.mode,
|
||||
selected: view.selected,
|
||||
row_offset: view.row_offset,
|
||||
@ -159,23 +176,20 @@ impl App {
|
||||
cat_panel_cursor: self.cat_panel_cursor,
|
||||
view_panel_cursor: self.view_panel_cursor,
|
||||
tile_cat_idx: self.tile_cat_idx,
|
||||
cell_key: layout.cell_key(sel_row, sel_col),
|
||||
row_count: layout.row_count(),
|
||||
col_count: layout.col_count(),
|
||||
none_cats: layout.none_cats.clone(),
|
||||
view_back_stack: self.view_back_stack.clone(),
|
||||
view_forward_stack: self.view_forward_stack.clone(),
|
||||
view_back_stack: &self.view_back_stack,
|
||||
view_forward_stack: &self.view_forward_stack,
|
||||
display_value: {
|
||||
let key = layout.cell_key(sel_row, sel_col);
|
||||
if let Some(k) = &key {
|
||||
if let Some((idx, dim)) = crate::view::synthetic_record_info(k) {
|
||||
// Synthetic records key: check pending drill edits first
|
||||
self.drill_state.as_ref()
|
||||
self.drill_state
|
||||
.as_ref()
|
||||
.and_then(|s| s.pending_edits.get(&(idx, dim)).cloned())
|
||||
.or_else(|| layout.resolve_display(k))
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
self.model.get_cell(k)
|
||||
self.model
|
||||
.get_cell(k)
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
@ -183,15 +197,17 @@ impl App {
|
||||
String::new()
|
||||
}
|
||||
},
|
||||
// Approximate visible rows 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: {
|
||||
let (fmt_comma, fmt_decimals) = parse_number_format(&view.number_format);
|
||||
let col_widths = compute_col_widths(&self.model, &layout, fmt_comma, fmt_decimals);
|
||||
let row_header_width = compute_row_header_width(&layout);
|
||||
compute_visible_cols(&col_widths, row_header_width, self.term_width, view.col_offset)
|
||||
let col_widths = compute_col_widths(&self.model, layout, fmt_comma, fmt_decimals);
|
||||
let row_header_width = compute_row_header_width(layout);
|
||||
compute_visible_cols(
|
||||
&col_widths,
|
||||
row_header_width,
|
||||
self.term_width,
|
||||
view.col_offset,
|
||||
)
|
||||
},
|
||||
expanded_cats: &self.expanded_cats,
|
||||
key_code: key,
|
||||
@ -202,6 +218,7 @@ impl App {
|
||||
for effect in effects {
|
||||
effect.apply(self);
|
||||
}
|
||||
self.rebuild_layout();
|
||||
}
|
||||
|
||||
/// True when the model has no categories yet (show welcome screen)
|
||||
@ -210,6 +227,8 @@ impl App {
|
||||
}
|
||||
|
||||
pub fn handle_key(&mut self, key: KeyEvent) -> Result<()> {
|
||||
self.rebuild_layout();
|
||||
|
||||
// Transient keymap (prefix key sequence) takes priority
|
||||
if let Some(transient) = self.transient_keymap.take() {
|
||||
let effects = {
|
||||
|
||||
@ -398,7 +398,7 @@ pub struct StartDrill(pub Vec<(CellKey, CellValue)>);
|
||||
impl Effect for StartDrill {
|
||||
fn apply(&self, app: &mut App) {
|
||||
app.drill_state = Some(super::app::DrillState {
|
||||
records: self.0.clone(),
|
||||
records: std::rc::Rc::new(self.0.clone()),
|
||||
pending_edits: std::collections::HashMap::new(),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user