#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
+10 -1
View File
@@ -36,7 +36,7 @@ use go_to::go_to_special;
use go_to::{GO_TO_MAX_AREAS, GO_TO_RECENT, reference_parts};
use paste_special::paste_special;
pub(crate) use paste_special::{rewrite_all_formulas, rewrite_cell_links, rewrite_python_refs};
use selection::{select_all, select_col, select_region, select_row, selection_move, selection_next, selection_set};
use selection::{select_all, select_col, select_region, select_row, selection_move, selection_next, selection_next_corner, selection_set};
pub fn specs() -> Vec<CommandSpec> {
vec![
@@ -62,6 +62,15 @@ pub fn specs() -> Vec<CommandSpec> {
cmd!(noundo "selection.set", "Select", [], None, "{range: \"A1:B2,D4\" | cell, active?: \"A1\", sheet?}", has_doc, selection_set),
cmd!(noundo "selection.move", "Move Selection", [], None, "{dr, dc, extend?: bool, jump?: bool (Ctrl+arrow), page?: bool}", has_doc, selection_move),
cmd!(noundo "selection.next", "Next Cell in Selection", [], None, "{forward?: true, byRow?: false} (Enter/Tab inside a selection)", has_doc, selection_next),
cmd!(
noundo "selection.nextCorner",
"Next Corner of Selection",
[],
Some("Ctrl+."),
"{} (the active cell moves clockwise to the next corner of the selected area, which stays selected)",
has_doc,
selection_next_corner
),
cmd!(noundo "edit.selectAll", "Select All", ["Edit"], Some("Cmd+A"), "{} (current region first, then the whole sheet)", has_doc, select_all),
cmd!(noundo "selection.currentRegion", "Select Current Region", [], Some("Ctrl+Shift+8"), "{}", has_doc, select_region),
cmd!(noundo "selection.row", "Select Entire Row", [], Some("Shift+Space"), "{}", has_doc, select_row),
+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");
}
+17
View File
@@ -128,6 +128,23 @@ impl Selection {
}
}
impl Selection {
/// Ctrl+. (Next Corner of Selection): the active cell moves clockwise to the next corner of
/// the current area (top-left, top-right, bottom-right, bottom-left) and becomes the anchor,
/// so Shift+arrows then move the opposite corner. The area stays selected. From a cell that
/// isn't a corner the next corner is the top-left one; a single cell stays where it is.
pub fn next_corner(&mut self) {
let r = self.current();
let corners = [r.start, CellRef::new(r.start.row, r.end.col), r.end, CellRef::new(r.end.row, r.start.col)];
let next = match corners.iter().position(|c| *c == self.active) {
Some(i) => (1..=4).filter_map(|k| corners.get((i + k) % 4)).copied().find(|c| *c != self.active).unwrap_or(self.active),
None => r.start,
};
self.active = next;
self.anchor = next;
}
}
/// Ctrl+arrow: jump to the edge of the current data region (Excel's End mode).
pub fn jump(sheet: &Sheet, from: CellRef, dr: i64, dc: i64) -> CellRef {
if dr == 0 && dc == 0 {
+2
View File
@@ -115,6 +115,8 @@ fn lookup_number_formats(ctx: LookupContext) -> Option<Shortcut> {
Num7 if ctrl && s => borders("outside"),
Minus if ctrl && s => borders("none"),
Num8 if ctrl && s => run("selection.currentRegion"),
// Ctrl+. (Control on a Mac too): Next Corner of Selection.
Period if ctrl && !s => run("selection.nextCorner"),
Semicolon if ctrl && s => Shortcut::Time,
Quote if ctrl && s => Shortcut::ValueAbove,
Quote if ctrl => Shortcut::FormulaAbove,
@@ -100,6 +100,11 @@ fn windows_and_mac_variants() {
assert_eq!(lookup(Key::ArrowLeft, Some(Key::Num4), alt_shift, false), run("data.ungroup"));
assert_eq!(lookup(Key::PageDown, Some(Key::Num3), Modifiers::ALT, false), Some(Shortcut::ScreenRight));
assert_eq!(lookup(Key::PageDown, Some(Key::Num3), CTRL_SHIFT, false), None);
// Ctrl+. is Next Corner of Selection, with Control on a Mac too (#1442); Cmd+Shift+. stays
// Increase Font Size.
assert_eq!(lookup(Key::Period, Some(Key::Period), cmd, false), run("selection.nextCorner"));
assert_eq!(lookup(Key::Period, Some(Key::Period), ctrl, true), run("selection.nextCorner"));
assert_eq!(lookup(Key::Period, Some(Key::Period), Modifiers { shift: true, ..mac_cmd }, true), run("home.increaseFontSize"));
// Plain keys stay with the grid (typing, navigation).
assert_eq!(lookup(Key::A, Some(Key::A), Modifiers::NONE, false), None);
assert_eq!(lookup(Key::F2, Some(Key::F2), Modifiers::NONE, false), None);
+3 -1
View File
@@ -1,6 +1,6 @@
# UI parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-02 · **Change:** minor (RefEdit reference boxes in dialogs: Goal Seek, Solver and Data Analysis use them, the arrow keys select while a dialog is collapsed and F4 cycles a reference's anchors; earlier: Python cells: Python mode in the formula bar; earlier: Insert › Equation and the equation editor; earlier: every floating object selects, resizes from eight handles and deletes with the keyboard; earlier: Formulas › Error Checking › Trace Error; trace arrows stay through a zoom (#3097); earlier: Page Setup sets the page order, Down, then over or Over, then down, which printing, the print preview, Page Break Preview and Page Layout view follow; earlier: Formulas › Function Library as labelled buttons with category menus; Recently Used and the Insert Function categories; Esc leaves a File-page text field before closing the view; earlier: checkbox cells print as boxes; Ctrl+arrow and Ctrl+Shift+arrow while pointing a formula reference; editing a hyperlink loads its existing fields; AutoSum over a selected row puts its total to the right; ribbon catalog count 276 / 292) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-03 · **Change:** minor (Ctrl+. Next Corner of Selection; earlier: RefEdit reference boxes in dialogs: Goal Seek, Solver and Data Analysis use them, the arrow keys select while a dialog is collapsed and F4 cycles a reference's anchors; earlier: Python cells: Python mode in the formula bar; earlier: Insert › Equation and the equation editor; earlier: every floating object selects, resizes from eight handles and deletes with the keyboard; earlier: Formulas › Error Checking › Trace Error; trace arrows stay through a zoom (#3097); earlier: Page Setup sets the page order, Down, then over or Over, then down, which printing, the print preview, Page Break Preview and Page Layout view follow; earlier: Formulas › Function Library as labelled buttons with category menus; Recently Used and the Insert Function categories; Esc leaves a File-page text field before closing the view; earlier: checkbox cells print as boxes; Ctrl+arrow and Ctrl+Shift+arrow while pointing a formula reference; editing a hyperlink loads its existing fields; AutoSum over a selected row puts its total to the right; ribbon catalog count 276 / 292) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
Interaction, controls and feel, against Excel. Part of [target-app-parity.md](target-app-parity.md).
**UI/UX fidelity: ~55% ready, 50–80 h** (estimated). Ribbon command coverage is measured
@@ -125,6 +125,7 @@ otherwise. Number-format and border keys match the physical key (Ctrl+Shift+4, w
| — | Cmd+Shift+H, Cmd+Shift+R, Cmd+Shift+K / J, Control+L, Control+U | replace, sort, group / ungroup, Define Name, edit cell | **added** |
| Alt+Shift+Right / Left | Option+Right / Left is next/previous sheet | group / ungroup | **added** |
| Ctrl+Shift+* (Ctrl+Shift+8), Ctrl+Shift+Space | Cmd+Shift+Space | current region, select all | **added** |
| Ctrl+. | Control+. | Next Corner of Selection: the active cell goes clockwise to the next corner and anchors there (`selection.nextCorner`, #1442) | **added** |
| Ctrl+Shift+Home / End, Shift+Home | same | extend to A1 / last used cell / row start | **added** |
| Ctrl+P | Cmd+P | print | **added** |
| Ctrl+Shift+> / < | Cmd+Shift+> / < | font size | **added** |
@@ -237,6 +238,7 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12.
| Date | Change | Summary |
|---|---|---|
| 2026-11-03 | minor | Ctrl+. (Control+. on a Mac): Next Corner of Selection, `selection.nextCorner`: the active cell moves clockwise to the next corner of the selected area, which stays selected, and becomes the anchor, as in Excel (#1442) |
| 2026-11-02 | minor | RefEdit reference boxes in dialogs: pick a range on the grid, collapse the dialog while selecting; Goal Seek, Solver (with the Model Range of Load/Save) and Data Analysis use them; while collapsed the arrow keys move the selection and Shift+arrow extends it, F4 cycles a reference's anchors, a click elsewhere keeps the box picking, a dialog opened over a picking one puts the selection back, OK stays on the sheet its command showed, and scripted cancel and confirm end a pick like the buttons; the dialog's sheet is followed through the recorded undo steps, so a sheet deleted and recreated under its name is not taken for it and an undo of the deletion brings it back; a box the dialog stops showing is no longer picked into |
| 2026-11-02 | minor | Python mode edits a cell's code in the formula bar's code editor (Ctrl+Alt+Shift+P, `=PY` then Tab, editing a Python cell), with the PY badge beside fx; its keys apply in input order, also several in one frame, and Ctrl+Enter commits through `python.set` (empty code clears the cell), keeping the code and showing the command's message when the code is refused |
| 2026-11-02 | minor | Insert › Symbols: Equation split button (access keys N E), a gallery of examples, and an equation editor with quick-insert buttons, the error underlined at its position and a live preview; double-clicking an equation edits it |