improvise-gk51 WIP: one predicate for the projection and the refusal; CellId::NonItem
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
@@ -11,7 +11,7 @@ use crate::ui::app::AppMode;
|
||||
use crate::ui::effect::{Effect, Panel};
|
||||
use crate::view::{Axis, GridLayout, View};
|
||||
use crate::workbook::Workbook;
|
||||
use improvise_protocol::CellId;
|
||||
use improvise_protocol::{CellId, non_item_refusal};
|
||||
|
||||
/// What a command says when the cursor sits on a collapsed group's row (§14).
|
||||
pub const FOLD_REFUSAL: &str = "Collapsed group — press z to expand it first";
|
||||
@@ -102,6 +102,7 @@ impl<'a> CmdContext<'a> {
|
||||
match self.cursor_cell_id() {
|
||||
Some(CellId::Aggregate(_)) => Err(self.aggregate_refusal()),
|
||||
Some(CellId::Derived { target }) => Err(derived_refusal(&target)),
|
||||
Some(CellId::NonItem { category, item }) => Err(non_item_refusal(&category, &item)),
|
||||
Some(CellId::Record(_) | CellId::Create(_) | CellId::Coordinate) | None => {
|
||||
self.cell_key().ok_or_else(|| NO_CELL.to_string())
|
||||
}
|
||||
@@ -116,6 +117,7 @@ impl<'a> CmdContext<'a> {
|
||||
Some(CellId::Aggregate(_)) => Err(self.aggregate_refusal()),
|
||||
Some(CellId::Derived { target }) => Err(derived_refusal(&target)),
|
||||
Some(CellId::Create(_) | CellId::Coordinate) => Err(NO_VALUE_REFUSAL.to_string()),
|
||||
Some(CellId::NonItem { category, item }) => Err(non_item_refusal(&category, &item)),
|
||||
None => Err(match self.cell_key() {
|
||||
Some(_) => NO_VALUE_REFUSAL,
|
||||
None => NO_CELL,
|
||||
|
||||
@@ -595,10 +595,9 @@ impl App {
|
||||
/// (improvise-b114, improvise-nacd). See `context/code/command.md`.
|
||||
fn non_item_refusal(&self, key: &CellKey) -> Option<String> {
|
||||
let model = &self.model_state.workbook.model;
|
||||
key.0.iter().find_map(|(cat, name)| {
|
||||
let item = model.effective_item_names(cat).contains(name);
|
||||
(!item).then(|| format!("{name} is no item of {cat}, so there is no one cell to write"))
|
||||
})
|
||||
model
|
||||
.first_non_item(key)
|
||||
.map(|(cat, name)| improvise_protocol::non_item_refusal(cat, name))
|
||||
}
|
||||
|
||||
/// A record the store does not hold is known only by its layout position, and
|
||||
|
||||
@@ -329,10 +329,16 @@ pub(crate) fn cell_id(
|
||||
// different intent from `SetCell` and still the host's (gotcha 1).
|
||||
RecordCol::Category(_) => return Some(CellId::Coordinate),
|
||||
};
|
||||
return Some(CellId::Record(RecordCell {
|
||||
record: RecordHash::of(record),
|
||||
measure,
|
||||
}));
|
||||
// The refusal's own question, asked here too, so no cell is offered
|
||||
// for a write the host would then refuse (improvise-gk51).
|
||||
let key = layout.record_cell_key(row, col)?;
|
||||
return Some(match model.first_non_item(&key) {
|
||||
Some((category, item)) => non_item(category, item),
|
||||
None => CellId::Record(RecordCell {
|
||||
record: RecordHash::of(record),
|
||||
measure,
|
||||
}),
|
||||
});
|
||||
}
|
||||
let key = layout.cell_key(row, col)?;
|
||||
// A folded group is an aggregate whatever is on None: no record carries
|
||||
@@ -348,17 +354,26 @@ pub(crate) fn cell_id(
|
||||
let derived = model.formula_for(&key).map(|f| CellId::Derived {
|
||||
target: f.target.clone(),
|
||||
});
|
||||
let stored = || match model.first_non_item(&key) {
|
||||
Some((category, item)) => non_item(category, item),
|
||||
None => CellId::Record(RecordCell {
|
||||
measure: key.get(MEASURE_CATEGORY).map(String::from),
|
||||
record: RecordHash::of(&key),
|
||||
}),
|
||||
};
|
||||
Some(match aggregated {
|
||||
true => CellId::Aggregate(AggregateHash::of(&key)),
|
||||
false => derived.unwrap_or_else(|| {
|
||||
CellId::Record(RecordCell {
|
||||
measure: key.get(MEASURE_CATEGORY).map(String::from),
|
||||
record: RecordHash::of(&key),
|
||||
})
|
||||
}),
|
||||
false => derived.unwrap_or_else(stored),
|
||||
})
|
||||
}
|
||||
|
||||
fn non_item(category: &str, item: &str) -> CellId {
|
||||
CellId::NonItem {
|
||||
category: category.to_string(),
|
||||
item: item.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The categories the category panel draws: **every** category, virtual ones
|
||||
/// included, with their items and their axis (improvise-vjms, gotcha 7).
|
||||
pub fn category_rows(model: &Model, view: &View) -> Vec<CategoryRow> {
|
||||
@@ -505,7 +520,8 @@ pub fn published_basis(app: &App, grid: &GridCache) -> Basis {
|
||||
CellId::Aggregate(_)
|
||||
| CellId::Derived { .. }
|
||||
| CellId::Create(_)
|
||||
| CellId::Coordinate => None,
|
||||
| CellId::Coordinate
|
||||
| CellId::NonItem { .. } => None,
|
||||
})
|
||||
.collect();
|
||||
basis::published(app.document_generation(), app.layout_version(), records)
|
||||
|
||||
@@ -589,6 +589,15 @@ impl Model {
|
||||
}
|
||||
}
|
||||
|
||||
/// The first coordinate of `key` naming no item of its category: the one
|
||||
/// question a write's refusal and a cell's projection share (improvise-gk51).
|
||||
pub fn first_non_item<'k>(&self, key: &'k CellKey) -> Option<(&'k str, &'k str)> {
|
||||
key.0
|
||||
.iter()
|
||||
.find(|(cat, name)| !self.effective_item_names(cat).contains(name))
|
||||
.map(|(cat, name)| (cat.as_str(), name.as_str()))
|
||||
}
|
||||
|
||||
/// Category names excluding virtual categories (_Index, _Dim).
|
||||
pub fn regular_category_names(&self) -> Vec<&str> {
|
||||
self.categories
|
||||
|
||||
@@ -33,7 +33,8 @@ pub use render::{
|
||||
DocumentCache, FormulaCache, FormulaEntry, FrameEntry, FrameGroup, GridCache, GridExtent,
|
||||
GridUpdate, MAX_ROW_HEADER_W, MIN_ROW_HEADER_W, Projection, RecordCell, Snapshot, TileBarCache,
|
||||
TileEntry, ViewListCache, ViewSummary, WidgetCache, WizardCache, WizardDateField,
|
||||
WizardFieldKind, WizardProposal, WizardStepKind, header_level_widths, opening_tile,
|
||||
WizardFieldKind, WizardProposal, WizardStepKind, header_level_widths, non_item_refusal,
|
||||
opening_tile,
|
||||
};
|
||||
pub use resolve::{
|
||||
Click, KeyOutcome, OpenedEditor, Request, click, edited, read_buffer, resolve, typed,
|
||||
|
||||
@@ -59,6 +59,15 @@ pub enum CellId {
|
||||
/// A records-mode coordinate. Renaming it moves every measure cell in the
|
||||
/// row (gotcha 1), which no client composes: `protocol.md` §3 (improvise-exdg).
|
||||
Coordinate,
|
||||
/// A stored cell whose `category` coordinate names no item of it, so no write
|
||||
/// can address it: `context/code/command.md` (improvise-gk51).
|
||||
NonItem { category: String, item: String },
|
||||
}
|
||||
|
||||
/// Why a cell whose coordinate names no item takes no write. One wording for the
|
||||
/// projection's decline and the host's refusal (improvise-gk51).
|
||||
pub fn non_item_refusal(category: &str, item: &str) -> String {
|
||||
format!("{item} is no item of {category}, so there is no one cell to write")
|
||||
}
|
||||
|
||||
/// What a creating column creates. The name typed into it is the whole
|
||||
|
||||
@@ -793,6 +793,9 @@ fn edit_or_drill(state: &ClientState, mode: AppMode) -> Option<Vec<Command>> {
|
||||
// them; declining rather than `None` keeps that a door, not a hole.
|
||||
CellId::Create(_) => Some(vec![Command::SetStatus(not_yet("creating a column"))]),
|
||||
CellId::Coordinate => Some(vec![Command::SetStatus(not_yet("renaming a coordinate"))]),
|
||||
CellId::NonItem { category, item } => Some(vec![Command::SetStatus(
|
||||
crate::non_item_refusal(category, item),
|
||||
)]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1004,9 +1007,11 @@ fn advanced(state: &ClientState, edit_mode: AppMode, advance: Advance) -> Option
|
||||
fn record_at_cursor(state: &ClientState) -> Option<crate::RecordHash> {
|
||||
match cell_at_cursor(state)?.id.as_ref()? {
|
||||
CellId::Record(cell) => Some(cell.record),
|
||||
CellId::Aggregate(_) | CellId::Derived { .. } | CellId::Create(_) | CellId::Coordinate => {
|
||||
None
|
||||
}
|
||||
CellId::Aggregate(_)
|
||||
| CellId::Derived { .. }
|
||||
| CellId::Create(_)
|
||||
| CellId::Coordinate
|
||||
| CellId::NonItem { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1112,9 +1117,11 @@ fn model(state: &ClientState, cmd: ModelCommand) -> Option<Command> {
|
||||
fn record_cell_at_cursor(state: &ClientState) -> Option<RecordCell> {
|
||||
match cell_at_cursor(state)?.id.as_ref()? {
|
||||
CellId::Record(cell) => Some(cell.clone()),
|
||||
CellId::Aggregate(_) | CellId::Derived { .. } | CellId::Create(_) | CellId::Coordinate => {
|
||||
None
|
||||
}
|
||||
CellId::Aggregate(_)
|
||||
| CellId::Derived { .. }
|
||||
| CellId::Create(_)
|
||||
| CellId::Coordinate
|
||||
| CellId::NonItem { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -201,7 +201,8 @@ impl Client {
|
||||
CellId::Aggregate(_)
|
||||
| CellId::Derived { .. }
|
||||
| CellId::Create(_)
|
||||
| CellId::Coordinate => None,
|
||||
| CellId::Coordinate
|
||||
| CellId::NonItem { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +147,8 @@ impl RenderCell {
|
||||
CellId::Aggregate(_)
|
||||
| CellId::Derived { .. }
|
||||
| CellId::Create(_)
|
||||
| CellId::Coordinate,
|
||||
| CellId::Coordinate
|
||||
| CellId::NonItem { .. },
|
||||
)
|
||||
| None => false,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user