mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-03 17:16:14 -06:00
#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:
co-authored by
Claude Opus 5.5
parent
178f3eb745
commit
8b2550c9cf
@@ -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),
|
||||
|
||||
@@ -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) };
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user