Browse / github.com/storytold/wordcraft / patch
#4414: Replace All reports its count in a message box, in the interface language
Replace dialog: the result of “Replace All” is shown in light gray and in English; Word shows a centered message box Replace dialog: the result of “Replace All” is shown in light gray and in English; Word shows a centered message box Version: WordCraft 0.11.0, Windows 11. Interface language: [French / English]. Steps: open Find and Replace (Ctrl+H), enter a term and a replacement, click Replace All. Actual: the result appears as light-gray text in the dialog, in English [even though the interface is in French]. The contrast is low and it is easy to miss. Expected (as in Microsoft Word): a small centered message box giving the number of replacements, in the interface language. Related: #[number] (right-click
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- MeHail-02 (imported from pull request #4486, open)
- Base
2cf1d6c7ae94723a45bd6e7e8c884fe375148750- Size
- +85 −6 in 5 files
- Review
- pending
- Shared
- 2026-10-28
- Id
339d6e46121cecee839a383b
How it went
No other operator has reported using it yet.
Diff
raw · crates/ui-egui/src/dialogs.rs crates/ui-egui/src/dialogs/chrome.rs crates/ui-egui/src/dialogs/dispatch.rs crates/ui-egui/src/dialogs/tests.rs docs/ui-parity.md
@@ -152,6 +152,8 @@ pub enum Dialog { /// Go To's box: a page number (`+2`, `-1` move from the current page) or a bookmark. goto_target: String, message: String,+ /// Replace All's result, in a message box over the dialog until OK, as in Word (#4414).+ notice: Option<String>, /// Put the caret in Find what on the first frame, so typing searches (#772). focus: bool, },@@ -673,6 +675,7 @@ impl Dialog { goto_what: GoToWhat::Page, goto_target: String::new(), message: String::new(),+ notice: None, focus: true, }, "insertTable" => Dialog::InsertTable { rows: 2, cols: 5 },@@ -138,6 +138,12 @@ pub fn show(app: &mut WordApp, ctx: &egui::Context) { ui.set_min_width(340.0); close = body(app, ui, &mut d); });+ // Replace All's result, above the dialog (drawn after it, so the dialog saw its keys as taken).+ if let Dialog::Find { notice: notice @ Some(_), .. } = &mut d+ && replace_all_notice(ctx, notice.as_deref().unwrap_or_default())+ {+ *notice = None;+ } if let Some(shown) = &shown { if let Dialog::SaveChanges { name, message, .. } = &d { let question = crate::i18n::fmt(tl!("Do you want to save changes to {name}?"), &[("name", name)]);@@ -169,6 +175,23 @@ pub(crate) fn cancel_key_pressed(ui: &Ui) -> bool { ui.input(|i| i.key_pressed(egui::Key::Escape) || (cfg!(target_os = "macos") && i.modifiers.command && i.key_pressed(egui::Key::Period))) } +/// Replace All's result in a message box centred over the Find and Replace dialog, as in Word+/// (#4414). True once OK, Enter or Escape dismisses it.+fn replace_all_notice(ctx: &egui::Context, text: &str) -> bool {+ let resp = egui::Modal::new(egui::Id::unique("wordcraft.replaceAllNotice")).show(ctx, |ui| {+ ui.set_max_width(320.0);+ ui.label(text);+ ui.add_space(10.0);+ ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {+ let ok = ui.add(crate::widgets::primary_button(ui, tl!("OK")));+ ok.request_focus();+ ok.clicked() || ui.input(|i| i.key_pressed(egui::Key::Enter))+ })+ .inner+ });+ resp.inner || resp.should_close()+}+ /// Lays out an OK and a Cancel button right-to-left, in the platform's order: /// macOS puts Cancel left of the default OK button; Windows and Linux put OK left of Cancel. /// Returns `(ok, cancel)` responses. Call inside a right-to-left layout.@@ -288,7 +288,7 @@ pub(super) fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool { } Dialog::Tabs { form } => crate::dialogs_para::tabs(app, ui, form), Dialog::Borders { form } => crate::dialogs_para::borders(app, ui, form),- Dialog::Find { query, replace, match_case, whole_word, regex, extra, tab, goto_what, goto_target, message, focus } => {+ Dialog::Find { query, replace, match_case, whole_word, regex, extra, tab, goto_what, goto_target, message, notice, focus } => { const FIND_TABS: [(FindTab, &str); 3] = [(FindTab::Find, "Find"), (FindTab::Replace, "Replace"), (FindTab::GoTo, "Go To")]; let names = FIND_TABS.map(|(_, l)| tl!(l).to_string()); let current = FIND_TABS.iter().position(|(t, _)| t == tab).unwrap_or(0);@@ -377,12 +377,18 @@ pub(super) fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool { if !message.is_empty() { ui.label(egui::RichText::new(message.as_str()).weak()); }+ // Enter and Escape belong to the message box while it is open above the dialog.+ let keys = notice.is_none(); let mut close = false; ui.horizontal(|ui| { if replace_mode { if ui.button(tl!("Replace All")).clicked() { match app.run("edit.replaceAll", opts.clone()) {- Ok(r) => *message = format!("All done. We made {} replacements.", r["replaced"]),+ Ok(r) => {+ message.clear();+ let count = r["replaced"].as_u64().unwrap_or(0).to_string();+ *notice = Some(crate::i18n::fmt(tl!("All done. We made {count} replacements."), &[("count", &count)]));+ } Err(e) => *message = e, } }@@ -390,14 +396,18 @@ pub(super) fn body(app: &mut WordApp, ui: &mut Ui, d: &mut Dialog) -> bool { let _ = app.run("edit.replace", opts.clone()); } }- if ui.button(tl!("Find Next")).clicked() || ui.input(|i| i.key_pressed(egui::Key::Enter)) {+ if ui.button(tl!("Find Next")).clicked() || (keys && ui.input(|i| i.key_pressed(egui::Key::Enter))) { // One step per click: `edit.find` would select a match itself, and the step after it skip one. match app.run("edit.findNext", opts.clone()) {- Ok(r) => *message = format!("Match {} of {}", r["index"].as_u64().unwrap_or(0) + 1, r["count"]),+ Ok(r) => {+ let n = (r["index"].as_u64().unwrap_or(0) + 1).to_string();+ let total = r["count"].as_u64().unwrap_or(0).to_string();+ *message = crate::i18n::fmt(tl!("Match {n} of {total}"), &[("n", &n), ("total", &total)]);+ } Err(e) => *message = e, } }- if ui.button(tl!("Close")).clicked() || ui.input(|i| i.key_pressed(egui::Key::Escape)) {+ if ui.button(tl!("Close")).clicked() || (keys && ui.input(|i| i.key_pressed(egui::Key::Escape))) { close = true; } });@@ -906,6 +906,44 @@ fn replace_all_with_regex_groups_from_the_dialog() { } } +/// #4414: Replace All reports its count in a message box over the dialog, as in Word, not as+/// faint text in it. OK, Enter or Escape closes the box only: the dialog stays open, and the key+/// doesn't also run Find Next or Close.+#[test]+fn replace_all_reports_in_a_message_box() {+ use egui_kittest::kittest::Queryable;+ // Found by its wording; the count itself is checked on the notice below.+ let done = |h: &egui_kittest::Harness<'static, WordApp>| h.query_by_label_contains("All done. We made").is_some();+ for dismiss in ["ok", "enter", "escape"] {+ let mut h = app_harness(app_with("cat and cat"));+ h.state_mut().run("ui.dialog", json!({"name": "replace"})).unwrap();+ h.run_steps(2);+ if let Some(Dialog::Find { query, replace, .. }) = h.state_mut().dialog.as_mut() {+ *query = "cat".into();+ *replace = "dog".into();+ } else {+ panic!("Replace opens the Find and Replace dialog");+ }+ h.run_steps(2);+ h.get_by_label("Replace All").click();+ h.run_steps(2);+ assert_eq!(h.state().session.doc.plain_text(wordcraft_doc::StoryRef::Body), "dog and dog");+ assert!(done(&h), "{dismiss}: the count is shown in a message box");+ let Some(Dialog::Find { notice: Some(text), .. }) = &h.state().dialog else { panic!("{dismiss}: no notice") };+ assert!(text.contains('2') && !text.contains("{count}"), "{dismiss}: {text:?}");+ let caret = h.state().session.sel.clone();+ match dismiss {+ "ok" => h.get_by_label("OK").click(),+ "enter" => h.key_press(egui::Key::Enter),+ _ => h.key_press(egui::Key::Escape),+ }+ h.run_steps(2);+ assert!(!done(&h), "{dismiss} closes the message box");+ assert!(matches!(&h.state().dialog, Some(Dialog::Find { notice: None, .. })), "{dismiss} leaves the dialog open");+ assert_eq!(h.state().session.sel, caret, "{dismiss} doesn't also run Find Next");+ }+}+ fn app_harness(app: WordApp) -> egui_kittest::Harness<'static, WordApp> { egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 900.0)).build_ui_state( |ui, app: &mut WordApp| {@@ -1,6 +1,6 @@ # UI parity with Microsoft Word -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-25 · **Change:** trivial (Custom Document Properties dialog from File › Info › Properties › Advanced Properties…, #2982; previously trivial: View › Window › New Window opens another window on the document, each with its own caret and view; previously trivial: mail merge: First Record, Last Record and a record number box in Preview Results; Finish & Merge asks which records to merge or print, #3028; previously trivial: selection bar for right-to-left paragraphs and table rows; previously trivial: read only and comments-only protection grey out edits; previously trivial: the Font dialog previews "NaN" or "inf" in the Size box at 12 pt, #3663; previously trivial: File › Options disables the User name field while Restrict Editing is active; previously trivial: while editing a header or footer, a click in the other one edits it there without scrolling; previously trivial: explicit documents keep the editor when recovery discovery finishes; readable recovery controls; previously trivial: Insert Table of Figures dialog: any caption label, #3013; previously trivial: Home › Underline drop-down shows its lines, #2738; previously trivial: the text context menu includes the formatting mini toolbar, #2959; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: the scrollbar page tip stays on one line as the page number grows, #3341; previously trivial: File › Home and New: templates and recent documents are buttons named for screen readers, #3176; previously trivial: forms protection greys out the Developer tab's control buttons and Properties; previously trivial: AltGr doesn't open KeyTips, #2388; previously trivial: closing the header or footer puts the caret back where it was in the body, #3331; previously trivial: Format Page Numbers dialog: number format and Continue from previous section / Start at, #1113; previously trivial: inline pictures drag to a dashed insertion cursor and stay inline, #2233, #2234; previously trivial: Save Changes prompt: a text file saved over itself closes, #2376; previously trivial: the equation being edited is framed as in Word: an outline as tall as its line, a grip, an options strip that opens its menu, and its current argument shaded; previously trivial: the table handles show while the caret is in the table, #2428; previously trivial: legacy menu keys, #1697; previously trivial: Shape Fill › Gradient and the Format Shape pane, #1500; previously trivial: Focus fills the screen, and Full Screen View shows the pages alone, #1678; previously trivial: the text pointer over the page is drawn black or white on Windows, #1746; previously trivial: a click in the header/footer band from the body no longer scrolls to the body text, #2103; previously trivial: File › Recover follows the app theme, #3243; previously trivial: long navigation headings wrap onto further lines within the pane, #3015; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: dragging the vertical scrollbar names the page at the top of the window, #2551; previously trivial: Add Font imports every selected font file, #1789); previously trivial (File › Close keeps the window, #1712; previously trivial: the Spike: Ctrl+F3 and Ctrl+Shift+F3; previously trivial: Ctrl+wheel zoom no longer scrolls the document, #1743; previously trivial: a mouse drag on a table leaves the view where it is, #1738; previously trivial: numbered headings, #1793; previously trivial: Navigation Find exposes Results, #2096; previously trivial: the tab strip never draws tabs over Comments / Editing / Share, #2026; previously trivial: Columns on selected text, #2024; previously trivial: Different First Page headers reachable from page 1, #2826; previously trivial: a copied equation also goes in the clipboard's MathML formats, #1297; previously trivial: Read Mode keeps the keyboard, shows whole columns and keeps text clear of the page-turn bar, #1772); previously trivial: font list entries of tall-ascent fonts sit in their row, not cut off, #2570); previously trivial: Split Cells and Convert Text to Table dialogs follow Word's merged-cell limits and Initial column width, #2592; previously trivial: a new source's Tag is made like Word's, three letters of the author and two digits of the year with a number added when taken, and an edit keeps the Tag, #2094; previously trivial: Web Layout text fills the window width, #1876; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717)); previously trivial: comment balloons readable in the dark theme, #2045; previously trivial: Recently Used Fonts section at the top of the font list, #2068; previously trivial: web preferences kept between visits, #2132; previously trivial: dialog close control: a Windows-style box at the title bar's right end, the whole box clickable, red on hover, #1885); previously trivial (Modify Table Style formats every table style part, #714); previously trivial: a click on the ribbon chrome hands the keyboard to the page in the same frame, #1093 follow-up; previously trivial: Page Setup vertical alignment, #1320; previously trivial: the app opens on the Home tab every launch, as in Word; previously trivial: no document behind the Start screen, #1129; previously trivial: equation pictures share a few textures, #1002; 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-26 · **Change:** trivial (Find and Replace: Replace All reports its count in a message box over the dialog, in the interface language, #4414; previously trivial: Custom Document Properties dialog from File › Info › Properties › Advanced Properties…, #2982; previously trivial: View › Window › New Window opens another window on the document, each with its own caret and view; previously trivial: mail merge: First Record, Last Record and a record number box in Preview Results; Finish & Merge asks which records to merge or print, #3028; previously trivial: selection bar for right-to-left paragraphs and table rows; previously trivial: read only and comments-only protection grey out edits; previously trivial: the Font dialog previews "NaN" or "inf" in the Size box at 12 pt, #3663; previously trivial: File › Options disables the User name field while Restrict Editing is active; previously trivial: while editing a header or footer, a click in the other one edits it there without scrolling; previously trivial: explicit documents keep the editor when recovery discovery finishes; readable recovery controls; previously trivial: Insert Table of Figures dialog: any caption label, #3013; previously trivial: Home › Underline drop-down shows its lines, #2738; previously trivial: the text context menu includes the formatting mini toolbar, #2959; previously trivial: personal templates in File › New and Save as Word Template, #2852; previously trivial: the scrollbar page tip stays on one line as the page number grows, #3341; previously trivial: File › Home and New: templates and recent documents are buttons named for screen readers, #3176; previously trivial: forms protection greys out the Developer tab's control buttons and Properties; previously trivial: AltGr doesn't open KeyTips, #2388; previously trivial: closing the header or footer puts the caret back where it was in the body, #3331; previously trivial: Format Page Numbers dialog: number format and Continue from previous section / Start at, #1113; previously trivial: inline pictures drag to a dashed insertion cursor and stay inline, #2233, #2234; previously trivial: Save Changes prompt: a text file saved over itself closes, #2376; previously trivial: the equation being edited is framed as in Word: an outline as tall as its line, a grip, an options strip that opens its menu, and its current argument shaded; previously trivial: the table handles show while the caret is in the table, #2428; previously trivial: legacy menu keys, #1697; previously trivial: Shape Fill › Gradient and the Format Shape pane, #1500; previously trivial: Focus fills the screen, and Full Screen View shows the pages alone, #1678; previously trivial: the text pointer over the page is drawn black or white on Windows, #1746; previously trivial: a click in the header/footer band from the body no longer scrolls to the body text, #2103; previously trivial: File › Recover follows the app theme, #3243; previously trivial: long navigation headings wrap onto further lines within the pane, #3015; previously trivial: clearing a no-match font query keeps the recent heading visible, #2068; previously trivial: dragging the vertical scrollbar names the page at the top of the window, #2551; previously trivial: Add Font imports every selected font file, #1789); previously trivial (File › Close keeps the window, #1712; previously trivial: the Spike: Ctrl+F3 and Ctrl+Shift+F3; previously trivial: Ctrl+wheel zoom no longer scrolls the document, #1743; previously trivial: a mouse drag on a table leaves the view where it is, #1738; previously trivial: numbered headings, #1793; previously trivial: Navigation Find exposes Results, #2096; previously trivial: the tab strip never draws tabs over Comments / Editing / Share, #2026; previously trivial: Columns on selected text, #2024; previously trivial: Different First Page headers reachable from page 1, #2826; previously trivial: a copied equation also goes in the clipboard's MathML formats, #1297; previously trivial: Read Mode keeps the keyboard, shows whole columns and keeps text clear of the page-turn bar, #1772); previously trivial: font list entries of tall-ascent fonts sit in their row, not cut off, #2570); previously trivial: Split Cells and Convert Text to Table dialogs follow Word's merged-cell limits and Initial column width, #2592; previously trivial: a new source's Tag is made like Word's, three letters of the author and two digits of the year with a number added when taken, and an edit keeps the Tag, #2094; previously trivial: Web Layout text fills the window width, #1876; previously trivial: Language dialog keeps inherited proofing language and no-proof choices (#2717)); previously trivial: comment balloons readable in the dark theme, #2045; previously trivial: Recently Used Fonts section at the top of the font list, #2068; previously trivial: web preferences kept between visits, #2132; previously trivial: dialog close control: a Windows-style box at the title bar's right end, the whole box clickable, red on hover, #1885); previously trivial (Modify Table Style formats every table style part, #714); previously trivial: a click on the ribbon chrome hands the keyboard to the page in the same frame, #1093 follow-up; previously trivial: Page Setup vertical alignment, #1320; previously trivial: the app opens on the Home tab every launch, as in Word; previously trivial: no document behind the Start screen, #1129; previously trivial: equation pictures share a few textures, #1002; 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 Recovery discovery no longer interrupts an explicitly created or opened document. Older copies remain in File › Recover, whose buttons use the interface palette. See [local recovery](recovery.md). @@ -68,6 +68,10 @@ Save Changes exposes the document-specific question as an alert-dialog name and The Language dialog reads effective character formatting, including document defaults, paragraph styles and character styles. Accepting it unchanged keeps inherited language and no-proof choices (#2717); explicit direct formatting still takes precedence. Other proofing and Windows-specific reports remain separate. +## Find and Replace: Replace All result (#4414)++Replace All reports how many replacements it made in a message box centred over the dialog, as in Word, instead of a faint line inside it. OK, Enter or Escape closes the box and leaves Find and Replace open; that key doesn't also run Find Next or Close. The message and Find Next's "Match N of M" go through the interface catalogs, so they show in the interface language once craft-i18n has the rows.+ ## Find and Replace: regular expressions (#4177) With **Regular expressions** on (Search Options), Replace with expands capture groups: `$1`…`$n`, `${n}`, `${name}` and `$name` (the longest run of letters, digits and `_`), `$0` / `${0}` (the whole match), and `\1`…`\9` as in wildcard Replace; `$$` is a dollar sign, `\t` a tab, `\\` a backslash, `\$` and `\^` a `$` or `^` as typed, and `\n` a paragraph mark, which splits the paragraph as `^p` does (Replace All stays one undo step). Word's `^&`, `^p`, `^t`, `^c` and the other `^` codes work there too (with Special characters off, only the `^` codes are taken literally). A group that doesn't exist, didn't take part in the match or is out of range expands to nothing; a `$` that starts no group, an unclosed `${`, and other `\` escapes stay as written. Plain and wildcard Replace are unchanged. Word has no regular-expression mode; this follows the Rust `regex` crate's Replace syntax.@@ -82,6 +86,7 @@ Footnote and endnote editing keeps body and note text at normal opacity, on ligh | Date | Change | Summary | |---|---|---|+| 2026-10-26 | trivial | Find and Replace: Replace All reports its count in a message box centred over the dialog (OK, Enter or Escape closes it; the dialog stays open), and the count and Find Next's "Match N of M" are looked up in the interface catalogs (#4414) | | 2026-10-22 | trivial | View › Window › New Window opens another window on the document, focused and cascaded; two windows on one document are titled `Name:1` and `Name:2` (one window: the plain name). Each window keeps its own selection, scroll position, zoom, view mode, ruler and panes; the text, undo history, unsaved state and file are the document's, so an edit or Undo in either window shows in both, and a caret in text the other window deleted moves to the nearest place left. Closing one of a document's windows asks nothing; closing its last asks about unsaved changes, and quitting asks once per document. Switch Windows lists every window. The web app keeps one window | | 2026-10-22 | trivial | One native window per open document on the desktop (the web app keeps one window): New and Open open the document in a window of its own, focused and cascaded from the last; Open replaces an untouched blank document in its window and brings an already-open file's window forward; each window keeps its own ribbon tab, File page, dialogs, panes and composition; View › Window › Switch Windows lists the windows and brings the chosen one forward; File › Close and the window's close button close the window (asking about unsaved changes) and the window focused before comes forward, while the last window stays open with no document; quitting asks Save / Don't Save / Cancel about each document in its own window | | 2026-10-22 | trivial | Several open documents in one window (one window per document comes next): View › Window › Switch Windows lists the open documents, the shown one marked and unsaved ones dotted, and shows the one chosen, with its own scroll position, zoom, comment drafts and AutoSave file; File › New, Open, Envelopes, Labels, Finish & Merge and personal templates open beside the open documents without asking about unsaved changes; File › Close shows the next open document; the window's close button asks Save / Don't Save / Cancel about each document with unsaved changes, showing it, and Cancel keeps the app open. Untitled documents are numbered (Document2) in the interface language |