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 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
Diego Guerini
2026-10-14 22:00:11 -04:00
committed by GitHub
co-authored by Claude Opus 5.5 Brandon Thomas
parent 35e24aa436
commit 2c7629fa7a
3 changed files with 55 additions and 1 deletions
+25
View File
@@ -152,6 +152,13 @@ fn shift_variant_params(id: &str) -> Option<Value> {
}
}
/// 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() };
+27
View File
@@ -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<WordApp>| 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.
+3 -1
View File
@@ -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 |