fix: navigation bounds and stale state after model load
- app.rs: scroll_rows (Ctrl+D/U) now clamps to the cross-product row count and follows the viewport, matching move_selection's behaviour. Previously it could push selected past the last row, causing selected_cell_key to return None and silently ignoring edits. - model.rs: add normalize_view_state() which resets row/col offsets to zero on all views. - main.rs, dispatch.rs, app.rs: call normalize_view_state() after every model replacement (initial load, :Load command, wizard import) so stale offsets from a previous session can't hide the grid. - app.rs: clamp formula_cursor to the current formula list length at the top of handle_formula_panel_key so a model reload with fewer formulas can't leave the cursor pointing past the end. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -594,6 +594,11 @@ impl App {
|
||||
// ── Panel key handlers ───────────────────────────────────────────────────
|
||||
|
||||
fn handle_formula_panel_key(&mut self, key: KeyEvent) -> Result<()> {
|
||||
// Clamp cursor in case the formula list shrank since it was last set.
|
||||
let flen = self.model.formulas.len();
|
||||
if flen == 0 { self.formula_cursor = 0; }
|
||||
else { self.formula_cursor = self.formula_cursor.min(flen - 1); }
|
||||
|
||||
match key.code {
|
||||
KeyCode::Esc | KeyCode::Tab => { self.mode = AppMode::Normal; }
|
||||
KeyCode::Char('a') | KeyCode::Char('n') | KeyCode::Char('o') => {
|
||||
@ -882,8 +887,10 @@ impl App {
|
||||
KeyCode::Backspace => wizard.pop_name_char(),
|
||||
KeyCode::Enter => {
|
||||
match wizard.build_model() {
|
||||
Ok(model) => {
|
||||
Ok(mut model) => {
|
||||
model.normalize_view_state();
|
||||
self.model = model;
|
||||
self.formula_cursor = 0;
|
||||
self.dirty = true;
|
||||
self.status_msg = "Import successful! Press :w <path> to save.".to_string();
|
||||
self.mode = AppMode::Normal;
|
||||
@ -956,9 +963,16 @@ impl App {
|
||||
}
|
||||
|
||||
fn scroll_rows(&mut self, delta: i32) {
|
||||
let row_max = {
|
||||
let view = match self.model.active_view() { Some(v) => v, None => return };
|
||||
let row_cats: Vec<String> = view.categories_on(Axis::Row).into_iter().map(String::from).collect();
|
||||
cross_product_strs(&row_cats, &self.model, view).len().saturating_sub(1)
|
||||
};
|
||||
if let Some(view) = self.model.active_view_mut() {
|
||||
let new_r = (view.selected.0 as i32 + delta).max(0) as usize;
|
||||
view.selected.0 = new_r;
|
||||
let nr = (view.selected.0 as i32 + delta).clamp(0, row_max as i32) as usize;
|
||||
view.selected.0 = nr;
|
||||
if nr < view.row_offset { view.row_offset = nr; }
|
||||
if nr >= view.row_offset + 20 { view.row_offset = nr.saturating_sub(19); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user