refactor: make active_view and axis_of infallible
Both functions previously returned Option despite their invariants guaranteeing a value: active_view always names an existing view (maintained by new/switch_view/delete_view), and axis_of only returns None for categories never registered with the view (a programming error). Callers no longer need to handle the impossible None case, eliminating ~15 match/if-let Option guards across app.rs, dispatch.rs, grid.rs, tile_bar.rs, and category_panel.rs. Also adds Model::evaluate_f64 (returns 0.0 for empty cells) and collapses the double match-on-axis pattern in tile_bar/category_panel into a single axis_display(Axis) helper. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -27,10 +27,7 @@ impl<'a> GridWidget<'a> {
|
||||
}
|
||||
|
||||
fn render_grid(&self, area: Rect, buf: &mut Buffer) {
|
||||
let view = match self.model.active_view() {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
let view = self.model.active_view();
|
||||
|
||||
let layout = GridLayout::new(self.model, view);
|
||||
let (sel_row, sel_col) = view.selected;
|
||||
@ -97,9 +94,7 @@ impl<'a> GridWidget<'a> {
|
||||
};
|
||||
let value = self.model.evaluate(&key);
|
||||
|
||||
let cell_str = value.as_ref()
|
||||
.map(|v| format_value(v, fmt_comma, fmt_decimals))
|
||||
.unwrap_or_default();
|
||||
let cell_str = format_value(value.as_ref(), fmt_comma, fmt_decimals);
|
||||
let is_selected = ri == sel_row && ci == sel_col;
|
||||
let is_search_match = !self.search_query.is_empty()
|
||||
&& cell_str.to_lowercase().contains(&self.search_query.to_lowercase());
|
||||
@ -151,7 +146,7 @@ impl<'a> GridWidget<'a> {
|
||||
if x >= area.x + area.width { break; }
|
||||
let total: f64 = (0..layout.row_count())
|
||||
.filter_map(|ri| layout.cell_key(ri, ci))
|
||||
.map(|key| self.model.evaluate(&key).and_then(|v| v.as_f64()).unwrap_or(0.0))
|
||||
.map(|key| self.model.evaluate_f64(&key))
|
||||
.sum();
|
||||
let total_str = format_f64(total, fmt_comma, fmt_decimals);
|
||||
buf.set_string(x, y,
|
||||
@ -176,34 +171,33 @@ impl<'a> Widget for GridWidget<'a> {
|
||||
block.render(area, buf);
|
||||
|
||||
// Page axis bar
|
||||
if let Some(view) = self.model.active_view() {
|
||||
let layout = GridLayout::new(self.model, view);
|
||||
if !layout.page_coords.is_empty() && inner.height > 0 {
|
||||
let page_info: Vec<String> = layout.page_coords.iter()
|
||||
.map(|(cat, sel)| format!("{cat} = {sel}"))
|
||||
.collect();
|
||||
let page_str = format!(" [{}] ", page_info.join(" | "));
|
||||
buf.set_string(inner.x, inner.y,
|
||||
&page_str,
|
||||
Style::default().fg(Color::Magenta));
|
||||
let layout = GridLayout::new(self.model, self.model.active_view());
|
||||
if !layout.page_coords.is_empty() && inner.height > 0 {
|
||||
let page_info: Vec<String> = layout.page_coords.iter()
|
||||
.map(|(cat, sel)| format!("{cat} = {sel}"))
|
||||
.collect();
|
||||
let page_str = format!(" [{}] ", page_info.join(" | "));
|
||||
buf.set_string(inner.x, inner.y,
|
||||
&page_str,
|
||||
Style::default().fg(Color::Magenta));
|
||||
|
||||
let grid_area = Rect {
|
||||
y: inner.y + 1,
|
||||
height: inner.height.saturating_sub(1),
|
||||
..inner
|
||||
};
|
||||
self.render_grid(grid_area, buf);
|
||||
} else {
|
||||
self.render_grid(inner, buf);
|
||||
}
|
||||
let grid_area = Rect {
|
||||
y: inner.y + 1,
|
||||
height: inner.height.saturating_sub(1),
|
||||
..inner
|
||||
};
|
||||
self.render_grid(grid_area, buf);
|
||||
} else {
|
||||
self.render_grid(inner, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_value(v: &CellValue, comma: bool, decimals: u8) -> String {
|
||||
fn format_value(v: Option<&CellValue>, comma: bool, decimals: u8) -> String {
|
||||
match v {
|
||||
CellValue::Number(n) => format_f64(*n, comma, decimals),
|
||||
CellValue::Text(s) => s.clone(),
|
||||
Some(CellValue::Number(n)) => format_f64(*n, comma, decimals),
|
||||
Some(CellValue::Text(s)) => s.clone(),
|
||||
None => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,9 +389,7 @@ mod tests {
|
||||
c.add_item("Alice");
|
||||
c.add_item("Bob");
|
||||
}
|
||||
if let Some(v) = m.active_view_mut() {
|
||||
v.set_page_selection("Payer", "Bob");
|
||||
}
|
||||
m.active_view_mut().set_page_selection("Payer", "Bob");
|
||||
let text = buf_text(&render(&m, 80, 24));
|
||||
assert!(text.contains("Payer = Bob"), "expected 'Payer = Bob' in:\n{text}");
|
||||
}
|
||||
@ -451,9 +443,7 @@ mod tests {
|
||||
if let Some(c) = m.category_mut("Type") { c.add_item("Food"); c.add_item("Clothing"); }
|
||||
if let Some(c) = m.category_mut("Month") { c.add_item("Jan"); }
|
||||
if let Some(c) = m.category_mut("Recipient") { c.add_item("Alice"); c.add_item("Bob"); }
|
||||
if let Some(v) = m.active_view_mut() {
|
||||
v.set_axis("Recipient", crate::view::Axis::Row);
|
||||
}
|
||||
m.active_view_mut().set_axis("Recipient", crate::view::Axis::Row);
|
||||
|
||||
let text = buf_text(&render(&m, 80, 24));
|
||||
// Cross-product rows: Food/Alice, Food/Bob, Clothing/Alice, Clothing/Bob
|
||||
@ -472,9 +462,7 @@ mod tests {
|
||||
if let Some(c) = m.category_mut("Type") { c.add_item("Food"); }
|
||||
if let Some(c) = m.category_mut("Month") { c.add_item("Jan"); }
|
||||
if let Some(c) = m.category_mut("Recipient") { c.add_item("Alice"); c.add_item("Bob"); }
|
||||
if let Some(v) = m.active_view_mut() {
|
||||
v.set_axis("Recipient", crate::view::Axis::Row);
|
||||
}
|
||||
m.active_view_mut().set_axis("Recipient", crate::view::Axis::Row);
|
||||
// Set data at the full 3-coordinate key
|
||||
m.set_cell(
|
||||
coord(&[("Month", "Jan"), ("Recipient", "Alice"), ("Type", "Food")]),
|
||||
@ -493,9 +481,7 @@ mod tests {
|
||||
if let Some(c) = m.category_mut("Type") { c.add_item("Food"); }
|
||||
if let Some(c) = m.category_mut("Month") { c.add_item("Jan"); }
|
||||
if let Some(c) = m.category_mut("Year") { c.add_item("2024"); c.add_item("2025"); }
|
||||
if let Some(v) = m.active_view_mut() {
|
||||
v.set_axis("Year", crate::view::Axis::Column);
|
||||
}
|
||||
m.active_view_mut().set_axis("Year", crate::view::Axis::Column);
|
||||
|
||||
let text = buf_text(&render(&m, 80, 24));
|
||||
assert!(text.contains("Jan/2024"), "expected 'Jan/2024' in:\n{text}");
|
||||
|
||||
Reference in New Issue
Block a user