mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-30 21:56:53 -06:00
Formula pointing: Ctrl+arrow and Ctrl+Shift+arrow jump to the edge of the data
While a formula reference is being pointed from the keyboard, Ctrl+arrow (⌘arrow on a Mac) moved the pointed cell by one, and Ctrl+Shift+arrow extended the pointed range by one cell, so `=COUNTIF(`, a click on the top of a column and ⌘⇧↓ pointed at two cells instead of the column of data. Both now go to the edge of the data region, as the same keys do on the selection outside an edit: `point_move` takes `jump` and uses the engine's `selection::jump`, which Ctrl+arrow on the grid already uses, so the two agree (last filled cell of a run, else the next filled cell, else the sheet edge). A pointed range that reaches the sheet edge is written as whole columns or rows (`A:B`), as Excel writes it. The editor used to read Shift from `i.modifiers`, the modifier state when the frame's input was collected; an arrow pressed and released within one frame (a control client's `ui.key`, an automation tool's keystroke) had already lost it. It now reads the modifiers carried by the arrow's own key event, as the grid's keyboard handler does. Test: ctrl_arrows_in_point_mode_jump_to_the_edge_of_the_data (down to the last filled cell, right across the data, down to the sheet edge, Ctrl+Up without Shift to the next filled cell, then a plain arrow). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
6ba1e19857
commit
57e40d2d14
@@ -518,9 +518,23 @@ fn editor_arrow_keys(
|
||||
ed: &mut crate::editor::EditState,
|
||||
mut action: Option<EditorAction>,
|
||||
) -> (Option<EditorAction>, bool) {
|
||||
let (up, down, left, right, shift) = ui.input(|i| {
|
||||
(i.key_pressed(Key::ArrowUp), i.key_pressed(Key::ArrowDown), i.key_pressed(Key::ArrowLeft), i.key_pressed(Key::ArrowRight), i.modifiers.shift)
|
||||
// The modifiers come with the key event: an arrow pressed and released within one frame (a
|
||||
// control client's or an automation tool's keystroke) has already lost them from `i.modifiers`.
|
||||
let arrow = ui.input(|i| {
|
||||
i.events.iter().find_map(|e| match e {
|
||||
egui::Event::Key { key: key @ (Key::ArrowUp | Key::ArrowDown | Key::ArrowLeft | Key::ArrowRight), pressed: true, modifiers, .. } => {
|
||||
Some((*key, *modifiers))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
});
|
||||
let (up, down, left, right) = (
|
||||
arrow.is_some_and(|(k, _)| k == Key::ArrowUp),
|
||||
arrow.is_some_and(|(k, _)| k == Key::ArrowDown),
|
||||
arrow.is_some_and(|(k, _)| k == Key::ArrowLeft),
|
||||
arrow.is_some_and(|(k, _)| k == Key::ArrowRight),
|
||||
);
|
||||
let (shift, jump) = arrow.map_or((false, false), |(_, m)| (m.shift, m.command));
|
||||
if (up || down) && !ed.autocomplete.is_empty() {
|
||||
ui.input_mut(|i| {
|
||||
i.consume_key(egui::Modifiers::NONE, Key::ArrowUp);
|
||||
@@ -545,13 +559,18 @@ fn editor_arrow_keys(
|
||||
let rtl = app.session.active().and_then(|d| d.wb.active()).is_some_and(|s| s.right_to_left);
|
||||
let dc = if rtl { -dc } else { dc };
|
||||
if ed.can_point() {
|
||||
// Ctrl+arrow (⌘arrow) jumps the pointed cell to the edge of its data, Ctrl+Shift+arrow
|
||||
// extends the pointed range there, as the same keys do on the selection.
|
||||
ui.input_mut(|i| {
|
||||
for key in [Key::ArrowUp, Key::ArrowDown, Key::ArrowLeft, Key::ArrowRight] {
|
||||
i.consume_key(egui::Modifiers::NONE, key);
|
||||
i.consume_key(egui::Modifiers::SHIFT, key);
|
||||
for modifiers in
|
||||
[egui::Modifiers::NONE, egui::Modifiers::SHIFT, egui::Modifiers::COMMAND, egui::Modifiers::COMMAND | egui::Modifiers::SHIFT]
|
||||
{
|
||||
i.consume_key(modifiers, key);
|
||||
}
|
||||
}
|
||||
});
|
||||
crate::grid::point_move(app, ed, dr, dc, shift);
|
||||
crate::grid::point_move(app, ed, dr, dc, shift, jump);
|
||||
(action, false)
|
||||
} else {
|
||||
action = Some((dr, dc, false, false));
|
||||
|
||||
@@ -1014,11 +1014,13 @@ pub fn follow_link(app: &mut SheetApp, target: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Used by `EditState` users to keep the active cell visible while pointing.
|
||||
pub fn point_move(app: &mut SheetApp, ed: &mut EditState, dr: i64, dc: i64, extend: bool) {
|
||||
/// Moves the pointed cell one step, or with `jump` (Ctrl+arrow) to the edge of its data region;
|
||||
/// `extend` (Shift) moves the far end of the pointed range instead. Keeps the pointed cell visible.
|
||||
pub fn point_move(app: &mut SheetApp, ed: &mut EditState, dr: i64, dc: i64, extend: bool, jump: bool) {
|
||||
let Some(d) = app.session.active() else { return };
|
||||
let Some(sheet) = d.wb.active() else { return };
|
||||
let from = ed.point_cell.unwrap_or(ed.cell);
|
||||
let to = from.offset_clamped(dr, dc);
|
||||
let to = if jump { gridcraft_engine::selection::jump(sheet, from, dr.signum(), dc.signum()) } else { from.offset_clamped(dr, dc) };
|
||||
let anchor = if extend {
|
||||
// The anchor is the corner opposite the moving end: the written range is normalized, so
|
||||
// after extending up or left its first cell is the moving end, not the anchor.
|
||||
|
||||
@@ -331,6 +331,33 @@ fn shift_arrows_in_point_mode_extend_from_the_pointed_cell() {
|
||||
assert_eq!(edited(&h).as_deref(), Some("=SUM(A8:A10"), "Shift+Down shrinks back towards the anchor");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ctrl_arrows_in_point_mode_jump_to_the_edge_of_the_data() {
|
||||
// Typing `=COUNTIF(`, clicking the top of a column and pressing Ctrl+Shift+Down (⌘⇧↓) points
|
||||
// at the whole column of data, as the same keys select it outside an edit.
|
||||
let mut s = blank();
|
||||
s.execute("range.setValues", json!({"range": "A1:B5", "values": [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]})).unwrap();
|
||||
s.execute("selection.set", json!({"cell": "D1"})).unwrap();
|
||||
let mut h = harness(s);
|
||||
text(&mut h, "=COUNTIF(");
|
||||
click_cell(&mut h, "A1");
|
||||
let edited = |h: &egui_kittest::Harness<'static, SheetApp>| h.state().editor.as_ref().map(|e| e.text.clone());
|
||||
assert_eq!(edited(&h).as_deref(), Some("=COUNTIF(A1"));
|
||||
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::COMMAND | Modifiers::SHIFT));
|
||||
key(&mut h, Key::ArrowDown, Modifiers::COMMAND | Modifiers::SHIFT);
|
||||
assert_eq!(edited(&h).as_deref(), Some("=COUNTIF(A1:A5"), "Ctrl+Shift+Down extends to the last filled cell");
|
||||
key(&mut h, Key::ArrowRight, Modifiers::COMMAND | Modifiers::SHIFT);
|
||||
assert_eq!(edited(&h).as_deref(), Some("=COUNTIF(A1:B5"), "Ctrl+Shift+Right extends the moving end across the data");
|
||||
key(&mut h, Key::ArrowDown, Modifiers::COMMAND | Modifiers::SHIFT);
|
||||
assert_eq!(edited(&h).as_deref(), Some("=COUNTIF(A:B"), "past the data, Ctrl+Shift+Down reaches the sheet edge (written as whole columns)");
|
||||
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::COMMAND));
|
||||
key(&mut h, Key::ArrowUp, Modifiers::COMMAND);
|
||||
assert_eq!(edited(&h).as_deref(), Some("=COUNTIF(B5"), "Ctrl+Up without Shift points at the next filled cell");
|
||||
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::NONE));
|
||||
key(&mut h, Key::ArrowUp, Modifiers::NONE);
|
||||
assert_eq!(edited(&h).as_deref(), Some("=COUNTIF(B4"), "a plain arrow still moves one cell");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shortcuts_format_and_extend() {
|
||||
let mut s = blank();
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
# UI parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-25 · **Change:** minor (AutoSum over a selected row puts its total to the right; earlier: macro interface on the Automate tab; ribbon catalog count 276 / 292) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (Ctrl+arrow and Ctrl+Shift+arrow while pointing a formula reference; earlier: 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
|
||||
@@ -195,6 +195,7 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12.
|
||||
| 2026-10-24 | minor | Macro step editor: step list with breakpoints, drag to reorder and a command picker, an inspector with reference boxes, a debug bar and grid overlays |
|
||||
| 2026-10-24 | minor | Automate › Macros group (Record Macro, Use Relative References, Run Macro), recording status in the status bar, Macros dialog with macros beside scripts, Record Macro dialog, Ctrl+Shift+letter macro shortcuts; View › Macros keytips open the dialogs |
|
||||
| 2026-10-24 | minor | Ribbon catalog count 275 / 291 → 276 / 292 (View › Macros › Record Macro) |
|
||||
| 2026-10-29 | minor | Formula pointing from the keyboard: Ctrl+arrow (⌘arrow) moves the pointed cell to the edge of its data and Ctrl+Shift+arrow (⌘⇧arrow) extends the pointed range there, as the same keys move and extend the selection, so typing `=COUNTIF(`, clicking the top of a column and pressing ⌘⇧↓ points at the whole column of data; the keys used to move or extend by one cell. A range reaching the sheet edge is written as whole columns or rows (`A:B`). The editor reads the modifiers of the arrow's own key event, so a keystroke pressed and released within one frame (a control client's `ui.key`, an automation tool) keeps its Shift and Ctrl |
|
||||
| 2026-10-24 | minor | Go To lists the named ranges on every sheet, as Excel's does ("The Go to popup window shows named ranges on every worksheet in your workbook"): a name scoped to another sheet appears as `Sheet2!Name`, and hidden names, constants and formulas are left out. It keeps the references entered (newest first, ten at most), and a double-click on an entry goes there. The Reference box and `edit.goTo` take several names or references separated by commas (`Price, Type`, `B14:C22,F19:G30,H21:H29`) and select them all; `edit.goTo {list: true}` returns the list |
|
||||
| 2026-10-24 | minor | Right-clicking the status bar opens a menu of its statistics, as in Excel ("If you want to customize the status bar, right-click it, and then click the options that you want"): Average, Count and Sum are on by default, and Numerical Count, Minimum and Maximum are new and off by default. The choice is a preference (`view.statusBar`), kept in `prefs.json` |
|
||||
| 2026-10-24 | minor | Ctrl+Shift+U (Control+Shift+U on a Mac) expands or collapses the formula bar, as in Excel ("Expand or collapse the formula bar. Ctrl+Shift+U"), on the grid and in the cell editor or formula bar while editing. On Windows and Linux it used to underline the selection, as Ctrl+U does. The command is `view.expandFormulaBar` (`{on?}`, toggles without it) |
|
||||
|
||||
Reference in New Issue
Block a user