improvise-7mro: every cache read goes through the frame's own origin
cell_at_cursor and seed_at read through projected_at, and cell_at (the cursor-offset read) is gone. A cell of the grid outside the window the cache holds is unprojected: i and an advance open an editor awaiting its seed, which the fold settles once the host projects the cell, and a commit, x, yy or p on it waits out loud rather than composing anything. 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:
@@ -176,6 +176,24 @@ impl ClientState {
|
||||
self.view.opened_editor = Some(crate::OpenedEditor::on(seed));
|
||||
}
|
||||
self.settle_declined_editor();
|
||||
self.settle_awaited_seed();
|
||||
}
|
||||
|
||||
/// An untouched editor opened on a cell not projected yet takes its seed
|
||||
/// once the host projects that cell, and never over typing (improvise-7mro).
|
||||
fn settle_awaited_seed(&mut self) {
|
||||
let (row, col) = self.view.cursor().selected;
|
||||
let waiting = self.view.mode.is_editing()
|
||||
&& self
|
||||
.view
|
||||
.opened_editor
|
||||
.as_ref()
|
||||
.is_some_and(|opened| opened.awaiting && !opened.touched);
|
||||
if waiting && !crate::resolve::unprojected(self, row, col) {
|
||||
let seed = crate::resolve::edit_seed(self).unwrap_or_default();
|
||||
self.view.buffers.insert("edit".to_string(), seed.clone());
|
||||
self.view.opened_editor = Some(crate::OpenedEditor::on(seed));
|
||||
}
|
||||
}
|
||||
|
||||
/// An untouched editor on a cell `i` declines closes once the host projects
|
||||
|
||||
@@ -325,10 +325,13 @@ fn step(state: &ClientState, event: KeyEvent, name: &str, args: &[String]) -> St
|
||||
|
||||
/// A cell commit onto a cell `i` declines, refused as the TUI's
|
||||
/// `committable_cell_key` refuses it: out loud, taking the binding with it (ik6l).
|
||||
/// A cell op on a cell the host has not shown yet waits, out loud too (7mro).
|
||||
fn refused_here(state: &ClientState, name: &str) -> Option<String> {
|
||||
let (row, col) = state.view.cursor().selected;
|
||||
let waiting = unprojected(state, row, col).then(|| NOT_PROJECTED.to_string());
|
||||
match name {
|
||||
"commit-cell-edit" | "commit-and-advance-right" => declined_at(state, row, col),
|
||||
"commit-cell-edit" | "commit-and-advance-right" => declined_at(state, row, col).or(waiting),
|
||||
"clear-cell" | "yank" | "paste" => waiting,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -770,13 +773,11 @@ fn tile_cursor(state: &ClientState, delta: i32) -> Option<Vec<Command>> {
|
||||
|
||||
// ── editing a cell ──────────────────────────────────────────────────────
|
||||
|
||||
/// The cell under the cursor in this viewport-indexed cache. `None` when the
|
||||
/// selection is outside the window — reachable only by a projection race.
|
||||
/// The cell under the cursor, as the host projected it. `None` outside the
|
||||
/// window the cache holds, which the cursor's own offsets may have left (7mro).
|
||||
fn cell_at_cursor(state: &ClientState) -> Option<&crate::CellDisplay> {
|
||||
let cursor = state.view.cursor();
|
||||
let row = cursor.selected.0.checked_sub(cursor.row_offset)?;
|
||||
let col = cursor.selected.1.checked_sub(cursor.col_offset)?;
|
||||
state.grid.cell(row, col)
|
||||
let (row, col) = state.view.cursor().selected;
|
||||
projected_at(state, row, col)
|
||||
}
|
||||
|
||||
/// `EditOrDrill::execute`, with ADR 002 answering the branch, and drill
|
||||
@@ -787,6 +788,10 @@ fn edit_or_drill(state: &ClientState, mode: AppMode) -> Option<Vec<Command>> {
|
||||
if state.grid.records {
|
||||
return Some(vec![Command::SetStatus(not_yet("editing in records mode"))]);
|
||||
}
|
||||
let (row, col) = state.view.cursor().selected;
|
||||
if unprojected(state, row, col) {
|
||||
return Some(awaiting_seed(mode));
|
||||
}
|
||||
let cell = cell_at_cursor(state)?;
|
||||
match cell.id.as_ref()? {
|
||||
// `edit_text`, never `text`: the drawn text has been through
|
||||
@@ -817,6 +822,19 @@ fn opened_on(seed: String, mode: AppMode) -> Vec<Command> {
|
||||
]
|
||||
}
|
||||
|
||||
/// Open the cell editor on a cell the host has not shown yet: empty, its seed
|
||||
/// settled by the fold once the cell is projected (improvise-7mro).
|
||||
fn awaiting_seed(mode: AppMode) -> Vec<Command> {
|
||||
vec![
|
||||
Command::SetBuffer {
|
||||
name: EDIT_BUFFER.to_string(),
|
||||
value: String::new(),
|
||||
},
|
||||
Command::SetOpenedEditor(Some(OpenedEditor::awaiting())),
|
||||
Command::ChangeMode(mode),
|
||||
]
|
||||
}
|
||||
|
||||
/// One direction of the view history. An empty stack is answered out loud:
|
||||
/// `reduce_view`'s own arm is silent there — `context/code/clients.md`.
|
||||
fn step_history(stack: &[crate::ViewFrame], step: Command, empty: &str) -> Vec<Command> {
|
||||
@@ -1023,27 +1041,20 @@ pub(crate) fn edit_seed(state: &ClientState) -> Option<String> {
|
||||
cell_at_cursor(state)?.edit_text.clone()
|
||||
}
|
||||
|
||||
/// The display text at an absolute grid position, as the cache holds it.
|
||||
/// The edit text the host projected at an absolute grid position.
|
||||
fn seed_at(state: &ClientState, row: usize, col: usize) -> String {
|
||||
cell_at(state, row, col)
|
||||
projected_at(state, row, col)
|
||||
.and_then(|cell| cell.edit_text.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The projected cell at an absolute grid position, `None` outside the window.
|
||||
fn cell_at(state: &ClientState, row: usize, col: usize) -> Option<&crate::CellDisplay> {
|
||||
let cursor = state.view.cursor();
|
||||
let r = row.checked_sub(cursor.row_offset)?;
|
||||
let c = col.checked_sub(cursor.col_offset)?;
|
||||
state.grid.cell(r, c)
|
||||
}
|
||||
|
||||
/// `EnterEditAtCursor`: re-open the editor where the advance landed, unless `i`
|
||||
/// would decline that cell. The host asks `uneditable`; this asks the same id.
|
||||
fn reopened_at(state: &ClientState, row: usize, col: usize, mode: AppMode) -> Vec<Command> {
|
||||
match declined_at(state, row, col) {
|
||||
Some(refusal) => vec![Command::SetStatus(refusal)],
|
||||
None => opened_on(seed_at(state, row, col), mode),
|
||||
match (declined_at(state, row, col), unprojected(state, row, col)) {
|
||||
(Some(refusal), _) => vec![Command::SetStatus(refusal)],
|
||||
(None, true) => awaiting_seed(mode),
|
||||
(None, false) => opened_on(seed_at(state, row, col), mode),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1065,6 +1076,21 @@ fn projected_at(state: &ClientState, row: usize, col: usize) -> Option<&crate::C
|
||||
state.grid.cell(r, c)
|
||||
}
|
||||
|
||||
/// Whether `(row, col)` is a cell of the grid that the window the cache holds
|
||||
/// does not cover: unknown, never the cell at the same cache index (7mro).
|
||||
pub(crate) fn unprojected(state: &ClientState, row: usize, col: usize) -> bool {
|
||||
let GridExtent { rows, cols } = state.grid.extent;
|
||||
let outside = |frame: &crate::AxisFrame, at: usize| {
|
||||
!(frame.first..frame.first + frame.len()).contains(&at)
|
||||
};
|
||||
row < rows
|
||||
&& col < cols
|
||||
&& (outside(&state.grid.row_frame, row) || outside(&state.grid.col_frame, col))
|
||||
}
|
||||
|
||||
/// What a cell op says while its cell is still unknown to this client.
|
||||
const NOT_PROJECTED: &str = "waiting for the host to show this cell";
|
||||
|
||||
/// What the host says of an aggregate, the fold of a collapsed group included.
|
||||
const AGGREGATE_DECLINE: &str =
|
||||
"Cannot write to an aggregate — it combines several records, so there is no one cell to write";
|
||||
@@ -1081,6 +1107,9 @@ pub struct OpenedEditor {
|
||||
pub seed: String,
|
||||
/// Whether the user has edited the buffer since it opened (improvise-2054).
|
||||
pub touched: bool,
|
||||
/// The seed is still to come, from a cell the host has not projected yet
|
||||
/// (improvise-7mro). Not `serde(default)`: `false` is a claim.
|
||||
pub awaiting: bool,
|
||||
}
|
||||
|
||||
impl OpenedEditor {
|
||||
@@ -1089,6 +1118,16 @@ impl OpenedEditor {
|
||||
Self {
|
||||
seed,
|
||||
touched: false,
|
||||
awaiting: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// An editor opened before its seed is known: empty until it settles.
|
||||
pub fn awaiting() -> Self {
|
||||
Self {
|
||||
seed: String::new(),
|
||||
touched: false,
|
||||
awaiting: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2232,6 +2232,7 @@ fn enter_on_an_editor_over_a_declined_cell_declines_out_loud_on_both_ends() {
|
||||
app.view_state.opened_editor = Some(improvise_protocol::OpenedEditor {
|
||||
seed: String::new(),
|
||||
touched: true,
|
||||
awaiting: false,
|
||||
});
|
||||
let before = document_text(&app);
|
||||
let mut client = client_ready_to_edit(&app, (6, 6));
|
||||
|
||||
Reference in New Issue
Block a user