From 2c7629fa7a19fc2cc55c7dc9605164c2cc405b22 Mon Sep 17 00:00:00 2001 From: Diego Guerini <106814760+D13GOOOO@users.noreply.github.com> Date: Thu, 15 Oct 2026 04:00:11 +0200 Subject: [PATCH] Shift+Delete cuts the selection on macOS and Linux too (#1669) (#1676) Word for Windows (checked by hand): Shift+Backspace erases like Backspace, Shift+Delete cuts the selection, and Shift+Delete with nothing selected does nothing. On Windows egui-winit already turns Shift+Delete into a Cut event, so WordCraft matched. On macOS and Linux it arrives as the Delete key with Shift, no shortcut was bound to it, and it did nothing, even with text selected. The canvas now runs Cut for Shift+Delete when there is a selection, through the same clipboard path as Mod+X, and does nothing otherwise. Shift+Backspace already erased (#707). Tests: Shift+Backspace erases, Shift+Delete without a selection changes nothing, Shift+Delete on a selection cuts it to the clipboard (fails without the change); the key check accepts Shift alone. Co-authored-by: Claude Opus 5.5 Co-authored-by: Brandon Thomas --- crates/ui-egui/src/keys.rs | 25 +++++++++++++++++++++++++ crates/ui-egui/src/lib.rs | 27 +++++++++++++++++++++++++++ docs/typing-parity.md | 4 +++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/keys.rs b/crates/ui-egui/src/keys.rs index 49b90eb5a..8723b5a9e 100644 --- a/crates/ui-egui/src/keys.rs +++ b/crates/ui-egui/src/keys.rs @@ -152,6 +152,13 @@ fn shift_variant_params(id: &str) -> Option { } } +/// Shift+Delete, which cuts the selection and does nothing without one, as in Word for Windows +/// (#1669). On Windows the windowing layer already sends it as a Cut; elsewhere it arrives as the +/// key, and no shortcut was bound to it. +fn is_shift_delete(key: Key, m: Modifiers) -> bool { + key == Key::Delete && m.shift && !m.command && !m.ctrl && !m.alt && !m.mac_cmd +} + /// Shift+F10 opens the context menu, as the Menu key does (#821). (The Menu key itself doesn't /// reach the app: the windowing layer drops it.) fn is_context_menu_key(key: Key, m: Modifiers) -> bool { @@ -385,6 +392,14 @@ pub fn canvas_events(app: &mut WordApp, ctx: &egui::Context) { let _ = app.run("insert.closeHeader", json!({})); continue; } + if is_shift_delete(key, modifiers) { + if !app.session.sel.is_collapsed() + && let Ok(r) = app.run("edit.cut", json!({})) + { + crate::system_clipboard::put_result(app, ctx, &r); + } + continue; + } if leave_full_screen(app, key, modifiers, true) { continue; } @@ -618,6 +633,16 @@ mod tests { assert!(!is_paste_special_release(Key::V, m(true, true, true)) && !is_paste_special_release(Key::V, m(true, false, false))); } + #[test] + fn shift_delete_is_only_shift_and_delete() { + let m = |shift, ctrl, alt| egui::Modifiers { shift, ctrl, command: ctrl, alt, ..Default::default() }; + assert!(is_shift_delete(Key::Delete, m(true, false, false))); + assert!(!is_shift_delete(Key::Delete, m(false, false, false)), "Delete alone erases"); + assert!(!is_shift_delete(Key::Delete, m(true, true, false)), "Ctrl+Shift+Delete"); + assert!(!is_shift_delete(Key::Delete, m(true, false, true)), "Alt+Shift+Delete"); + assert!(!is_shift_delete(Key::Backspace, m(true, false, false)), "Shift+Backspace erases (#707)"); + } + #[test] fn shift_f10_is_the_context_menu_key() { let m = |shift, ctrl, alt| egui::Modifiers { shift, ctrl, command: ctrl, alt, ..Default::default() }; diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 0c8f3a4fe..f6a76f40c 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -4690,6 +4690,33 @@ mod tests { assert_eq!(h.state().session.doc.plain_text(wordcraft_doc::StoryRef::Body), "Kilns"); } + /// #1669: with Shift held, Backspace still erases (#707) and Delete cuts the selection, doing + /// nothing without one, as in Word for Windows. (Windows sends Shift+Delete as a Cut; macOS and + /// Linux send the key, which reached no command.) + #[test] + fn shift_delete_cuts_the_selection_and_shift_backspace_erases() { + let mut h = harness(app()); + for _ in 0..6 { + h.step(); + } + let key = |key, modifiers| egui::Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers }; + let body = |h: &egui_kittest::Harness| h.state().session.doc.plain_text(wordcraft_doc::StoryRef::Body); + h.event(egui::Event::Text("Kilns fire clay".into())); + h.step(); + h.event(key(egui::Key::Backspace, egui::Modifiers::SHIFT)); + h.step(); + assert_eq!(body(&h), "Kilns fire cla"); + h.event(key(egui::Key::Home, egui::Modifiers::NONE)); + h.event(key(egui::Key::Delete, egui::Modifiers::SHIFT)); + h.step(); + assert_eq!(body(&h), "Kilns fire cla", "no selection: nothing to cut"); + h.state_mut().run("select.text", json!({"text": "fire "})).unwrap(); + h.event(key(egui::Key::Delete, egui::Modifiers::SHIFT)); + h.step(); + assert_eq!(body(&h), "Kilns cla"); + assert_eq!(h.state().session.clipboard_text, "fire ", "cut, not just deleted"); + } + /// #1538: inserting WordArt leaves its text selected, so Delete erased only the text and the /// empty box stayed. Escape now selects the box itself, Delete removes it, and a second Escape /// is back to a caret in the body. diff --git a/docs/typing-parity.md b/docs/typing-parity.md index f3a5e4ad1..7abd05d4d 100644 --- a/docs/typing-parity.md +++ b/docs/typing-parity.md @@ -1,6 +1,6 @@ # Typing parity with Word -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** trivial (Select All then Delete removes a table the document starts with, #1198; previously trivial: Alt+numpad character codes, #961; previously trivial: Down and Up from a line start beside a table go into it, #1007; previously trivial: sample-text formulas on Enter, #1067; previously trivial: Track Changes: Accept All after a tracked delete from a paragraph end keeps the next paragraph; Reject All after typing into an empty paragraph keeps it (#864); previously minor: Correct TWo INitial CApitals, cAPS LOCK and table-cell capitals, #544; previously trivial: complex-script caret and Backspace, #604; Move Paragraph Up/Down, Promote/Demote, Go Back and Alt+X keys, #343) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** trivial (Shift+Delete cuts the selection on every platform, #1669; previously trivial: Select All then Delete removes a table the document starts with, #1198; previously trivial: Alt+numpad character codes, #961; previously trivial: Down and Up from a line start beside a table go into it, #1007; previously trivial: sample-text formulas on Enter, #1067; previously trivial: Track Changes: Accept All after a tracked delete from a paragraph end keeps the next paragraph; Reject All after typing into an empty paragraph keeps it (#864); previously minor: Correct TWo INitial CApitals, cAPS LOCK and table-cell capitals, #544; previously trivial: complex-script caret and Backspace, #604; Move Paragraph Up/Down, Promote/Demote, Go Back and Alt+X keys, #343) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4 What Word does when you type, observed black-box (same keystrokes typed into Word and WordCraft, results read back paragraph by paragraph) and pinned by tests in @@ -76,6 +76,7 @@ Windows/Linux keys are given first, macOS second. | Outline view: Alt+Shift+Plus / Minus, Alt+Shift+1–9, Alt+Shift+A | Expand / Collapse the heading (one level at a time); Show Level 1–9; show all levels. The caret never stays in hidden text: it moves on to the next shown paragraph | `outline_show_level_filters_paragraphs` | | Shift+F5 | Go Back: the caret cycles through the last four places edited, most recent first | `shift_f5_goes_back_through_the_last_edits` | | Alt+X (no macOS default) | the hex code before the caret (up to six digits, `U+` optional) or the selected code becomes its character; otherwise the character before the caret becomes its code (`é` ↔ `00E9`); one undo step | `alt_x_toggles_between_a_character_and_its_code` | +| Shift+Backspace / Shift+Delete | Shift+Backspace erases like Backspace (#707). Shift+Delete cuts the selection to the clipboard and does nothing without one (Word for Windows, observed 2026-10-14). Windows delivers Shift+Delete as a Cut; macOS and Linux deliver the key, which did nothing (#1669) | `shift_delete_cuts_the_selection_and_shift_backspace_erases` | | Alt+numpad code (Windows and Linux; not macOS) | hold Alt, type a decimal code, release Alt: the character appears at the caret as typed text (one undo step). With a leading 0 the code is the ANSI code page — Windows-1252 by default (`0233` → é, `0128` → €); without one, 1–255 is the OEM code page — 437 by default and in US English (`233` → Θ, `1` → ☺, `7` → •), 850 for Western European locales and English outside the US (`155` → ø; `233` is Ú there, per the CP850 mapping file — é is `130` in both). On Windows the system locale picks both pages (`text.typeCode` `ansi`/`oem`): Central European 1250/852 (Polish `0185` and `165` → ą), Cyrillic 1251/866, Greek 1253/737, Turkish 1254/857, and for codes up to 255 the character Windows composes itself replaces ours (it knows every code page, double-byte ones too); Linux keeps 1252 and 437/850. Above 255, with or without the 0, the Unicode character with that decimal value (`8364` → €). Control characters, 0, unassigned bytes and values past Unicode type nothing; auto-repeat of a held digit adds nothing, and switching windows drops a half-typed code. Word counts only keypad digits; egui reports keypad and top-row digits alike, so top-row digits with Alt alone compose codes too. An Alt code never opens KeyTips | `alt_numpad_codes_map_like_word_on_windows`, `alt_numpad_code_types_one_character`, ui-egui `alt_numpad_code_types_its_character_without_keytips` | | Shift+Right (⇧→) at the end of a cell's text | the whole cell is selected first (its end-of-cell mark); the next Shift+Right selects the next cell with it. In an empty cell the first press selects the cell | `shift_right_at_the_end_of_a_cell_selects_the_cell_first` | @@ -194,6 +195,7 @@ Word. | Date | Change | Summary | |---|---|---| +| 2026-10-14 | trivial | Shift+Delete cuts the selection, and does nothing without one, on macOS and Linux too, as in Word for Windows (#1669); Shift+Backspace already erased (#707) | | 2026-10-13 | trivial | Select All then Delete or Backspace removes a table the document starts with (#1198) | | 2026-10-12 | trivial | Editing keys: Down and Up from the start of a line beside a table go into it instead of skipping it (#1007) | | 2026-10-12 | trivial | Sample-text formulas on Enter (#1067), exact matching, bounded parameters, original English and one-step undo |