mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-06 10:19:59 -06:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ebfc3c2a7 | ||
|
|
c09da43475 |
+2
-1
File diff suppressed because one or more lines are too long
@@ -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::{PARA_WALK_DEPTH, walk_between};
|
||||
pub use text_walk::{count_chars, count_chars_no_spaces, count_words};
|
||||
pub(crate) use text_walk::walk_between;
|
||||
pub use text_walk::{PARA_WALK_DEPTH, count_chars, count_chars_no_spaces, count_words};
|
||||
|
||||
/// A document.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -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(crate) const PARA_WALK_DEPTH: usize = 16;
|
||||
pub 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])> {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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
|
||||
@@ -14,8 +15,15 @@ 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.
|
||||
/// Nothing at all sets US English, as before. `scope: "document"` sets it for every story.
|
||||
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)
|
||||
@@ -45,20 +53,75 @@ 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 result = crate::cmd::format::apply(s, &|c| {
|
||||
let change = |c: &mut CharProps| {
|
||||
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();
|
||||
|
||||
@@ -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)}"#,
|
||||
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)}"#,
|
||||
),
|
||||
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}."#)
|
||||
|
||||
@@ -290,3 +290,104 @@ 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,7 +208,13 @@ 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 }))));
|
||||
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),
|
||||
}))));
|
||||
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());
|
||||
@@ -232,6 +238,96 @@ 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,6 +320,11 @@ 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 {
|
||||
@@ -328,12 +333,27 @@ 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 { lang, no_proof: here.no_proof, detect: s.prefs.detect_language }
|
||||
LanguageForm { initial: (lang.clone(), here.no_proof), lang, no_proof: here.no_proof, detect: s.prefs.detect_language, whole_document: false }
|
||||
}
|
||||
|
||||
/// `review.language` parameters for OK.
|
||||
pub fn params(&self) -> Value {
|
||||
json!({"lang": self.lang, "noProof": self.no_proof, "detect": self.detect})
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,7 +363,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(tl!("Mark selected text as:"));
|
||||
ui.label(if f.whole_document { tl!("Mark the whole document as:") } else { 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| {
|
||||
@@ -360,6 +380,7 @@ 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})) {
|
||||
|
||||
+3
-2
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user