#1442: Ctrl+. moves to the next corner of the selection

Excel's Next Corner of Selection (Ctrl+., Control+. on a Mac) moves the
active cell clockwise to the next corner of the selected area (top-left,
top-right, bottom-right, bottom-left) and keeps the area selected; the
new active cell is also the anchor, so Shift+arrows then move the
opposite corner. GridCraft had no such command.

New command selection.nextCorner (Selection::next_corner) bound to
Ctrl+. on every platform. A one-row or one-column area goes end to end;
a single cell stays where it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hector Villarreal
2026-11-03 00:44:15 -06:00
co-authored by Claude Opus 5.5
parent 178f3eb745
commit 8b2550c9cf
7 changed files with 69 additions and 2 deletions
+6
View File
@@ -137,6 +137,12 @@ pub(super) fn selection_next(s: &mut Session, p: &Json) -> Result<Json> {
Ok(json!({"active": d.selection.active.a1()}))
}
pub(super) fn selection_next_corner(s: &mut Session, _: &Json) -> Result<Json> {
let d = s.doc_mut()?;
d.selection.next_corner();
Ok(json!({"active": d.selection.active.a1(), "selection": d.selection.a1()}))
}
pub(super) fn select_all(s: &mut Session, _: &Json) -> Result<Json> {
let d = s.doc_mut()?;
let Some(sheet) = d.wb.active() else { return Err(EngineError::NoDocument) };
+26
View File
@@ -371,3 +371,29 @@ fn replace_case_insensitive_keeps_text_when_folding_moves_boundaries() {
let sh = s.doc().unwrap().wb.active().unwrap();
assert_eq!(sh.value(CellRef::new(0, 0)), Value::text(text));
}
#[test]
fn next_corner_of_selection_goes_clockwise_and_anchors_there() {
// #1442, as Excel does it: B2 → D2 → D4 → B4 → B2, the area stays selected, and Shift+arrows
// then move the corner opposite the new active cell.
let mut s = Session::new();
s.new_workbook();
s.execute("selection.set", json!({"range": "B2:D4", "active": "B2"})).unwrap();
let mut seen = Vec::new();
for _ in 0..4 {
let r = s.execute("selection.nextCorner", json!({})).unwrap();
assert_eq!(r["selection"], "B2:D4");
seen.push(r["active"].as_str().unwrap().to_string());
}
assert_eq!(seen, ["D2", "D4", "B4", "B2"]);
s.execute("selection.nextCorner", json!({})).unwrap();
s.execute("selection.nextCorner", json!({})).unwrap();
s.execute("selection.move", json!({"dr": 0, "dc": 1, "extend": true})).unwrap();
assert_eq!(s.doc().unwrap().selection.current().a1(), "C2:D4");
// One row goes end to end; a single cell stays.
s.execute("selection.set", json!({"range": "B2:D2", "active": "B2"})).unwrap();
assert_eq!(s.execute("selection.nextCorner", json!({})).unwrap()["active"], "D2");
assert_eq!(s.execute("selection.nextCorner", json!({})).unwrap()["active"], "B2");
s.execute("selection.set", json!({"range": "C5"})).unwrap();
assert_eq!(s.execute("selection.nextCorner", json!({})).unwrap()["active"], "C5");
}