refactor!: unify records and pivot mode cell handling
Refactor records mode to use synthetic CellKeys (_Index, _Dim) for all columns, allowing uniform handling of display values and edits across both pivot and records modes. - Introduce `synthetic_record_info` to extract metadata from synthetic keys. - Update `GridLayout::cell_key` to return synthetic keys in records mode. - Add `GridLayout::resolve_display` to handle value resolution for synthetic keys. - Replace `records_col` and `records_value` in `CmdContext` with a unified `display_value`. - Update `EditOrDrill` and `AddRecordRow` to use synthetic key detection. - Refactor `CommitCellEdit` to use a shared `commit_cell_value` helper. BREAKING CHANGE: CmdContext fields `records_col` and `records_value` are replaced by `display_value` . Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-31B-it-GGUF:UD-Q5_K_XL)
This commit is contained in:
@ -2,6 +2,14 @@ use crate::model::cell::{CellKey, CellValue};
|
||||
use crate::model::Model;
|
||||
use crate::view::{Axis, View};
|
||||
|
||||
/// Extract (record_index, dim_name) from a synthetic records-mode CellKey.
|
||||
/// Returns None for normal pivot-mode keys.
|
||||
pub fn synthetic_record_info(key: &CellKey) -> Option<(usize, String)> {
|
||||
let idx: usize = key.get("_Index")?.parse().ok()?;
|
||||
let dim = key.get("_Dim")?.to_string();
|
||||
Some((idx, dim))
|
||||
}
|
||||
|
||||
/// One entry on a grid axis: either a visual group header or a data-item tuple.
|
||||
///
|
||||
/// `GroupHeader` entries are always visible so the user can see the group label
|
||||
@ -352,18 +360,36 @@ impl GridLayout {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Resolve the display string for a synthetic records-mode CellKey.
|
||||
/// Returns None for non-synthetic (pivot) keys.
|
||||
pub fn resolve_display(&self, key: &CellKey) -> Option<String> {
|
||||
let (idx, dim) = synthetic_record_info(key)?;
|
||||
let records = self.records.as_ref()?;
|
||||
let (orig_key, value) = records.get(idx)?;
|
||||
if dim == "Value" {
|
||||
Some(value.to_string())
|
||||
} else {
|
||||
Some(orig_key.get(&dim).unwrap_or("").to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the CellKey for the data cell at (row, col), including the active
|
||||
/// page-axis filter. Returns None if row or col is out of bounds.
|
||||
/// In records mode: returns the real underlying CellKey when the column
|
||||
/// is "Value" (editable); returns None for coord columns (read-only).
|
||||
/// In records mode: returns a synthetic `(_Index, _Dim)` key for every column.
|
||||
pub fn cell_key(&self, row: usize, col: usize) -> Option<CellKey> {
|
||||
if let Some(records) = &self.records {
|
||||
// Records mode: only the Value column maps to a real, editable cell.
|
||||
if self.col_label(col) == "Value" {
|
||||
return records.get(row).map(|(k, _)| k.clone());
|
||||
} else {
|
||||
if self.records.is_some() {
|
||||
let records = self.records.as_ref().unwrap();
|
||||
if row >= records.len() {
|
||||
return None;
|
||||
}
|
||||
let col_label = self.col_label(col);
|
||||
if col_label.is_empty() {
|
||||
return None;
|
||||
}
|
||||
return Some(CellKey::new(vec![
|
||||
("_Index".to_string(), row.to_string()),
|
||||
("_Dim".to_string(), col_label),
|
||||
]));
|
||||
}
|
||||
let row_item = self
|
||||
.row_items
|
||||
@ -527,7 +553,7 @@ fn cross_product(model: &Model, view: &View, cats: &[String]) -> Vec<AxisEntry>
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{AxisEntry, GridLayout};
|
||||
use super::{synthetic_record_info, AxisEntry, GridLayout};
|
||||
use crate::model::cell::{CellKey, CellValue};
|
||||
use crate::model::Model;
|
||||
use crate::view::Axis;
|
||||
@ -592,40 +618,66 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn records_mode_cell_key_editable_for_value_column() {
|
||||
fn records_mode_cell_key_returns_synthetic_for_all_columns() {
|
||||
let mut m = records_model();
|
||||
let v = m.active_view_mut();
|
||||
v.set_axis("_Index", Axis::Row);
|
||||
v.set_axis("_Dim", Axis::Column);
|
||||
let layout = GridLayout::new(&m, m.active_view());
|
||||
assert!(layout.is_records_mode());
|
||||
// Find the "Value" column index
|
||||
let cols: Vec<String> = (0..layout.col_count()).map(|i| layout.col_label(i)).collect();
|
||||
// All columns return synthetic keys
|
||||
let value_col = cols.iter().position(|c| c == "Value").unwrap();
|
||||
// cell_key should be Some for Value column
|
||||
let key = layout.cell_key(0, value_col);
|
||||
assert!(key.is_some(), "Value column should be editable");
|
||||
// cell_key should be None for coord columns
|
||||
let key = layout.cell_key(0, value_col).unwrap();
|
||||
assert_eq!(key.get("_Index"), Some("0"));
|
||||
assert_eq!(key.get("_Dim"), Some("Value"));
|
||||
|
||||
let region_col = cols.iter().position(|c| c == "Region").unwrap();
|
||||
assert!(
|
||||
layout.cell_key(0, region_col).is_none(),
|
||||
"Region column should not be editable"
|
||||
);
|
||||
let key = layout.cell_key(0, region_col).unwrap();
|
||||
assert_eq!(key.get("_Index"), Some("0"));
|
||||
assert_eq!(key.get("_Dim"), Some("Region"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn records_mode_cell_key_maps_to_real_cell() {
|
||||
fn records_mode_resolve_display_returns_values() {
|
||||
let mut m = records_model();
|
||||
let v = m.active_view_mut();
|
||||
v.set_axis("_Index", Axis::Row);
|
||||
v.set_axis("_Dim", Axis::Column);
|
||||
let layout = GridLayout::new(&m, m.active_view());
|
||||
let cols: Vec<String> = (0..layout.col_count()).map(|i| layout.col_label(i)).collect();
|
||||
|
||||
// Value column resolves to the cell value
|
||||
let value_col = cols.iter().position(|c| c == "Value").unwrap();
|
||||
// The CellKey at (0, Value) should look up a real cell value
|
||||
let key = layout.cell_key(0, value_col).unwrap();
|
||||
let val = m.evaluate(&key);
|
||||
assert!(val.is_some(), "cell_key should resolve to a real cell");
|
||||
let display = layout.resolve_display(&key);
|
||||
assert!(display.is_some(), "Value column should resolve");
|
||||
|
||||
// Category column resolves to the coordinate value
|
||||
let region_col = cols.iter().position(|c| c == "Region").unwrap();
|
||||
let key = layout.cell_key(0, region_col).unwrap();
|
||||
let display = layout.resolve_display(&key).unwrap();
|
||||
assert!(!display.is_empty(), "Region column should resolve to a value");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthetic_record_info_returns_none_for_pivot_keys() {
|
||||
let key = CellKey::new(vec![
|
||||
("Region".to_string(), "East".to_string()),
|
||||
("Product".to_string(), "Shoes".to_string()),
|
||||
]);
|
||||
assert!(synthetic_record_info(&key).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthetic_record_info_extracts_index_and_dim() {
|
||||
let key = CellKey::new(vec![
|
||||
("_Index".to_string(), "3".to_string()),
|
||||
("_Dim".to_string(), "Region".to_string()),
|
||||
]);
|
||||
let (idx, dim) = synthetic_record_info(&key).unwrap();
|
||||
assert_eq!(idx, 3);
|
||||
assert_eq!(dim, "Region");
|
||||
}
|
||||
|
||||
fn coord(pairs: &[(&str, &str)]) -> CellKey {
|
||||
|
||||
Reference in New Issue
Block a user