Merge pull request #395 from vangel76/fix/space-selection-shortcuts

Keyboard: Shift+Space and Ctrl+Space select without starting an edit (#338)

# Conflicts:
#	ROADMAP.md
#	crates/ui-egui/src/grid.rs
This commit is contained in:
Brandon Thomas 2026-10-11 15:43:44 -04:00
commit 12b46e6bbe
5 changed files with 49 additions and 2 deletions

View File

@ -135,6 +135,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in
| 2026-10-11 | Windows: DirectX 12 shaders compile with FXC, so a missing or stray `dxcompiler.dll` no longer stops the app at start (#339, #354) |
| 2026-10-11 | Merged cells draw their whole outline: right and bottom edges come from the last column's and last row's cells (#376) |
| 2026-10-11 | Shrink to fit draws too-wide text and numbers in a smaller font inside the cell or merged area (#375) |
| 2026-10-11 | Shift+Space and Ctrl+Space select the row / column without starting an edit; Control+Space on Mac (#338) |
| 2026-10-11 | Legacy Excel 97-2003 `.xls` (BIFF8) import with formulas, number formats, merges, sizes and names (#121) |
| 2026-10-11 | XLSX pass-through: custom XML, external links, connections, query tables, printer settings, form/ActiveX controls and OLE objects are kept on save (#330, #324) |
| 2026-10-11 | Data › Forecast › Forecast Sheet: a new sheet with the history, FORECAST.ETS forecast, confidence bounds, statistics and a chart (#322) |

View File

@ -2260,6 +2260,11 @@ fn keyboard(app: &mut SheetApp, ctx: &egui::Context, resp: &egui::Response, geo:
m.set_focus_lock_filter(resp.id, egui::EventFilter { tab: true, horizontal_arrows: true, vertical_arrows: true, escape: true })
});
let events = ctx.input(|i| i.events.clone());
// A shortcut key that also types a character (Shift+Space sends Text(" ")) must not start an edit.
let shortcut_pressed = events.iter().any(|e| {
matches!(e, egui::Event::Key { pressed: true, modifiers: m, key, .. }
if !m.alt && (m.ctrl || m.command || (m.shift && *key == egui::Key::Space)))
});
let page = ((geo.cells.height() / geo.z) / sh.default_row_height).floor().max(1.0) as i64;
for ev in events {
match ev {
@ -2270,6 +2275,7 @@ fn keyboard(app: &mut SheetApp, ctx: &egui::Context, resp: &egui::Response, geo:
app.copy_to_clipboard(ctx, "edit.cut");
}
egui::Event::Paste(text) => app.paste_external(text),
egui::Event::Text(_) if shortcut_pressed => {}
egui::Event::Text(text) if text.chars().all(|c| !c.is_control()) && !text.is_empty() => {
app.begin_edit(Some(text), false);
if let Some(ed) = app.editor.as_mut() {
@ -2338,8 +2344,6 @@ fn keyboard(app: &mut SheetApp, ctx: &egui::Context, resp: &egui::Response, geo:
app.session.format_painter = None;
}
Key::Space if !cmd && !shift && is_checkbox(sh, app) => app.run_or_alert("cell.toggleCheckbox", json!({})),
Key::Space if cmd && !shift => app.run_or_alert("selection.column", json!({})),
Key::Space if shift && !cmd => app.run_or_alert("selection.row", json!({})),
Key::F8 if m.alt => {
app.open_dialog("macros", json!({}));
}

View File

@ -108,6 +108,11 @@ pub fn lookup(key: Key, physical: Option<Key>, m: Modifiers, mac: bool) -> Optio
Period if cmd && s => run("home.increaseFontSize"),
Comma if cmd && s => run("home.decreaseFontSize"),
Space if cmd && s => run("edit.selectAll"),
// Shift+Space selects the row; Ctrl+Space (Windows) / Control+Space (Excel for Mac) the
// column. Cmd+Space stays free: it is the macOS Spotlight shortcut.
Space if plain && s => run("selection.row"),
Space if !mac && cmd && !s => run("selection.column"),
Space if mac && ctrl && !m.command && !s => run("selection.column"),
Home if cmd && s => Shortcut::ExtendToStart,
End if cmd && s => Shortcut::ExtendToEnd,
Home if plain && s => Shortcut::ExtendToRowStart,

View File

@ -106,3 +106,39 @@ fn windows_and_mac_variants() {
assert_eq!(lookup(Key::A, Some(Key::A), Modifiers::NONE, false), None);
assert_eq!(lookup(Key::F2, Some(Key::F2), Modifiers::NONE, false), None);
}
#[test]
fn space_selection_shortcuts_do_not_start_an_edit() {
let mut h = harness(book("B2"));
// Shift+Space: the key press, then the space the OS types with it.
let shift = Modifiers::SHIFT;
h.input_mut().events.push(Event::Key { key: Key::Space, physical_key: Some(Key::Space), pressed: true, repeat: false, modifiers: shift });
h.input_mut().events.push(Event::Text(" ".into()));
h.run_steps(2);
assert!(h.state().editor.is_none(), "Shift+Space started an edit");
assert_eq!(h.state().session.active().unwrap().selection.ranges[0].a1(), "2:2");
h.state_mut().session.run("selection.set", json!({"range": "B2"})).unwrap();
// Ctrl+Space selects the column, also without starting an edit.
h.input_mut().events.push(Event::Key { key: Key::Space, physical_key: Some(Key::Space), pressed: true, repeat: false, modifiers: CTRL });
h.input_mut().events.push(Event::Text(" ".into()));
h.run_steps(2);
assert!(h.state().editor.is_none());
assert_eq!(h.state().session.active().unwrap().selection.ranges[0].a1(), "B:B");
// Plain typing still edits.
h.input_mut().events.push(Event::Text("x".into()));
h.run_steps(2);
assert!(h.state().editor.is_some());
}
#[test]
fn space_shortcuts_per_platform() {
let run = |id: &'static str| Some(Shortcut::Run(id, json!({})));
let mac_cmd = Modifiers { mac_cmd: true, command: true, ..Modifiers::NONE };
let space = |m, mac| lookup(Key::Space, Some(Key::Space), m, mac);
assert_eq!(space(Modifiers::SHIFT, false), run("selection.row"));
assert_eq!(space(Modifiers::SHIFT, true), run("selection.row"));
assert_eq!(space(CTRL, false), run("selection.column"));
assert_eq!(space(Modifiers::CTRL, true), run("selection.column"));
// Cmd+Space is Spotlight on macOS.
assert_eq!(space(mac_cmd, true), None);
}

View File

@ -150,6 +150,7 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12.
| Date | Change | Summary |
|---|---|---|
| 2026-10-11 | minor | Shift+Space / Ctrl+Space no longer start an edit; Control+Space selects the column on Mac (#338) |
| 2026-10-11 | minor | Selected whole columns can be reordered by dragging their headers to an insertion point |
| 2026-10-11 | minor | Page Layout view with rulers and in-place headers and footers (#329) |
| 2026-10-11 | minor | Forecast Sheet dialog (#322) |