+31
-10
@@ -312,16 +312,37 @@ carries it or has to agree with it. The TUI colouring the ticket hoped for is
|
||||
still possible later, and would want the severity *on the message*; that is a
|
||||
separate change with a separate staleness problem.
|
||||
|
||||
**The classification is per writer, and it is not total.** Every refusal in
|
||||
`ui/effect.rs` and in `cmd/{cell,commit,fit,grid,mode,search,text_buffer}.rs`
|
||||
goes through `refuse`. `EditOrDrill`'s collapsed-group arm and the three
|
||||
spelled-paste declines in `cell.rs` (aggregate, coordinate column, inside a
|
||||
drill) joined them with improvise-i3o5. Those still written with `set_status`
|
||||
exit 0 as before, and are listed on that bead: three in `cmd/panel.rs`, and
|
||||
`command/keymap.rs::dispatch`'s arg-error arm. The sweeps that pin refusals
|
||||
revert `refuse(` calls, so they **cannot see a refusal written as
|
||||
`set_status`**. Finding one takes a read of every `set_status` on a decline
|
||||
path, and that is how these were found. `q` on a dirty document is deliberately left a
|
||||
**The classification is per writer.** Every refusal in `ui/effect.rs`, in
|
||||
`cmd/{cell,commit,fit,grid,mode,panel,search,text_buffer}.rs` and in
|
||||
`command/keymap.rs::dispatch` goes through `refuse`. The last of them were
|
||||
converted in two steps:
|
||||
- **improvise-i3o5:** `EditOrDrill`'s collapsed-group arm, and the three
|
||||
spelled-paste declines in `cell.rs` (aggregate, coordinate column, inside a
|
||||
drill).
|
||||
- **improvise-cwfj:** the three `cmd/panel.rs` declines, and `dispatch`'s
|
||||
arg-error arm.
|
||||
|
||||
The sweeps that pin refusals revert `refuse(` calls, so they **cannot see a
|
||||
refusal written as `set_status`**. Finding one takes a read of every
|
||||
`set_status` on a decline path, and that is how both batches were found.
|
||||
|
||||
**A bound key whose command will not build is a refusal** (the orchestrator's
|
||||
ruling, improvise-cwfj). The key asked for something that did not happen,
|
||||
which is this section's definition. So `dispatch` answers a constructor's
|
||||
`Err` with `refuse(e)` rather than `set_status(e)`. That arm is where every
|
||||
key-path decline lands, including `p`, `x` and `i` onto an aggregate, a fold,
|
||||
a coordinate column or inside a drill.
|
||||
|
||||
Before the conversion, the key path was checked for any reader of the flag:
|
||||
- `last_batch_refused` is read in production only by `Host::mutating_frame`,
|
||||
which handles frames and not keys, and by `main.rs`'s headless loop, which
|
||||
handles `:` lines and not keys.
|
||||
- `Refuse` is `ViewOnly`, like `SetStatus`, so the `Dirty` a key returns and
|
||||
the projections built from it are unchanged.
|
||||
- The browser lockstep suite compares `ViewState`, including `status_msg`,
|
||||
never the flag, and `Refuse` writes the same message.
|
||||
|
||||
The whole workspace stayed green with the change. `q` on a dirty document is deliberately left a
|
||||
report: headless, its text is false — the CLI saves anyway (improvise-ep5o).
|
||||
The two rivals udt2 named both stay green: a successful `:w` and a successful
|
||||
`fit-measure` report through `set_status` and exit 0.
|
||||
|
||||
@@ -6397,6 +6397,28 @@ runs `cargo test --workspace --no-fail-fast`. Four of four fail this test, and
|
||||
nothing else fails. None of the four needs an abort: each is the only effect its
|
||||
command emits, and `p`/`i` are single bindings, not sequences.
|
||||
|
||||
#### improvise-cwfj: the category panel's declines, and a bound key that will not build
|
||||
|
||||
Four more cases in `every_refusal_site_refuses_its_failure_and_only_its_failure`.
|
||||
The first three are `cycle-axis-at-cursor`, `open-item-add-at-cursor` and
|
||||
`delete-category-at-cursor`, each run with the panel cursor past the tree's last
|
||||
row (99). Each twin runs the same command at row 1, deliberately not row 0.
|
||||
|
||||
The fourth is `p` pressed through `App::handle_key` on an aggregate, so the
|
||||
paste constructor's `Err` reaches `command/keymap.rs::dispatch`. Its twin is `p`
|
||||
onto a stored record at (1, 1). The expected text is `AGGREGATE_REFUSAL`, which
|
||||
`dispatch` prefixes with the command name ("paste: …").
|
||||
|
||||
**Red at `bb5dfe5`**, one site at a time: each case failed with its own text and
|
||||
`refused=false` until its own site was converted.
|
||||
|
||||
**Per-site revert:** `/tmp/refuse-fu/sweep.py`, with `command/keymap.rs` added to
|
||||
its file list, reverts each of the four `refuse(` calls alone and runs
|
||||
`cargo test --workspace --no-fail-fast`. Four of four fail this test, and
|
||||
nothing else fails. The full suite stayed green with `dispatch` converted, which
|
||||
is the empirical half of the check that nothing relied on a failed key leaving
|
||||
`last_batch_refused` false.
|
||||
|
||||
### a_formula_rows_error_does_not_poison_the_pooled_total
|
||||
|
||||
improvise-b5dp, the user's ruling on the conflict between wm20's rulings (1) and
|
||||
|
||||
@@ -722,7 +722,7 @@ impl Cmd for CycleAxisAtCursor {
|
||||
.extend(cycled.map(|axis| effect::set_status(axis_change_status(&cat_name, axis))));
|
||||
effects
|
||||
} else {
|
||||
vec![effect::set_status(
|
||||
vec![effect::refuse(
|
||||
"Move cursor to a category header to change axis".to_string(),
|
||||
)]
|
||||
}
|
||||
@@ -740,7 +740,7 @@ impl Cmd for OpenItemAddAtCursor {
|
||||
if let Some(cat_name) = ctx.cat_at_cursor() {
|
||||
vec![effect::change_mode(AppMode::item_add(cat_name))]
|
||||
} else {
|
||||
vec![effect::set_status(
|
||||
vec![effect::refuse(
|
||||
"No category selected. Press n to add a category first.",
|
||||
)]
|
||||
}
|
||||
@@ -832,7 +832,7 @@ impl Cmd for DeleteCategoryAtCursor {
|
||||
effects.extend(retreat_from_deleted_item(ctx));
|
||||
effects
|
||||
}
|
||||
None => vec![effect::set_status("No category to delete")],
|
||||
None => vec![effect::refuse("No category to delete")],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ fn effects_of(
|
||||
Ok(acc)
|
||||
}),
|
||||
};
|
||||
Some(built.unwrap_or_else(|e| vec![crate::ui::effect::set_status(e)]))
|
||||
Some(built.unwrap_or_else(|e| vec![crate::ui::effect::refuse(e)]))
|
||||
}
|
||||
|
||||
/// The shared keymap table plus the registry that gives its names meaning.
|
||||
|
||||
@@ -3300,6 +3300,18 @@ mod tests {
|
||||
app
|
||||
}
|
||||
|
||||
/// One key through the keymap, as the TUI delivers it; true when it refused.
|
||||
fn press_key(app: &mut App, code: KeyCode) -> bool {
|
||||
app.handle_key(KeyEvent::new(code, KeyModifiers::NONE))
|
||||
.unwrap();
|
||||
app.last_batch_refused()
|
||||
}
|
||||
|
||||
fn panel_cursor_at(mut app: App, at: usize) -> App {
|
||||
app.view_state.cat_panel_cursor = at;
|
||||
app
|
||||
}
|
||||
|
||||
/// Storage holding one loadable document, `doc.improv`, and nothing else.
|
||||
fn with_a_document_to_load() -> App {
|
||||
let storage = crate::storage::MemoryFs::new();
|
||||
@@ -3609,6 +3621,43 @@ mod tests {
|
||||
|| yanked(records_model_with_two_rows()),
|
||||
|app| run_line(app, "paste _Index/1 _Dim/2"),
|
||||
),
|
||||
// improvise-cwfj: the category panel's three "nothing at the cursor"
|
||||
// declines, and a bound key whose command will not build.
|
||||
(
|
||||
"cycle-axis-at-cursor past the panel's last row",
|
||||
|| panel_cursor_at(two_col_model(), 99),
|
||||
|app| run_line(app, "cycle-axis-at-cursor"),
|
||||
"Move cursor to a category header",
|
||||
|| panel_cursor_at(two_col_model(), 1),
|
||||
|app| run_line(app, "cycle-axis-at-cursor"),
|
||||
),
|
||||
(
|
||||
"open-item-add-at-cursor past the panel's last row",
|
||||
|| panel_cursor_at(two_col_model(), 99),
|
||||
|app| run_line(app, "open-item-add-at-cursor"),
|
||||
"No category selected",
|
||||
|| panel_cursor_at(two_col_model(), 1),
|
||||
|app| run_line(app, "open-item-add-at-cursor"),
|
||||
),
|
||||
(
|
||||
"a bound key whose command will not build (p onto an aggregate)",
|
||||
|| yanked(aggregated_grid(true)),
|
||||
|app| press_key(app, KeyCode::Char('p')),
|
||||
crate::command::cmd::core::AGGREGATE_REFUSAL,
|
||||
|| yanked(two_col_model()),
|
||||
|app| {
|
||||
app.cursor_mut().selected = (1, 1);
|
||||
press_key(app, KeyCode::Char('p'))
|
||||
},
|
||||
),
|
||||
(
|
||||
"delete-category-at-cursor past the panel's last row",
|
||||
|| panel_cursor_at(two_col_model(), 99),
|
||||
|app| run_line(app, "delete-category-at-cursor"),
|
||||
"No category to delete",
|
||||
|| panel_cursor_at(two_col_model(), 1),
|
||||
|app| run_line(app, "delete-category-at-cursor"),
|
||||
),
|
||||
];
|
||||
for (what, fail_setup, fails, says, ok_setup, succeeds) in cases {
|
||||
let mut app = fail_setup();
|
||||
|
||||
Reference in New Issue
Block a user