Files
improvise/crates/improvise-command/src/command/cmd/mod.rs
T

207 lines
6.9 KiB
Rust

pub mod cell;
pub mod commit;
pub mod core;
pub mod effect_cmds;
pub mod fit;
pub mod grid;
pub mod mode;
pub mod navigation;
pub mod panel;
pub mod registry;
pub mod search;
pub mod text_buffer;
pub mod tile;
pub mod wizard;
#[cfg(test)]
mod determinism;
// Re-export items used by external code
pub use self::core::{Cmd, CmdContext, CmdRegistry};
pub use registry::default_registry;
#[cfg(test)]
pub(super) mod test_helpers {
use std::collections::HashMap;
use crate::command::key::KeyCode;
use crate::ui::app::AppMode;
use crate::ui::effect::Effect;
use crate::view::GridLayout;
use crate::workbook::Workbook;
use super::core::CmdContext;
use super::registry::default_registry;
pub type CmdRegistry = super::core::CmdRegistry;
pub static EMPTY_BUFFERS: std::sync::LazyLock<HashMap<String, String>> =
std::sync::LazyLock::new(HashMap::new);
pub static EMPTY_EXPANDED: std::sync::LazyLock<std::collections::HashSet<String>> =
std::sync::LazyLock::new(std::collections::HashSet::new);
pub fn make_layout(workbook: &Workbook) -> GridLayout {
GridLayout::new(&workbook.model, workbook.active_view())
}
pub fn make_ctx<'a>(
workbook: &'a Workbook,
layout: &'a GridLayout,
registry: &'a CmdRegistry,
) -> CmdContext<'a> {
let view = workbook.active_view();
let (sr, sc) = (0, 0);
CmdContext {
model: &workbook.model,
workbook,
view,
layout,
registry,
mode: &AppMode::Normal,
selected: (0, 0),
row_offset: 0,
col_offset: 0,
search_query: "",
yanked: &None,
dirty: false,
search_mode: false,
formula_panel_open: false,
category_panel_open: false,
view_panel_open: false,
formula_cursor: 0,
cat_panel_cursor: 0,
view_panel_cursor: 0,
tile_cat_idx: 0,
buffers: &EMPTY_BUFFERS,
opened_editor: None,
view_back_stack: &[],
view_forward_stack: &[],
has_drill_state: false,
display_value: {
let key = layout.cell_key(sr, sc);
key.as_ref()
.and_then(|k| workbook.model.get_cell(k))
.map(crate::model::cell::CellValue::edit_text)
.unwrap_or_default()
},
visible_rows: 20,
visible_cols: 8,
expanded_cats: &EMPTY_EXPANDED,
wizard: None,
key_code: KeyCode::Null,
}
}
pub fn two_cat_model() -> Workbook {
let mut wb = Workbook::new("Test");
wb.add_category("Type").unwrap();
wb.add_category("Month").unwrap();
wb.add_item("Type", "Food").unwrap();
wb.add_item("Type", "Clothing").unwrap();
wb.add_item("Month", "Jan").unwrap();
wb.add_item("Month", "Feb").unwrap();
wb
}
pub fn three_cat_model_with_page() -> Workbook {
let mut wb = Workbook::new("Test");
wb.add_category("Type").unwrap();
wb.add_category("Month").unwrap();
wb.add_category("Region").unwrap();
wb.add_item("Type", "Food").unwrap();
wb.add_item("Type", "Clothing").unwrap();
wb.add_item("Month", "Jan").unwrap();
wb.add_item("Month", "Feb").unwrap();
wb.add_item("Region", "North").unwrap();
wb.add_item("Region", "South").unwrap();
wb.add_item("Region", "East").unwrap();
wb.active_view_mut()
.set_axis("Region", crate::view::Axis::Page);
wb
}
/// `two_cat_model` with one `_Measure` formula per target, in order.
pub fn with_formulas(targets: &[&str]) -> Workbook {
let mut wb = two_cat_model();
for target in targets {
wb.model.add_formula(crate::formula::ast::Formula {
raw: format!("{target} = 0"),
target: target.to_string(),
target_category: "_Measure".to_string(),
expr: crate::formula::ast::Expr::Number(0.0),
filter: None,
format: None,
});
}
wb
}
pub fn effects_debug(effects: &[Box<dyn Effect>]) -> String {
format!("{:?}", effects)
}
pub fn make_registry() -> CmdRegistry {
default_registry()
}
/// Two quarters of months on Row, two measures on Column, every cell funded,
/// Q2 collapsed: rows `[Jan, Feb, Q2]`, and the cursor on the fold at `(2, 1)`.
pub fn app_on_a_fold() -> crate::ui::app::App {
use crate::model::cell::{CellKey, CellValue};
use crate::view::Axis;
let mut wb = Workbook::new("T");
wb.add_category("Month").unwrap();
for (month, quarter) in [("Jan", "Q1"), ("Feb", "Q1"), ("Apr", "Q2"), ("May", "Q2")] {
wb.add_item_in_group("Month", month, quarter).unwrap();
}
wb.active_view_mut().set_expand_depth("Month", 1);
wb.add_item("_Measure", "Revenue").unwrap();
wb.add_item("_Measure", "Cost").unwrap();
for (i, month) in ["Jan", "Feb", "Apr", "May"].into_iter().enumerate() {
for measure in ["Revenue", "Cost"] {
let key = CellKey::new(vec![
("Month".into(), month.into()),
("_Measure".into(), measure.into()),
]);
wb.model.set_cell(key, CellValue::Number(10.0 + i as f64));
}
}
wb.active_view_mut().set_axis("Month", Axis::Row);
wb.active_view_mut().set_axis("_Measure", Axis::Column);
wb.active_view_mut().toggle_group_collapse("Month", &["Q2"]);
let mut app = crate::ui::app::App::new(wb, None);
app.rebuild_layout();
app.cursor_mut().selected = (2, 1);
assert!(
app.layout.folds_a_group(2, 1),
"setup: the cursor is on the fold"
);
assert_eq!(app.layout.row_label(2), "Q2", "setup: the fold is Q2's row");
app
}
/// Every stored coordinate naming no item of its category — what a write to a
/// fold leaves behind (core.md §14).
pub fn non_item_coordinates(wb: &Workbook) -> Vec<(String, String)> {
let model = &wb.model;
model
.data
.iter_cells()
.flat_map(|(key, _)| key.0.clone())
.filter(|(cat, item)| {
!model
.category(cat)
.is_some_and(|c| c.items.contains_key(item.as_str()))
})
.collect()
}
/// The document saved and read back: what reaches the file, not the store.
pub fn saved_and_reloaded(wb: &Workbook) -> (String, Workbook) {
let text = crate::persistence::format_md(wb);
let back = crate::persistence::parse_md(&text).expect("the saved text reloads");
(text, back)
}
}