Compare commits

..
Author SHA1 Message Date
FredAppsandClaude Opus 5.5 02a802cbaf Release 0.16.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 20:32:38 +01:00
FredAppsandClaude Opus 5.5 6e266e8525 Merge main into release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 20:32:38 +01:00
Dylan M. Taylor cfcc8c2aec Merge main into release 2026-11-04 21:12:48 -05:00
Dylan M. Taylor 64a3badfe3 Merge main into release 2026-11-04 21:07:43 -05:00
Dylan M. Taylor edfea68101 Release 0.15.2 2026-11-04 19:49:42 -05:00
Dylan M. Taylor 74d3701c74 Release 0.15.2 2026-11-04 19:46:26 -05:00
Dylan M. Taylor 8512ac6f36 Revert the release environment on the workflow call 2026-11-04 19:39:27 -05:00
Dylan M. Taylor 421700e890 Run the signed release from the release environment 2026-11-04 19:36:14 -05:00
Dylan M. Taylor d69cbec87f Release 0.15.2 2026-11-04 18:49:09 -05:00
11 changed files with 34 additions and 317 deletions
Generated
+19 -19
View File
@@ -6795,7 +6795,7 @@ dependencies = [
[[package]]
name = "wordcraft"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"arboard",
"chrono",
@@ -6833,7 +6833,7 @@ dependencies = [
[[package]]
name = "wordcraft-cli"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"serde_json",
"sys-locale",
@@ -6846,11 +6846,11 @@ dependencies = [
[[package]]
name = "wordcraft-control-key"
version = "0.15.1"
version = "0.16.0"
[[package]]
name = "wordcraft-doc"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"base64 0.23.1",
"log",
@@ -6864,7 +6864,7 @@ dependencies = [
[[package]]
name = "wordcraft-docbin"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"cfb",
"chrono",
@@ -6881,7 +6881,7 @@ dependencies = [
[[package]]
name = "wordcraft-docx"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"aes",
"base64 0.23.1",
@@ -6906,7 +6906,7 @@ dependencies = [
[[package]]
name = "wordcraft-engine"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"cfb",
"chrono",
@@ -6939,7 +6939,7 @@ dependencies = [
[[package]]
name = "wordcraft-fonts"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"brotli-decompressor",
"harfrust 0.13.3",
@@ -6953,7 +6953,7 @@ dependencies = [
[[package]]
name = "wordcraft-formats"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"aes",
"base64 0.23.1",
@@ -6980,7 +6980,7 @@ dependencies = [
[[package]]
name = "wordcraft-geom"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"image",
"serde",
@@ -6988,7 +6988,7 @@ dependencies = [
[[package]]
name = "wordcraft-layout"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"craft-proof",
"image",
@@ -7008,7 +7008,7 @@ dependencies = [
[[package]]
name = "wordcraft-mcp"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"log",
"serde_json",
@@ -7020,7 +7020,7 @@ dependencies = [
[[package]]
name = "wordcraft-metafile"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"kurbo",
"thiserror 2.0.21",
@@ -7028,7 +7028,7 @@ dependencies = [
[[package]]
name = "wordcraft-pdf"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"chrono",
"craft-pdf",
@@ -7049,7 +7049,7 @@ dependencies = [
[[package]]
name = "wordcraft-render"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"image",
"log",
@@ -7063,7 +7063,7 @@ dependencies = [
[[package]]
name = "wordcraft-ui-egui"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"accesskit_consumer",
"cfb",
@@ -7096,7 +7096,7 @@ dependencies = [
[[package]]
name = "wordcraft-web"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"craft-web-host",
"eframe",
@@ -7116,7 +7116,7 @@ dependencies = [
[[package]]
name = "wordcraft-zotero"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"log",
"proptest",
@@ -7279,7 +7279,7 @@ checksum = "9440ea3e5aeabb0ac63af70daf835274065238cdd0cec83418f417eae38bacee"
[[package]]
name = "xtask"
version = "0.15.1"
version = "0.16.0"
dependencies = [
"serde_json",
]
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
default-members = ["crates/*", "apps/wordcraft", "apps/wordcraft-cli", "xtask"]
[workspace.package]
version = "0.15.1"
version = "0.16.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
+1 -2
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -90,8 +90,8 @@ pub use settings::{
COMPAT_MODE_CURRENT, CoreProps, CustomProp, DEFAULT_GRID, DEFAULT_HYPHENATION_ZONE, LEGACY_COMPAT_MODE, OdfLayout, PagePicture, Settings, Source,
THEME_COLORS, Watermark, Zoom, ZoomFit,
};
pub(crate) use text_walk::walk_between;
pub use text_walk::{PARA_WALK_DEPTH, count_chars, count_chars_no_spaces, count_words};
pub(crate) use text_walk::{PARA_WALK_DEPTH, walk_between};
pub use text_walk::{count_chars, count_chars_no_spaces, count_words};
/// A document.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+1 -1
View File
@@ -48,7 +48,7 @@ pub fn count_chars_no_spaces(s: &str) -> usize {
}
/// How deep in nested tables [`Document::para_paths`] goes: cells below this are skipped.
pub const PARA_WALK_DEPTH: usize = 16;
pub(crate) const PARA_WALK_DEPTH: usize = 16;
/// The note and text box stories an object shows, for `Document::reachable`.
pub(crate) fn notes_and_boxes(o: &InlineObject) -> Vec<(u32, &'static [PartKind])> {
+3 -66
View File
@@ -1,5 +1,4 @@
use super::{Arc, CmdError, CmdResult, Document, Pos, Selection, Session, StoryRef, Value, delete_selection, json, p, sel_result};
use wordcraft_doc::CharProps;
/// Options › Language › Set as default: only the saved default proofing language (what future
/// blank documents are written in), as Word's Set as Preferred. A setting, like `file.options`: the
@@ -15,15 +14,8 @@ pub(super) fn preferred_language(s: &mut Session, v: &Value) -> CmdResult {
/// Set Proofing Language: the selection's language and "do not check spelling or grammar",
/// or (`default`) the document's default and the saved language for future blank documents;
/// `detect` is the per-user detection preference.
/// Nothing at all sets US English, as before. `scope: "document"` sets it for every story.
/// Nothing at all sets US English, as before.
pub(super) fn language(s: &mut Session, v: &Value) -> CmdResult {
// Checked first, so a bad scope changes nothing (not even the detect preference).
let whole_document = match v.get("scope") {
None | Some(Value::Null) => false,
Some(Value::String(t)) if t == "selection" => false,
Some(Value::String(t)) if t == "document" => true,
Some(other) => return Err(CmdError::Params(format!("`scope` must be \"selection\" or \"document\", not {other}"))),
};
let lang = match p::str(v, "lang") {
Some(t) => Some(
wordcraft_proof::lang::normalize_tag(t)
@@ -53,75 +45,20 @@ pub(super) fn language(s: &mut Session, v: &Value) -> CmdResult {
if lang.is_none() && no_proof.is_none() {
return Ok(json!({"detect": s.prefs.detect_language}));
}
let change = |c: &mut CharProps| {
let result = crate::cmd::format::apply(s, &|c| {
if let Some(l) = &lang {
c.lang = Some(l.clone());
}
if let Some(np) = no_proof {
c.no_proof = Some(np);
}
};
let result = if whole_document {
set_document_props(s, &change)?;
json!({"scope": "document"})
} else {
crate::cmd::format::apply(s, &change)?
};
})?;
if p::bool(v, "default") == Some(true) {
s.prefs.default_language = lang;
}
Ok(result)
}
/// Apply `f` to the character formatting of every story of the document (Language dialog, "Apply
/// to the whole document"): text and paragraph marks of the body, headers, footers, notes,
/// comments, text boxes (all `Document::parts`), table cells, and the glossary's building blocks.
/// The selection is untouched; formatting waiting for typing follows.
fn set_document_props(s: &mut Session, f: &dyn Fn(&mut CharProps)) -> Result<(), CmdError> {
let stories: Vec<StoryRef> = std::iter::once(StoryRef::Body).chain(s.doc.parts.keys().map(|k| StoryRef::Part(*k))).collect();
for story in stories {
for path in s.doc.para_paths(story) {
let para = s.doc.para_mut(story, &path)?;
let len = para.len();
para.format(0, len, f)?;
f(&mut para.mark);
para.touch();
}
}
for block in &mut s.doc.glossary {
format_blocks(&mut block.blocks, f, 0);
}
if let Some(pend) = s.pending.as_mut() {
f(pend);
}
Ok(())
}
/// [`set_document_props`] for a block list outside any story (depth-limited like `para_paths`).
fn format_blocks(blocks: &mut wordcraft_doc::Blocks, f: &dyn Fn(&mut CharProps), depth: usize) {
if depth > wordcraft_doc::PARA_WALK_DEPTH {
return;
}
for blk in blocks.iter_mut() {
match Arc::make_mut(blk) {
wordcraft_doc::Block::Para(p) => {
let len = p.len();
// A range inside the paragraph can't fail; the mark is formatted regardless.
let _ = p.format(0, len, f);
f(&mut p.mark);
p.touch();
}
wordcraft_doc::Block::Table(t) => {
for row in &mut t.rows {
for cell in &mut row.cells {
format_blocks(&mut cell.blocks, f, depth + 1);
}
}
}
}
}
}
pub(super) fn macros(s: &mut Session, v: &Value) -> CmdResult {
if let Some(d) = v.get("define") {
let name = d.get("name").and_then(Value::as_str).unwrap_or("Macro").to_string();
+1 -1
View File
@@ -340,7 +340,7 @@ pub fn specs() -> Vec<CommandSpec> {
})
.params(r#"{"format": "decimal|upperRoman|lowerLetter…", "text"?: "%1.", "start"?: n}"#),
CommandSpec::new("review.language", "Language", "Review › Language", language).params(
r#"{"lang"?: "en-US|en-GB|pl-PL|fr-FR|…" (BCP 47; default en-US), "noProof"?: bool, "detect"?: bool (detect language automatically, a preference), "default"?: bool (also the document's default and the saved language for future blank documents), "scope"?: "selection" (default) | "document" (every run and paragraph mark of every story: body, headers, footers, notes, comments, text boxes, tables, glossary; one undo step, selection unchanged)}"#,
r#"{"lang"?: "en-US|en-GB|pl-PL|fr-FR|…" (BCP 47; default en-US), "noProof"?: bool, "detect"?: bool (detect language automatically, a preference), "default"?: bool (also the document's default and the saved language for future blank documents)}"#,
),
CommandSpec::new("review.language.preferred", "Set as Default Language", "File › Options › Language", preferred_language)
.params(r#"{"lang": "en-US|en-GB|pl-PL|nl-NL|…" (BCP 47)} — only the saved default proofing language, what future blank documents are written in, as Options › Language › Set as default does; the open document, the selection and the undo history don't change. Returns {"preferred": tag}."#)
-101
View File
@@ -290,104 +290,3 @@ fn inspect_removals_work_when_the_document_does_not_track_changes() {
assert!(!text.contains("Hidden"), "the hidden text is still there: {text}");
assert!(text.contains("Visible text."), "{text}");
}
/// #3518: every run and paragraph mark of a story, as (story, path, lang, noProof) rows.
fn proofing_rows(s: &Session) -> Vec<(StoryRef, Option<String>, Option<bool>)> {
let stories = std::iter::once(StoryRef::Body).chain(s.doc.parts.keys().map(|k| StoryRef::Part(*k)));
let mut out = Vec::new();
for st in stories {
for path in s.doc.para_paths(st) {
let Some(p) = s.doc.para(st, &path) else { continue };
out.push((st, p.mark.lang.clone(), p.mark.no_proof));
out.extend(p.runs.iter().map(|r| (st, r.props.lang.clone(), r.props.no_proof)));
}
}
out
}
/// A document with its text in German in the body, a table cell, a header, a footnote, a comment
/// and a text box, as a Word DOCX keeps a `w:lang` on each run, plus a glossary entry.
fn multi_story_doc() -> Session {
use wordcraft_doc::{Block, BuildingBlock, CharProps, Paragraph, PartKind, Table};
let de = || CharProps { lang: Some("de-DE".into()), ..Default::default() };
let blk = |t: &str| Arc::new(Block::Para(Paragraph::with_text(t, de())));
let mut d = Document::from_text("Guten Tag");
d.body = vec![blk("Guten Tag")];
let mut t = Table::new(1, 2, 200.0);
if let Some(c) = t.rows.first_mut().and_then(|r| r.cells.first_mut()) {
c.blocks = vec![blk("Zelle")];
}
d.body.push(Arc::new(Block::Table(t)));
d.body.push(blk("Ende"));
for kind in [PartKind::Header, PartKind::Footnote, PartKind::Comment, PartKind::TextBox] {
d.add_part(kind, vec![blk("Teil")]);
}
d.glossary.push(BuildingBlock { name: "g".into(), blocks: vec![blk("Baustein")], ..Default::default() });
Session::new(d)
}
#[test]
fn language_scope_document_sets_every_story() {
let mut s = multi_story_doc();
s.run("select.text", &json!({"text": "Guten"})).unwrap();
let (sel, before) = (s.sel.clone(), s.doc.clone());
let rows = proofing_rows(&s);
assert!(rows.len() >= 10 && rows.iter().any(|r| r.1.as_deref() == Some("de-DE")));
s.run("review.language", &json!({"lang": "fr-FR", "scope": "document"})).unwrap();
// Runs that had their own language, the paragraph marks and the empty cell: all French.
let rows = proofing_rows(&s);
assert!(rows.iter().all(|r| r.1.as_deref() == Some("fr-FR")), "{rows:?}");
for kind_story in s.doc.parts.keys() {
assert!(rows.iter().any(|r| r.0 == StoryRef::Part(*kind_story)));
}
assert!(s.doc.plain_text(StoryRef::Body).contains("Zelle"));
let g = &s.doc.glossary[0].blocks;
assert_eq!(g[0].as_para().map(|p| p.runs[0].props.lang.clone()), Some(Some("fr-FR".into())));
assert_eq!(g[0].as_para().map(|p| p.mark.lang.clone()), Some(Some("fr-FR".into())));
// The selection stays; the document is modified.
assert_eq!(s.sel, sel);
assert!(s.doc != before);
// One undo restores everything.
s.run("edit.undo", &json!({})).unwrap();
assert!(s.doc == before);
assert_eq!(s.sel, sel);
}
#[test]
fn language_scope_document_no_proof_and_pending() {
let mut s = multi_story_doc();
s.run("review.language", &json!({"noProof": true, "scope": "document"})).unwrap();
assert!(proofing_rows(&s).iter().all(|r| r.2 == Some(true)));
// The language itself wasn't asked for and stays.
assert!(proofing_rows(&s).iter().any(|r| r.1.as_deref() == Some("de-DE")));
// Formatting waiting for typing follows.
s.run("format.bold", &json!({})).unwrap();
s.run("review.language", &json!({"lang": "pl-PL", "scope": "document"})).unwrap();
assert_eq!(s.pending.as_ref().and_then(|p| p.lang.as_deref()), Some("pl-PL"));
}
#[test]
fn language_default_scope_is_selection_only() {
let mut s = multi_story_doc();
s.run("select.text", &json!({"text": "Guten"})).unwrap();
s.run("review.language", &json!({"lang": "fr-FR"})).unwrap();
let rows = proofing_rows(&s);
assert!(rows.iter().any(|r| r.1.as_deref() == Some("fr-FR")));
assert!(rows.iter().filter(|r| r.1.as_deref() == Some("fr-FR")).count() < rows.len() / 2);
let mut s = multi_story_doc();
s.run("select.text", &json!({"text": "Guten"})).unwrap();
s.run("review.language", &json!({"lang": "fr-FR", "scope": "selection"})).unwrap();
assert_eq!(proofing_rows(&s), rows);
}
#[test]
fn language_bad_scope_is_a_params_error() {
let mut s = multi_story_doc();
let before = s.doc.clone();
for bad in [json!("everything"), json!(1), json!(true), json!(["document"])] {
let e = s.run("review.language", &json!({"lang": "fr-FR", "detect": false, "scope": bad})).unwrap_err();
assert!(matches!(e, CmdError::Params(_)), "{e:?}");
}
assert!(s.doc == before);
assert!(s.prefs.detect_language);
}
@@ -208,13 +208,7 @@ fn language_dialog_default_survives_a_restart() {
use egui_kittest::kittest::Queryable;
let mut a = app();
a.ui.language = "en".into();
a.dialog = Some(Dialog::Layout(Box::new(LayoutDialog::Language(LanguageForm {
lang: "fr-FR".into(),
no_proof: false,
detect: false,
whole_document: false,
initial: ("fr-FR".into(), false),
}))));
a.dialog = Some(Dialog::Layout(Box::new(LayoutDialog::Language(LanguageForm { lang: "fr-FR".into(), no_proof: false, detect: false }))));
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 900.0)).build_ui_state(
|ui, app: &mut WordApp| {
app.logic(&ui.ctx().clone());
@@ -238,96 +232,6 @@ fn language_dialog_default_survives_a_restart() {
assert_eq!(LanguageForm::read(&b).lang, "fr-FR");
}
/// #3518: ticking "Apply to the whole document" makes OK send `scope: "document"`; unticked sends none.
#[test]
fn language_dialog_whole_document_checkbox_sends_scope() {
use egui_kittest::kittest::Queryable;
let f = LanguageForm { lang: "fr-FR".into(), no_proof: false, detect: false, whole_document: false, initial: ("fr-FR".into(), false) };
assert!(f.params().get("scope").is_none());
assert_eq!(LanguageForm { whole_document: true, ..f.clone() }.params()["scope"], "document");
let mut a = app();
a.ui.language = "en".into();
let para = wordcraft_doc::Paragraph::with_text("Header", Default::default());
let header = a.session.doc.add_part(wordcraft_doc::PartKind::Header, vec![Arc::new(wordcraft_doc::Block::Para(para))]);
a.dialog = Some(Dialog::Layout(Box::new(LayoutDialog::Language(f))));
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1440.0, 900.0)).build_ui_state(
|ui, app: &mut WordApp| {
app.logic(&ui.ctx().clone());
app.ui(ui);
},
a,
);
h.run_steps(3);
h.get_by_label("Apply to the whole document").click();
h.run_steps(3);
h.get_by_label("OK").click();
h.run_steps(3);
// The header, which the caret isn't in, took the language.
let doc = &h.state().session.doc;
let hp = doc.para(wordcraft_doc::StoryRef::Part(header), &wordcraft_doc::Path::top(0)).unwrap();
assert_eq!(hp.runs.first().and_then(|r| r.props.lang.as_deref()), Some("fr-FR"));
}
/// "Plain text, then a Do-not-check run", the caret in the plain text.
fn proofing_mix_app() -> WordApp {
let mut a = app();
a.run("text.insert", json!({"text": "Plain code"})).unwrap();
a.run("select.text", json!({"text": "code"})).unwrap();
a.run("review.language", json!({"lang": "de-DE", "noProof": true})).unwrap();
a.run("select.text", json!({"text": "Plain"})).unwrap();
a.run("review.language", json!({"lang": "en-US", "noProof": false})).unwrap();
a.run("select.text", json!({"text": "Plain"})).unwrap();
a
}
fn run_proofing(a: &mut WordApp, text: &str) -> (Option<String>, Option<bool>) {
a.run("select.text", json!({"text": text})).unwrap();
let st = a.run("format.state", json!({})).unwrap();
(st["lang"].as_str().map(String::from), st["noProof"].as_bool())
}
/// #3518: whole-document OK sends only what the user changed.
#[test]
fn language_dialog_whole_document_sends_only_changes() {
// Changing only the language leaves the Do-not-check run alone.
let mut a = proofing_mix_app();
let mut f = LanguageForm::read(&a);
f.whole_document = true;
f.lang = "en-GB".into();
let p = f.params();
assert!(p.get("noProof").is_none());
a.run("review.language", p).unwrap();
assert_eq!(run_proofing(&mut a, "code"), (Some("en-GB".into()), Some(true)));
assert_eq!(run_proofing(&mut a, "Plain"), (Some("en-GB".into()), Some(false)));
// Changing only Do-not-check leaves each run's language alone.
let mut a = proofing_mix_app();
let mut f = LanguageForm::read(&a);
f.whole_document = true;
f.no_proof = true;
let p = f.params();
assert!(p.get("lang").is_none());
a.run("review.language", p).unwrap();
assert_eq!(run_proofing(&mut a, "code"), (Some("de-DE".into()), Some(true)));
assert_eq!(run_proofing(&mut a, "Plain"), (Some("en-US".into()), Some(true)));
// No edits: the caret's language, no noProof (never the engine's en-US default).
let mut a = proofing_mix_app();
let mut f = LanguageForm::read(&a);
f.lang = "fr-FR".into();
f.initial.0 = "fr-FR".into();
f.whole_document = true;
let p = f.params();
assert_eq!(p["lang"], "fr-FR");
assert!(p.get("noProof").is_none());
a.run("review.language", p).unwrap();
assert_eq!(run_proofing(&mut a, "code"), (Some("fr-FR".into()), Some(true)));
// Selection mode is unchanged: both are sent.
f.whole_document = false;
assert_eq!(f.params()["noProof"], false);
}
/// #407: Envelopes starts from the selected address; Add to Document puts an envelope
/// section before the letter without asking to save (nothing is replaced).
#[test]
@@ -320,11 +320,6 @@ pub struct LanguageForm {
pub lang: String,
pub no_proof: bool,
pub detect: bool,
/// "Apply to the whole document": OK sends `scope: "document"` (every story), not the selection.
pub whole_document: bool,
/// The language and Do-not-check state the dialog opened with, to tell what the user changed.
#[serde(skip)]
pub initial: (String, bool),
}
impl LanguageForm {
@@ -333,27 +328,12 @@ impl LanguageForm {
let style = s.doc.para_at(&s.sel.focus).and_then(|p| p.props.style.as_deref());
let here = s.doc.styles.resolve_char(style, &s.typing_props());
let lang = here.lang.unwrap_or_else(|| "en-US".into());
LanguageForm { initial: (lang.clone(), here.no_proof), lang, no_proof: here.no_proof, detect: s.prefs.detect_language, whole_document: false }
LanguageForm { lang, no_proof: here.no_proof, detect: s.prefs.detect_language }
}
/// `review.language` parameters for OK.
pub fn params(&self) -> Value {
if !self.whole_document {
return json!({"lang": self.lang, "noProof": self.no_proof, "detect": self.detect});
}
// Whole document: the caret's values must not flatten what differs elsewhere, so send only
// what was changed. With no edits at all the language goes (never the engine's en-US default).
let (lang_changed, proof_changed) = (self.lang != self.initial.0, self.no_proof != self.initial.1);
let mut v = json!({"scope": "document", "detect": self.detect});
if let Some(m) = v.as_object_mut() {
if lang_changed || !proof_changed {
m.insert("lang".into(), json!(self.lang));
}
if proof_changed {
m.insert("noProof".into(), json!(self.no_proof));
}
}
v
json!({"lang": self.lang, "noProof": self.no_proof, "detect": self.detect})
}
}
@@ -363,7 +343,7 @@ pub(super) fn language_name(tag: &str) -> String {
}
pub(super) fn language_ui(app: &mut WordApp, ui: &mut Ui, f: &mut LanguageForm) -> bool {
ui.label(if f.whole_document { tl!("Mark the whole document as:") } else { tl!("Mark selected text as:") });
ui.label(tl!("Mark selected text as:"));
let list = crate::ribbon::language_list(&f.lang);
egui::Frame::NONE.stroke(egui::Stroke::new(1.0, crate::theme::Tokens::get(ui.ctx()).border_strong)).inner_margin(4.0).show(ui, |ui| {
egui::ScrollArea::vertical().max_height(220.0).min_scrolled_height(220.0).show(ui, |ui| {
@@ -380,7 +360,6 @@ pub(super) fn language_ui(app: &mut WordApp, ui: &mut Ui, f: &mut LanguageForm)
ui.add_space(4.0);
ui.checkbox(&mut f.no_proof, tl!("Do not check spelling or grammar"));
ui.checkbox(&mut f.detect, tl!("Detect language automatically"));
ui.checkbox(&mut f.whole_document, tl!("Apply to the whole document"));
ui.add_space(4.0);
let set_default = ui.button(tl!("Set As Default")).clicked();
if set_default && run(app, "review.language", json!({"lang": f.lang, "default": true, "detect": f.detect})) {
+2 -3
View File
File diff suppressed because one or more lines are too long