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:
@ -73,7 +73,10 @@ fn main() -> Result<()> {
|
|||||||
// Load or create model
|
// Load or create model
|
||||||
let mut model = if let Some(ref path) = file_path {
|
let mut model = if let Some(ref path) = file_path {
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
persistence::load(path).with_context(|| format!("Failed to load {}", path.display()))?
|
let mut m = persistence::load(path)
|
||||||
|
.with_context(|| format!("Failed to load {}", path.display()))?;
|
||||||
|
m.normalize_view_state();
|
||||||
|
m
|
||||||
} else {
|
} else {
|
||||||
let name = path.file_stem()
|
let name = path.file_stem()
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
|
|||||||
@ -594,6 +594,11 @@ impl App {
|
|||||||
// ── Panel key handlers ───────────────────────────────────────────────────
|
// ── Panel key handlers ───────────────────────────────────────────────────
|
||||||
|
|
||||||
fn handle_formula_panel_key(&mut self, key: KeyEvent) -> Result<()> {
|
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 {
|
match key.code {
|
||||||
KeyCode::Esc | KeyCode::Tab => { self.mode = AppMode::Normal; }
|
KeyCode::Esc | KeyCode::Tab => { self.mode = AppMode::Normal; }
|
||||||
KeyCode::Char('a') | KeyCode::Char('n') | KeyCode::Char('o') => {
|
KeyCode::Char('a') | KeyCode::Char('n') | KeyCode::Char('o') => {
|
||||||
@ -882,8 +887,10 @@ impl App {
|
|||||||
KeyCode::Backspace => wizard.pop_name_char(),
|
KeyCode::Backspace => wizard.pop_name_char(),
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
match wizard.build_model() {
|
match wizard.build_model() {
|
||||||
Ok(model) => {
|
Ok(mut model) => {
|
||||||
|
model.normalize_view_state();
|
||||||
self.model = model;
|
self.model = model;
|
||||||
|
self.formula_cursor = 0;
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
self.status_msg = "Import successful! Press :w <path> to save.".to_string();
|
self.status_msg = "Import successful! Press :w <path> to save.".to_string();
|
||||||
self.mode = AppMode::Normal;
|
self.mode = AppMode::Normal;
|
||||||
@ -956,9 +963,16 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_rows(&mut self, delta: i32) {
|
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() {
|
if let Some(view) = self.model.active_view_mut() {
|
||||||
let new_r = (view.selected.0 as i32 + delta).max(0) as usize;
|
let nr = (view.selected.0 as i32 + delta).clamp(0, row_max as i32) as usize;
|
||||||
view.selected.0 = new_r;
|
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