Merge branch 'main' into worktree-improvise-ewi-formula-crate

# Conflicts:
#	TAGS
This commit is contained in:
Edward Langley
2026-04-15 22:47:51 -07:00
9 changed files with 9970 additions and 433 deletions

View File

@ -124,10 +124,19 @@ impl Effect for RemoveFormula {
/// Re-enter edit mode by reading the cell value at the current cursor.
/// Used after commit+advance to continue data entry.
///
/// `target_mode` is supplied by the caller (keymap binding via
/// `EnterEditAtCursorCmd`, or `CommitAndAdvance` from its own `edit_mode`
/// field). The effect itself never inspects `app.mode` — the mode is decided
/// statically by whoever invoked us.
#[derive(Debug)]
pub struct EnterEditAtCursor;
pub struct EnterEditAtCursor {
pub target_mode: AppMode,
}
impl Effect for EnterEditAtCursor {
fn apply(&self, app: &mut App) {
// Layout may be stale relative to prior effects in this batch (e.g.
// AddRecordRow added a row); rebuild before reading display_value.
app.rebuild_layout();
let ctx = app.cmd_context(
crossterm::event::KeyCode::Null,
@ -136,11 +145,7 @@ impl Effect for EnterEditAtCursor {
let value = ctx.display_value.clone();
drop(ctx);
app.buffers.insert("edit".to_string(), value);
app.mode = if app.mode.is_records() {
AppMode::records_editing()
} else {
AppMode::editing()
};
app.mode = self.target_mode.clone();
}
}
@ -1288,6 +1293,41 @@ mod tests {
assert_eq!(app.mode, AppMode::Help);
}
/// `EnterEditAtCursor` must use its `target_mode` field, *not* whatever
/// `app.mode` happens to be when applied. Previous implementation
/// branched on `app.mode.is_records()` — the parameterized version
/// trusts the caller (keymap or composing command).
#[test]
fn enter_edit_at_cursor_uses_target_mode_not_app_mode() {
let mut app = test_app();
// App starts in Normal mode — but caller has decided we want
// RecordsEditing (e.g. records-mode `o` sequence).
assert_eq!(app.mode, AppMode::Normal);
EnterEditAtCursor {
target_mode: AppMode::records_editing(),
}
.apply(&mut app);
assert!(
matches!(app.mode, AppMode::RecordsEditing { .. }),
"Expected RecordsEditing, got {:?}",
app.mode
);
// Same effect with editing target — should land in plain Editing
// even if app.mode was something else.
let mut app2 = test_app();
app2.mode = AppMode::RecordsNormal;
EnterEditAtCursor {
target_mode: AppMode::editing(),
}
.apply(&mut app2);
assert!(
matches!(app2.mode, AppMode::Editing { .. }),
"Expected Editing, got {:?}",
app2.mode
);
}
/// SetBuffer with empty value clears the buffer (used by clear-buffer command
/// in keymap sequences after commit).
#[test]