diff --git a/crates/ui-egui/src/grid.rs b/crates/ui-egui/src/grid.rs index b485104b1..09690eb94 100644 --- a/crates/ui-egui/src/grid.rs +++ b/crates/ui-egui/src/grid.rs @@ -701,9 +701,14 @@ pub fn show(app: &mut SheetApp, ui: &mut egui::Ui) { // Keep the active cell (or the moving end of an extension) visible after keyboard moves. if app.grid.ensure_visible { app.grid.ensure_visible = false; - let cell = if std::mem::take(&mut app.grid.reveal_moving_end) { sel.moving_end() } else { sel.active }; + let moving = std::mem::take(&mut app.grid.reveal_moving_end); + let cell = if moving { sel.moving_end() } else { sel.active }; let from = app.grid.reveal_from.take(); let target = app.editor.as_ref().and_then(|e| e.point_cell).unwrap_or(cell); + // A whole column's moving end sits on the sheet's last row (a whole row's on its last + // column): extending it only moves across, so the view keeps its place the other way. + let extended = (moving && target == cell).then(|| sel.current()); + let before = view.scroll; if paper { crate::paper_view::ensure_covers(app, &wb, si, target); geo = make_geo(app, view.scroll); @@ -731,6 +736,12 @@ pub fn show(app: &mut SheetApp, ui: &mut egui::Ui) { } else if from.is_some_and(|f| f.col >= geo.fc) { view.scroll.x = 0.0; } + if extended.is_some_and(|r| r.is_full_cols()) { + view.scroll.y = before.y; + } + if extended.is_some_and(|r| r.is_full_rows()) { + view.scroll.x = before.x; + } view.scroll = view.scroll.max(egui::Vec2::ZERO); geo = make_geo(app, view.scroll); } diff --git a/crates/ui-egui/tests/it/selection_scroll.rs b/crates/ui-egui/tests/it/selection_scroll.rs index 9a78f9b9f..97f4d3c17 100644 --- a/crates/ui-egui/tests/it/selection_scroll.rs +++ b/crates/ui-egui/tests/it/selection_scroll.rs @@ -290,3 +290,47 @@ fn down_from_a_frozen_header_row_goes_to_row_2_at_the_top_from_far_down() { assert_eq!(scroll(&h).y, 0.0); assert!(visible(&h, "A2")); } + +#[test] +fn shift_arrows_on_a_whole_column_leave_the_rows_where_they_are() { + // A whole column's moving end is on the sheet's last row: Shift+Right from Ctrl+Space must + // widen it to the next columns without scrolling down to the bottom of the sheet. + let mut h = harness(); + h.state_mut().session.execute("selection.set", json!({"cell": "A100"})).unwrap(); + h.state_mut().grid.ensure_visible = true; + h.run_steps(2); + let down = scroll(&h).y; + assert!(down > 0.0 && visible(&h, "A100")); + h.state_mut().session.execute("selection.column", json!({})).unwrap(); + h.run_steps(2); + key(&mut h, Key::ArrowRight, Modifiers::SHIFT); + assert_eq!(h.state().session.active().unwrap().selection.current(), RangeRef::parse("A:B").unwrap()); + assert_eq!(scroll(&h).y, down, "the view stays on the rows it showed"); + // The columns still scroll to follow the extension. + for _ in 0..30 { + key(&mut h, Key::ArrowRight, Modifiers::SHIFT); + } + assert!(visible(&h, "AF100") && !visible(&h, "A100")); + assert_eq!(scroll(&h).y, down); +} + +#[test] +fn shift_arrows_on_a_whole_row_leave_the_columns_where_they_are() { + let mut h = harness(); + h.state_mut().session.execute("selection.set", json!({"cell": "CA1"})).unwrap(); + h.state_mut().grid.ensure_visible = true; + h.run_steps(2); + let right = scroll(&h).x; + assert!(right > 0.0 && visible(&h, "CA1")); + h.state_mut().session.execute("selection.row", json!({})).unwrap(); + h.run_steps(2); + key(&mut h, Key::ArrowDown, Modifiers::SHIFT); + assert_eq!(h.state().session.active().unwrap().selection.current(), RangeRef::parse("1:2").unwrap()); + assert_eq!(scroll(&h).x, right, "the view stays on the columns it showed"); + // The rows still scroll to follow the extension. + for _ in 0..60 { + key(&mut h, Key::ArrowDown, Modifiers::SHIFT); + } + assert!(visible(&h, "CA62") && !visible(&h, "CA1")); + assert_eq!(scroll(&h).x, right); +}