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 |