Tables: Select All then Delete removes a table the document starts with; right-click Delete offers Delete Table again (#1202)

Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
Marco Arnulfo
2026-10-14 17:59:17 -04:00
committed by GitHub
co-authored by Brandon Thomas
parent 8ae23cee29
commit 70a4917973
5 changed files with 88 additions and 21 deletions
+21
View File
@@ -389,6 +389,27 @@ impl Document {
}
}
}
// From the very start of a top-level table to a top-level paragraph (Select All in a
// document that starts with a table, #1198): the tables are covered whole and go too,
// and the paragraphs left between join.
if a.path.depth() > 0 && b.path.depth() == 0 && a.off == 0 {
let ta = a.path.0.first().copied().unwrap_or(0) as usize;
let first = self.para_paths(a.story).into_iter().find(|q| q.0.first().is_some_and(|t| *t as usize == ta));
if first.as_ref() == Some(&a.path) {
let ib = b.path.last();
let bl = self.container_mut(a.story, &b.path)?;
let mut removed = 0;
for i in (ta..ib).rev() {
if matches!(bl.get(i).map(|b| &**b), Some(Block::Table(_))) {
bl.remove(i);
removed += 1;
}
}
let at = Pos { story: a.story, path: Path::top(ta), off: 0 };
let end = Pos { story: a.story, path: Path::top(ib.saturating_sub(removed)), off: 0 };
return self.delete_range(&at, &end);
}
}
Ok(a)
}
+26
View File
@@ -1067,6 +1067,32 @@ fn typing_rtl_letters_into_an_empty_paragraph_makes_it_rtl() {
assert!(x < 300.0, "left-to-right paragraph: caret at {x}");
}
/// Select All then Delete or Backspace empties the document, tables included (Word, #1198):
/// whether the text starts with a table or with a paragraph.
#[test]
fn select_all_then_delete_removes_tables() {
let tables = |s: &Session| s.doc.body.iter().filter(|b| b.as_table().is_some()).count();
for key in ["text.delete", "text.backspace"] {
let mut s = typed("");
run(&mut s, "insert.table", json!({"rows": 2, "cols": 2}));
typ(&mut s, "cell");
assert_eq!(tables(&s), 1);
run(&mut s, "select.all", json!({}));
run(&mut s, key, json!({}));
assert_eq!(tables(&s), 0, "{key}: a table at the start of the document goes");
assert_eq!(s.doc.plain_text(StoryRef::Body), "", "{key}");
let mut s = typed("before\n");
run(&mut s, "insert.table", json!({"rows": 2, "cols": 2}));
typ(&mut s, "cell");
assert_eq!(tables(&s), 1);
run(&mut s, "select.all", json!({}));
run(&mut s, key, json!({}));
assert_eq!(tables(&s), 0, "{key}: a table after text goes");
assert_eq!(s.doc.plain_text(StoryRef::Body), "", "{key}");
}
}
/// #898: a click just after a citation at the end of a paragraph puts the caret before the
/// citation's hidden field end; what is typed there goes after the citation, as in Word, instead
/// of into its result (Zotero then reports the citation as edited by hand).
+35 -18
View File
@@ -2651,12 +2651,14 @@ fn context_menu(app: &mut WordApp, ui: &mut Ui) {
item(ui, app, "Insert Rows Above", "table.insertRowAbove", json!({}));
item(ui, app, "Insert Rows Below", "table.insertRowBelow", json!({}));
});
match pick {
TablePick::Cell | TablePick::Cells => item(ui, app, "Delete Cells", "table.deleteCells", json!({})),
TablePick::Rows => item(ui, app, "Delete Rows", "table.deleteRow", json!({})),
TablePick::Columns => item(ui, app, "Delete Columns", "table.deleteColumn", json!({})),
TablePick::Table => item(ui, app, "Delete Table", "table.deleteTable", json!({})),
}
// Every Delete is there whatever is selected, as on Word's Delete button: with the caret
// in a cell, Delete Table is how the table goes (#1198).
ui.menu_button(tl!("Delete"), |ui| {
item(ui, app, "Delete Cells", "table.deleteCells", json!({}));
item(ui, app, "Delete Columns", "table.deleteColumn", json!({}));
item(ui, app, "Delete Rows", "table.deleteRow", json!({}));
item(ui, app, "Delete Table", "table.deleteTable", json!({}));
});
if pick == TablePick::Cell {
item(ui, app, "Split Cells", "table.split", json!({}));
}
@@ -3658,9 +3660,9 @@ mod tests {
}
/// Right-clicking a table cell puts the caret there, and the table's items follow what is
/// selected, as in Word: with the caret in a cell, Delete Cells and Split Cells; with the
/// whole table, Delete Table, Distribute, AutoFit and Insert Caption… (never Select or Cell
/// Alignment).
/// selected, as in Word: with the caret in a cell, Split Cells; with the whole table,
/// Distribute, AutoFit and Insert Caption… (never Select or Cell Alignment). Delete is there
/// for both.
#[test]
fn the_context_menu_in_a_table_follows_the_selection() {
let mut h = table_window();
@@ -3668,8 +3670,8 @@ mod tests {
let at = screen(&mut h, b.x + b.w / 2.0, b.y + b.h / 2.0);
right_click(&mut h, at);
assert_eq!(h.state().session.sel.focus.path.cell().map(|(_, r, c)| (r, c)), Some((1, 2)), "the caret moved to the cell");
assert_menu(&h, &["Insert", "Delete Cells", "Split Cells", "Text Direction", "Table Properties…"], true);
assert_menu(&h, &["Merge Cells", "Distribute Rows", "Delete Table", "AutoFit", "Select", "Cell Alignment"], false);
assert_menu(&h, &["Insert", "Delete", "Split Cells", "Text Direction", "Table Properties…"], true);
assert_menu(&h, &["Merge Cells", "Distribute Rows", "AutoFit", "Select", "Cell Alignment"], false);
let depth = h.state().session.undo_depth();
pick(&mut h, &["Insert", "Insert Rows Below"]);
assert_eq!(h.state().session.doc.body.iter().find_map(|b| b.as_table()).map(|t| t.rows.len()), Some(3));
@@ -3677,14 +3679,29 @@ mod tests {
h.state_mut().run("table.selectTable", json!({})).unwrap();
right_click(&mut h, at);
assert!(h.state().session.cell_range().is_some(), "a right-click on the selected table keeps it");
assert_menu(&h, &["Delete Table", "Distribute Rows", "Distribute Columns", "AutoFit", "Insert Caption…", "Table Properties…"], true);
assert_menu(&h, &["Delete Cells", "Split Cells", "Merge Cells"], false);
pick(&mut h, &["Delete Table"]);
assert_menu(&h, &["Delete", "Distribute Rows", "Distribute Columns", "AutoFit", "Insert Caption…", "Table Properties…"], true);
assert_menu(&h, &["Split Cells", "Merge Cells"], false);
pick(&mut h, &["Delete", "Delete Table"]);
assert!(h.state().session.doc.body.iter().all(|b| b.as_table().is_none()));
}
/// With just the caret in a cell, right-click › Delete › Delete Table removes the table, in
/// one undo step (#1198: the menu offered only Delete Cells there).
#[test]
fn a_right_click_in_a_cell_deletes_the_table() {
let mut h = table_window();
let b = cell(&mut h, 0, 1);
let at = screen(&mut h, b.x + b.w / 2.0, b.y + b.h / 2.0);
right_click(&mut h, at);
assert!(h.state().session.cell_range().is_none(), "just the caret");
let depth = h.state().session.undo_depth();
pick(&mut h, &["Delete", "Delete Table"]);
assert!(h.state().session.doc.body.iter().all(|b| b.as_table().is_none()));
assert_eq!(h.state().session.undo_depth(), depth + 1);
}
/// With a column's cells selected, a right-click on one of them keeps them: the menu is a
/// column's (Delete Columns, Merge Cells, Distribute) and its Text Direction turns just the
/// column's (Merge Cells, Distribute) and its Text Direction turns just the
/// column, in one undo step; one on a cell beside them drops them, though that cell lies
/// between the selection's ends in the text, and the menu is a cell's again.
#[test]
@@ -3697,8 +3714,8 @@ mod tests {
let at = screen(&mut h, on.x + on.w / 2.0, on.y + on.h / 2.0);
right_click(&mut h, at);
assert_eq!(h.state().session.cell_range().map(|r| r.cols), Some((0, 0)), "still the column");
assert_menu(&h, &["Delete Columns", "Merge Cells", "Distribute Rows", "Distribute Columns", "Text Direction"], true);
assert_menu(&h, &["Delete Cells", "Split Cells", "AutoFit"], false);
assert_menu(&h, &["Merge Cells", "Distribute Rows", "Distribute Columns", "Text Direction"], true);
assert_menu(&h, &["Split Cells", "AutoFit"], false);
let depth = h.state().session.undo_depth();
pick(&mut h, &["Text Direction"]);
assert_eq!(h.state().session.undo_depth(), depth + 1);
@@ -3714,7 +3731,7 @@ mod tests {
assert!(h.state().session.cell_range().is_none(), "the column is no longer selected");
assert_eq!(h.state().session.sel.focus.path.cell().map(|(_, r, c)| (r, c)), Some((0, 2)));
assert_menu(&h, &["Split Cells"], true);
assert_menu(&h, &["Delete Columns", "Merge Cells"], false);
assert_menu(&h, &["Merge Cells"], false);
}
/// Issue #123: zoomed out, pages sit side by side in rows when the width allows; at 100% a
+3 -1
View File
@@ -1,6 +1,6 @@
# Typing parity with Word
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-14 · **Change:** 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 (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
@@ -57,6 +57,7 @@ formatting use the existing editing primitives. Tests: `sample_text_formulas_exp
| Paste of several paragraphs (tracking on) | every paragraph mark the paste adds is a tracked insertion; Reject All restores the one paragraph, Accept All keeps the pasted ones | `reject_all_takes_out_the_paragraph_breaks_a_tracked_paste_inserted` |
| Backspace at a paragraph start / Delete at a paragraph end (tracking on) | the paragraph mark is marked deleted (still shown); the caret moves before it (Backspace) or past it (Delete). Accept joins the paragraphs, Reject keeps them. A mark you inserted yourself is just removed | `tracked_enter_and_backspace_track_the_paragraph_mark`, `tests::tracked_backspace_and_delete_mark_a_paragraph_break_deleted` |
| Delete a range from the end of one paragraph into a later one (tracking on) | the paragraph marks and text in the range are tracked deletions. Accept All removes exactly those, so the paragraph after the range stays a paragraph of its own; Reject All restores everything. Typing into an empty paragraph and rejecting leaves that paragraph | `tests::accept_all_after_a_tracked_delete_from_a_paragraph_end_keeps_the_next_paragraph`, `tests::reject_all_after_typing_into_an_empty_paragraph_keeps_the_paragraph` |
| Select All, then Delete or Backspace | the whole document goes, tables included, also when it starts with a table; one empty paragraph is left | `select_all_then_delete_removes_tables` |
| Table Layout › Delete Table / Rows / Columns / Cells (tracking on) | like Word deleting a selected table: the cells' text is marked deleted and the table structure stays (WordCraft has no tracked row or column deletion, `w:trPr/w:del`, so rows and columns keep their cells too, where Word's Accept removes the rows); text you inserted yourself is just removed; Reject All restores the text. A table, row or column with no text at all (empty, or its deletion already accepted) is removed as with tracking off; one whose text is all already marked deleted is left alone with a message (accept or reject first). Delete Columns leaves a merged cell that also spans a kept column alone (it narrows, keeping its text). A content control locked against deletion, or content locked against editing, refuses the delete with tracking on or off | `tests_revision_checks::tracked_table_delete_marks_the_text_deleted_and_reject_all_restores_it`, `tracked_row_column_and_cell_deletes_mark_their_text_deleted`, `tracked_table_delete_over_tracked_insertions_rejects_back_to_the_original`, `table_deletes_refuse_a_control_locked_against_deletion`, `tracked_delete_table_removes_a_table_with_no_live_text`, `delete_columns_leaves_a_merged_cell_that_keeps_a_column_alone`, `tracked_row_and_column_deletes_over_tracked_changes_reject_back_to_the_original` |
| Change Case (Shift+F3, Home › Aa) (tracking on) | Word shows no insertion or deletion markup (checked with Formatting markup set to none, so Word may record it as a hidden formatting revision). **Differs:** WordCraft records the changed letters as a tracked deletion of the old and insertion of the new, since case isn't a run property a formatting revision could hold; Reject All restores the old case, Accept All keeps the new. Your own tracked insertion is rewritten in place; already-deleted text is left alone and doesn't count (with tracking off too), so changing case again (or Shift+F3) works from the visible text. Refused in content locked against editing | `tests_revision_checks::tracked_change_case_rejects_back_to_the_original_case`, `tracked_change_case_of_own_insertion_stays_an_insertion`, `tracked_change_case_twice_reads_only_the_live_text`, `tracked_change_case_handles_sharp_s_and_mixed_formatting`, `change_case_refuses_content_locked_against_editing` |
| Unlink Fields (Ctrl+Shift+F9) (tracking on) | not tracked, like Word: the field's result stays as ordinary text. Refused in content locked against editing | `tests_revision_checks::tracked_unlink_keeps_the_result_as_plain_text`, `unlink_refuses_a_field_in_content_locked_against_editing` |
@@ -193,6 +194,7 @@ Word.
| Date | Change | Summary |
|---|---|---|
| 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 |
| 2026-10-13 | minor | Alt+numpad decimal character codes: the ANSI code page with a leading 0, the OEM page without (the system locale's on Windows: 1250–1254, 437/737/850/852/857/866), Unicode above 255 (#961) |
+3 -2
View File
@@ -1,6 +1,6 @@
# UI parity with Microsoft Word
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (Track Changes Options › Comments: show only the selected text's comment, beyond Word, #1055; previously trivial: Recipient List dialog gains Filter and Sort, #778; previously trivial: Ctrl+drag copies a picture, shape or text box, #1014; previously trivial: the mini toolbar scrolls under the ribbon, not over it; previously trivial: the mini toolbar steps aside for the ribbon; previously trivial: selection bar in the left margin, #856; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously major: first version; ribbon, dialogs, shortcuts and on-canvas interaction measured from `crates/ui-egui` and the command registry) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (the table context menu's Delete offers Delete Table with the caret in a cell, #1198; previously trivial: Track Changes Options › Comments: show only the selected text's comment, beyond Word, #1055; previously trivial: Recipient List dialog gains Filter and Sort, #778; previously trivial: Ctrl+drag copies a picture, shape or text box, #1014; previously trivial: the mini toolbar scrolls under the ribbon, not over it; previously trivial: the mini toolbar steps aside for the ribbon; previously trivial: selection bar in the left margin, #856; previously trivial: table column and row markers on the rulers, #700; previously trivial: table borders and the resize handle drag on the page, #217; previously major: first version; ribbon, dialogs, shortcuts and on-canvas interaction measured from `crates/ui-egui` and the command registry) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
How WordCraft looks and feels next to Word: ribbon, dialogs, panes, keyboard, mouse and touch.
Typing behaviour has its own checklist ([`typing-parity.md`](typing-parity.md)). All assets are our
@@ -22,7 +22,7 @@ Document title opens Rename Document; the account badge opens Options › Genera
| Modal dialogs | **56** (`Dialog` enum: Discard Recovery, Font, Enclose Characters, Paragraph, Tabs, Borders and Shading, Find, Go To, Insert Table, Table Properties, Page Setup, Link, Bookmark, Word Count, Zoom, Watermark, New Style, Modify Style, Manage Styles, Table Style, Commands, Icons, Templates and Add-ins, Organizer, Compare and Combine, AutoCorrect Options, Paste Special, About, Save Changes, Password, Encrypt with Password, Define New Multilevel List, Track Changes Options; Columns, Symbol with Special Characters, Field (#321); Field code (#339); mail merge Recipients, Insert Merge Field, Find Recipient, Rules, Match Fields, Check for Errors; Caption with New Label and Numbering, Index, Mark Index Entry, Custom Table of Contents with Options and TOC styles, Source Manager, Create/Edit Source (#402); Cross-reference (#916); Line Numbers, Hyphenation with Manual Hyphenation, Language, Envelopes, Labels (#407); Page Setup has a Paper size list and custom width and height, opened from Layout › Size › More Paper Sizes… (#1433)) plus panes and menus for the rest | ~100 (Options panes…) | ~45% | 11–19 |
| Keyboard shortcuts | **152** commands bound in `CommandSpec::key`, 172 key combinations (counted from the registry on Linux, 2026-10-13); styles, spacing, symbols, fields, views, mail merge (#330); field keys Alt+F9, Shift+F9, Ctrl+Shift+F9, Ctrl+F11, Ctrl+Shift+F11, Shift+F11 (#339), Next Field on Alt+F1 and Alt+Shift+F1 (Word's other keys for it) since F11 went to full screen (#980; GNOME and KDE may keep Alt+F1 for their own overview or application menu, and then Next Field has no key there; Previous Field keeps Shift+F11); Alt+Shift+arrows (move paragraphs, promote/demote), Shift+F5 Go Back and Alt+X (#343); Outline view: Alt+Shift+Plus/Minus expand/collapse, Alt+Shift+1–9 and Alt+Shift+A Show Level (#493); Alt+Q to "Tell me" command search (#819, not on macOS). Missing: en dash on the numeric keypad | ~250 default shortcuts | ~70% | 2–4 |
| Task panes (Navigation, Styles, Comments, Reviewing, Format Picture/Shape, Thesaurus, Accessibility, Clipboard, Selection) | Navigation (long headings truncate with an ellipsis instead of expanding the pane, #759), Styles, Comments, Thesaurus, Restrict Editing, Accessibility (grouped findings, jump-to, fixes) and Alt Text (#386), Read Aloud, Zotero; Style Inspector (#236); Clipboard pane (#264) open | all | 70% | 4–6 |
| Context menus (right-click) | text, spelling suggestions, fields (Update Field, Edit Field…, Toggle Field Codes, #339); right-click selects a picture, shape or text box and offers Wrap Text (styles, Move with Text / Fix Position on Page, #513); tables, the items following the selection as in Word (Insert, Delete Cells, Rows, Columns or Table, Merge and Split Cells, Distribute, AutoFit, Text Direction, Insert Caption…, Table Properties…; no Border Styles yet) | rich per context | 65% | 2–4 |
| Context menus (right-click) | text, spelling suggestions, fields (Update Field, Edit Field…, Toggle Field Codes, #339); right-click selects a picture, shape or text box and offers Wrap Text (styles, Move with Text / Fix Position on Page, #513); tables, the items following the selection as in Word (Insert, Delete › Cells, Columns, Rows or Table whatever is selected (#1198), Merge and Split Cells, Distribute, AutoFit, Text Direction, Insert Caption…, Table Properties…; no Border Styles yet) | rich per context | 65% | 2–4 |
| Backstage (Home, New, Open, Info, Save As, Print, Share, Export, Options) | Home and New separate (#254), Info properties kept (#268), Options in Word's panes with working settings (#485), Print page with preview, page range and copies (#641); launched with nothing to open, the app starts on Home (the Start screen: Blank document, templates, Recent; no document is open until one is picked or opened, so Esc or Back shows an empty window with the ribbon greyed out but for Help, #1129), with File › Options › General › Start-up to turn it off (#766); Info › Inspect Document finds and removes hidden text by its effective formatting (direct, character or paragraph style) in every story: body, headers, footers, notes, comments and text boxes, and lists what it couldn't reach (#1344) | | 68% | 4–6 |
| Rulers: indents, tabs, margins, table columns | indents draggable (one undo, #62); tabs; table column and row markers drag their borders (one undo) and in a cell the indents are measured from it (#700) | ✅ | 85% | 1–2 |
| On-canvas objects: select, drag, Ctrl+drag to copy, resize handles, nudge | ✅ tested (#228, #1014) | ✅ | 75% | — |
@@ -51,6 +51,7 @@ Document title opens Rename Document; the account badge opens Options › Genera
| Date | Change | Summary |
|---|---|---|
| 2026-10-13 | trivial | Table context menu: Delete is a submenu with Delete Cells, Columns, Rows and Table whatever is selected, so the caret in a cell is enough to delete the table (#1198) |
| 2026-10-12 | trivial | Track Changes Options › Comments: "In the margin" (default, as in Word) or "Only for the selected text", an option Word doesn't have (#1055). In the second mode the margin column is gone and only the comment under the caret, or the clicked one, floats beside the page (over its edge in a narrow window); comment ranges stay shaded in the text. `review.trackingOptions {"commentDisplay": "margin"\|"selected"}` |
| 2026-10-12 | trivial | On-canvas objects: Ctrl+drag copies a picture, shape or text box (`arrange.copyTo`, #1014) |
| 2026-10-12 | trivial | Cross-reference dialog: reference type (heading, bookmark, equation, figure, table), what to insert and the item; the ribbon button opened nothing before (#916); modal dialog count 55 |