diff --git a/crates/proof/src/cs/mod.rs b/crates/proof/src/cs/mod.rs index 55e21aef6..1d72dc913 100644 --- a/crates/proof/src/cs/mod.rs +++ b/crates/proof/src/cs/mod.rs @@ -18,7 +18,7 @@ pub use suggest::suggest; const NAMES: &[&str] = &["cs_CZ", "cs-CZ", "cs"]; /// The Czech dictionary, found on first use (see [`DictSlot`]). -static DICT: DictSlot = DictSlot::new(NAMES, "Czech"); +pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Czech"); /// The Czech dictionary: the installed Hunspell `cs_CZ` (read on first use), one given with /// [`install_dictionary`], or an empty one when none is available (Czech spelling is off @@ -33,18 +33,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { DICT.install(aff, dic) } -/// Forget the loaded dictionary, so the next check looks for an installed one again (after -/// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed). -pub fn reload_dictionary() { - DICT.reload(); -} - -/// The `.dic` file of the installed Czech dictionary (File › Options › Proofing shows it); -/// `None` when there is none, or when one given with [`install_dictionary`] is in use. -pub fn installed_path() -> Option { - DICT.installed_path() -} - /// Is a Czech dictionary available (spelling is checked)? pub fn has_dictionary() -> bool { DICT.has_dictionary() diff --git a/crates/proof/src/es/mod.rs b/crates/proof/src/es/mod.rs index 253642c83..e16e9d6bd 100644 --- a/crates/proof/src/es/mod.rs +++ b/crates/proof/src/es/mod.rs @@ -25,10 +25,10 @@ use crate::slot::DictSlot; pub use suggest::suggest; /// The dictionary of each variety, found on first use (see [`DictSlot`]). -static SPAIN_DICT: DictSlot = DictSlot::new(SPAIN, "Spanish (Spain)"); -static MEXICO_DICT: DictSlot = DictSlot::new(MEXICO, "Spanish (Mexico)"); -static LATIN_AMERICA_DICT: DictSlot = DictSlot::new(LATIN_AMERICA, "Spanish (Latin America)"); -static URUGUAY_DICT: DictSlot = DictSlot::new(URUGUAY, "Spanish (Uruguay)"); +pub(crate) static SPAIN_DICT: DictSlot = DictSlot::new(SPAIN, "Spanish (Spain)"); +pub(crate) static MEXICO_DICT: DictSlot = DictSlot::new(MEXICO, "Spanish (Mexico)"); +pub(crate) static LATIN_AMERICA_DICT: DictSlot = DictSlot::new(LATIN_AMERICA, "Spanish (Latin America)"); +pub(crate) static URUGUAY_DICT: DictSlot = DictSlot::new(URUGUAY, "Spanish (Uruguay)"); /// File names of a Spanish Hunspell dictionary for Spain, most specific first (`es_ANY` is the /// generic one RLA-ES publishes). @@ -72,21 +72,6 @@ pub fn install_dictionary(lang: ProofLang, aff: &str, dic: &str) -> Result<(), S slot(lang).install(aff, dic) } -/// Forget every variety's loaded dictionary, so the next check looks for an installed one again -/// (after [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed). -pub fn reload_dictionary() { - for slot in [&SPAIN_DICT, &MEXICO_DICT, &LATIN_AMERICA_DICT, &URUGUAY_DICT] { - slot.reload(); - } -} - -/// The `.dic` file of the Hunspell dictionary `lang`'s variety finds in the dictionaries folder -/// or the system locations now (File › Options › Proofing shows it); `None` when there is none, -/// or when one given with [`install_dictionary`] is in use. -pub fn installed_path(lang: ProofLang) -> Option { - slot(lang).installed_path() -} - /// Is a dictionary available for `lang`'s variety (spelling is checked)? pub fn has_dictionary(lang: ProofLang) -> bool { slot(lang).has_dictionary() diff --git a/crates/proof/src/hr/mod.rs b/crates/proof/src/hr/mod.rs index 6372a47d2..036b72350 100644 --- a/crates/proof/src/hr/mod.rs +++ b/crates/proof/src/hr/mod.rs @@ -20,7 +20,7 @@ pub use suggest::suggest; const NAMES: &[&str] = &["hr_HR", "hr-HR", "hr"]; /// The Croatian dictionary, found on first use (see [`DictSlot`]). -static DICT: DictSlot = DictSlot::new(NAMES, "Croatian"); +pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Croatian"); /// The Croatian dictionary: the installed Hunspell `hr_HR` (read on first use), one given with /// [`install_dictionary`], or an empty one when none is available (Croatian spelling is off @@ -35,18 +35,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { DICT.install(aff, dic) } -/// Forget the loaded dictionary, so the next check looks for an installed one again (after -/// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed). -pub fn reload_dictionary() { - DICT.reload(); -} - -/// The `.dic` file of the installed Croatian dictionary (File › Options › Proofing shows it); -/// `None` when there is none, or when one given with [`install_dictionary`] is in use. -pub fn installed_path() -> Option { - DICT.installed_path() -} - /// Is a Croatian dictionary available (spelling is checked)? pub fn has_dictionary() -> bool { DICT.has_dictionary() diff --git a/crates/proof/src/id/mod.rs b/crates/proof/src/id/mod.rs index 65b820dfc..f1e44df45 100644 --- a/crates/proof/src/id/mod.rs +++ b/crates/proof/src/id/mod.rs @@ -21,7 +21,7 @@ pub use suggest::suggest; const NAMES: &[&str] = &["id_ID", "id-ID", "id", "in_ID"]; /// The Indonesian dictionary, found on first use (see [`DictSlot`]). -static DICT: DictSlot = DictSlot::new(NAMES, "Indonesian"); +pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Indonesian"); /// The Indonesian dictionary: the installed Hunspell `id_ID` (read on first use), one given with /// [`install_dictionary`], or an empty one when none is available (Indonesian spelling is off @@ -36,18 +36,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { DICT.install(aff, dic) } -/// Forget the loaded dictionary, so the next check looks for an installed one again (after -/// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed). -pub fn reload_dictionary() { - DICT.reload(); -} - -/// The `.dic` file of the installed Indonesian dictionary (File › Options › Proofing shows it); -/// `None` when there is none, or when one given with [`install_dictionary`] is in use. -pub fn installed_path() -> Option { - DICT.installed_path() -} - /// Is an Indonesian dictionary available (spelling is checked)? pub fn has_dictionary() -> bool { DICT.has_dictionary() diff --git a/crates/proof/src/it/mod.rs b/crates/proof/src/it/mod.rs index 168883e17..1e606ad74 100644 --- a/crates/proof/src/it/mod.rs +++ b/crates/proof/src/it/mod.rs @@ -29,7 +29,7 @@ pub use suggest::suggest; const NAMES: &[&str] = &["it_IT", "it_CH", "it-IT", "it"]; /// The Italian dictionary, found on first use (see [`DictSlot`]). -static DICT: DictSlot = DictSlot::new(NAMES, "Italian"); +pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Italian"); /// The Italian dictionary: the installed Hunspell `it_IT` (read on first use), one given with /// [`install_dictionary`], or an empty one when none is available (Italian spelling is off @@ -44,18 +44,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { DICT.install(aff, dic) } -/// Forget the loaded dictionary, so the next check looks for an installed one again (after -/// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed). -pub fn reload_dictionary() { - DICT.reload(); -} - -/// The `.dic` file of the installed Italian dictionary (File › Options › Proofing shows it); -/// `None` when there is none, or when one given with [`install_dictionary`] is in use. -pub fn installed_path() -> Option { - DICT.installed_path() -} - /// Is an Italian dictionary available (spelling is checked)? pub fn has_dictionary() -> bool { DICT.has_dictionary() diff --git a/crates/proof/src/lib.rs b/crates/proof/src/lib.rs index 4bf4da78b..0c51e2362 100644 --- a/crates/proof/src/lib.rs +++ b/crates/proof/src/lib.rs @@ -159,57 +159,150 @@ pub fn ignore_word(w: &str) { } } +/// A language proofed against an installed Hunspell dictionary: all the functions below need to +/// know about it, so a language joins with one row in [`LANGUAGES`]. +struct Language { + lang: ProofLang, + slot: &'static dyn slot::Slot, + /// The spelling mark's message, in the language. + message: &'static str, + is_correct: fn(&str, ProofLang, &Options) -> bool, + suggest: fn(&str, usize, ProofLang) -> Vec, + /// Grammar issues; the flag says whether the first word must have a capital (not in a list + /// item). + grammar: fn(&str, ProofLang, bool) -> Vec, +} + +/// Every language proofed against a Hunspell dictionary, in the order File › Options › Proofing +/// lists them. English has a built-in word list and is handled apart. +static LANGUAGES: [Language; 12] = [ + Language { + lang: ProofLang::Pl, + slot: &pl::DICT, + message: "Możliwy błąd pisowni", + is_correct: |w, _, o| pl::is_correct_with_options(w, o), + suggest: |w, n, _| pl::suggest(w, n), + grammar: |t, _, c| pl::grammar::check_with(t, c), + }, + Language { + lang: ProofLang::Pt, + slot: &pt::DICT, + message: "Possível erro de ortografia", + is_correct: |w, _, o| pt::is_correct_with_options(w, o), + suggest: |w, n, _| pt::suggest(w, n), + grammar: |t, _, c| pt::grammar::check_with(t, c), + }, + Language { + lang: ProofLang::It, + slot: &it::DICT, + message: "Possibile errore di ortografia", + is_correct: |w, _, o| it::is_correct_with_options(w, o), + suggest: |w, n, _| it::suggest(w, n), + grammar: |t, _, c| it::grammar::check_with(t, c), + }, + Language { + lang: ProofLang::Hr, + slot: &hr::DICT, + message: "Moguća pravopisna pogreška", + is_correct: |w, _, o| hr::is_correct_with_options(w, o), + suggest: |w, n, _| hr::suggest(w, n), + grammar: |t, _, c| hr::grammar::check_with(t, c), + }, + Language { + lang: ProofLang::Tr, + slot: &tr::DICT, + message: "Olası yazım hatası", + is_correct: |w, _, o| tr::is_correct_with_options(w, o), + suggest: |w, n, _| tr::suggest(w, n), + grammar: |t, _, c| tr::grammar::check_with(t, c), + }, + Language { + lang: ProofLang::Ru, + slot: &ru::DICT, + message: "Возможна орфографическая ошибка", + is_correct: |w, _, o| ru::is_correct_with_options(w, o), + suggest: |w, n, _| ru::suggest(w, n), + grammar: |t, _, c| ru::grammar::check_with(t, c), + }, + // Czech, Indonesian and Spanish have no sentence-capital rule, so a list item needs no + // exception. + Language { + lang: ProofLang::Cs, + slot: &cs::DICT, + message: "Možná pravopisná chyba", + is_correct: |w, _, o| cs::is_correct_with_options(w, o), + suggest: |w, n, _| cs::suggest(w, n), + grammar: |t, _, _| cs::grammar::check(t), + }, + Language { + lang: ProofLang::Id, + slot: &id::DICT, + message: "Kemungkinan salah eja", + is_correct: |w, _, o| id::is_correct_with_options(w, o), + suggest: |w, n, _| id::suggest(w, n), + grammar: |t, _, _| id::grammar::check(t), + }, + Language { + lang: ProofLang::Es, + slot: &es::SPAIN_DICT, + message: "Posible error ortográfico", + is_correct: es::is_correct_with_options, + suggest: es::suggest, + grammar: |t, l, _| es::grammar::check(t, l), + }, + Language { + lang: ProofLang::EsMx, + slot: &es::MEXICO_DICT, + message: "Posible error ortográfico", + is_correct: es::is_correct_with_options, + suggest: es::suggest, + grammar: |t, l, _| es::grammar::check(t, l), + }, + Language { + lang: ProofLang::Es419, + slot: &es::LATIN_AMERICA_DICT, + message: "Posible error ortográfico", + is_correct: es::is_correct_with_options, + suggest: es::suggest, + grammar: |t, l, _| es::grammar::check(t, l), + }, + Language { + lang: ProofLang::EsUy, + slot: &es::URUGUAY_DICT, + message: "Posible error ortográfico", + is_correct: es::is_correct_with_options, + suggest: es::suggest, + grammar: |t, l, _| es::grammar::check(t, l), + }, +]; + +/// `lang`'s row in [`LANGUAGES`]; `None` for English and the languages WordCraft doesn't proof. +fn language(lang: ProofLang) -> Option<&'static Language> { + LANGUAGES.iter().find(|l| l.lang == lang) +} + /// Search `dir` for Hunspell dictionaries before the system locations (File › Options › /// Proofing › Dictionaries folder, #1063); `None` clears it. The dictionaries load again from /// the new search path, and text is checked again. pub fn set_dictionaries_folder(dir: Option) { hunspell::set_dictionary_dir(dir); - pl::reload_dictionary(); - pt::reload_dictionary(); - it::reload_dictionary(); - hr::reload_dictionary(); - tr::reload_dictionary(); - ru::reload_dictionary(); - cs::reload_dictionary(); - id::reload_dictionary(); - es::reload_dictionary(); + for l in &LANGUAGES { + l.slot.reload(); + } DICTIONARY_GENERATION.fetch_add(1, Ordering::Relaxed); } /// Each language that reads an installed Hunspell dictionary, with the `.dic` file found for it /// now (`None`: not installed, so the language has no spelling marks). pub fn installed_dictionaries() -> Vec<(ProofLang, Option)> { - vec![ - (ProofLang::Pl, pl::installed_path()), - (ProofLang::Pt, pt::installed_path()), - (ProofLang::It, it::installed_path()), - (ProofLang::Hr, hr::installed_path()), - (ProofLang::Tr, tr::installed_path()), - (ProofLang::Ru, ru::installed_path()), - (ProofLang::Cs, cs::installed_path()), - (ProofLang::Id, id::installed_path()), - (ProofLang::Es, es::installed_path(ProofLang::Es)), - (ProofLang::EsMx, es::installed_path(ProofLang::EsMx)), - (ProofLang::Es419, es::installed_path(ProofLang::Es419)), - (ProofLang::EsUy, es::installed_path(ProofLang::EsUy)), - ] + LANGUAGES.iter().map(|l| (l.lang, l.slot.installed_path())).collect() } /// Why the dictionary found for `lang` failed to load (unsupported encoding, too large, a parse /// error), if it was read and failed. Doesn't read anything itself: a dictionary nobody has /// checked yet reports `None` (#2830). pub fn dictionary_load_error(lang: ProofLang) -> Option { - let key = match lang { - ProofLang::Pl => "pl_PL", - ProofLang::Pt => "pt_BR", - ProofLang::It => "it_IT", - ProofLang::Hr => "hr_HR", - ProofLang::Tr => "tr_TR", - ProofLang::Id => "id_ID", - ProofLang::Es | ProofLang::EsMx | ProofLang::Es419 | ProofLang::EsUy => es::dictionary_name(lang), - _ => return None, - }; - hunspell::load_error(key).map(|(_, reason)| reason) + hunspell::load_error(language(lang)?.slot.name()).map(|(_, reason)| reason) } /// A number that changes whenever a word is added to or removed from the user dictionary, or the @@ -440,36 +533,13 @@ pub fn unreadable_dictionary(lang: Option<&str>) -> Option<(&'static str, std::p /// Did the Hunspell dictionary of `lang` load? `None` for languages that use none (English). fn dictionary_loaded(lang: ProofLang) -> Option { - Some(match lang { - ProofLang::Pl => pl::has_dictionary(), - ProofLang::Pt => pt::has_dictionary(), - ProofLang::It => it::has_dictionary(), - ProofLang::Hr => hr::has_dictionary(), - ProofLang::Tr => tr::has_dictionary(), - ProofLang::Ru => ru::has_dictionary(), - ProofLang::Cs => cs::has_dictionary(), - ProofLang::Id => id::has_dictionary(), - l if l.is_spanish() => es::has_dictionary(l), - _ => return None, - }) + language(lang).map(|l| l.slot.has_dictionary()) } /// The Hunspell name `lang` needs when `installed` is false (the first name its module looks for; /// a Spanish variety's is its own: `es_ES`, `es_MX`, `es_419`, `es_UY`). fn dictionary_needed(lang: ProofLang, installed: bool) -> Option<&'static str> { - match lang { - _ if installed => None, - ProofLang::Pl => Some("pl_PL"), - ProofLang::Pt => Some("pt_BR"), - ProofLang::It => Some("it_IT"), - ProofLang::Hr => Some("hr_HR"), - ProofLang::Tr => Some("tr_TR"), - ProofLang::Ru => Some("ru_RU"), - ProofLang::Cs => Some("cs_CZ"), - ProofLang::Id => Some("id_ID"), - l if l.is_spanish() => Some(es::dictionary_name(l)), - _ => None, - } + language(lang).filter(|_| !installed).map(|l| l.slot.name()) } /// Is `word` spelled correctly in the run's proofing language? A language without a dictionary @@ -739,18 +809,7 @@ fn spelling_issue(text: &str, a: usize, b: usize, lang: ProofLang, opts: &Option if (opts.ignore_internet && internet) || (lang.is_english() && vi::is_vietnamese_word(w)) || is_correct_in_with(w, lang, opts) { return None; } - let message = match lang { - ProofLang::Pl => "Możliwy błąd pisowni", - ProofLang::Hr => "Moguća pravopisna pogreška", - ProofLang::Pt => "Possível erro de ortografia", - ProofLang::It => "Possibile errore di ortografia", - ProofLang::Tr => "Olası yazım hatası", - ProofLang::Ru => "Возможна орфографическая ошибка", - ProofLang::Cs => "Možná pravopisná chyba", - ProofLang::Id => "Kemungkinan salah eja", - ProofLang::Es | ProofLang::EsMx | ProofLang::Es419 | ProofLang::EsUy => "Posible error ortográfico", - _ => "Possible spelling mistake", - }; + let message = language(lang).map_or("Possible spelling mistake", |l| l.message); Some(Issue { start: a, end: b, kind: IssueKind::Spelling, message: message.into(), suggestions: Vec::new(), lang }) } @@ -774,40 +833,21 @@ fn composed(word: &str) -> std::borrow::Cow<'_, str> { /// checked as its composed form ([`composed`]). pub fn is_correct_in_with(word: &str, lang: ProofLang, opts: &Options) -> bool { let word = &*composed(word); - match lang { - ProofLang::En | ProofLang::EnGb | ProofLang::EnCa => is_correct_english(word, lang, opts), - ProofLang::Pl => accepted_by_options(word, opts) || pl::is_correct_with_options(word, opts), - ProofLang::Hr => accepted_by_options(word, opts) || hr::is_correct_with_options(word, opts), - ProofLang::Pt => accepted_by_options(word, opts) || pt::is_correct_with_options(word, opts), - ProofLang::It => accepted_by_options(word, opts) || it::is_correct_with_options(word, opts), - ProofLang::Tr => accepted_by_options(word, opts) || tr::is_correct_with_options(word, opts), - ProofLang::Ru => accepted_by_options(word, opts) || ru::is_correct_with_options(word, opts), - ProofLang::Es | ProofLang::EsMx | ProofLang::Es419 | ProofLang::EsUy => { - accepted_by_options(word, opts) || es::is_correct_with_options(word, lang, opts) - } - ProofLang::Cs => accepted_by_options(word, opts) || cs::is_correct_with_options(word, opts), - ProofLang::Id => accepted_by_options(word, opts) || id::is_correct_with_options(word, opts), - ProofLang::Bg | ProofLang::De | ProofLang::Other => true, + if lang.is_english() { + return is_correct_english(word, lang, opts); } + // A language WordCraft doesn't proof (Bulgarian, German, any other) has no spelling marks. + language(lang).is_none_or(|l| accepted_by_options(word, opts) || (l.is_correct)(word, lang, opts)) } /// Spelling suggestions for a word misspelled in `lang` (best first, at most `max`), composed /// ([`composed`]) like the dictionaries' words. pub fn suggest_in(word: &str, max: usize, lang: ProofLang) -> Vec { let word = &*composed(word); - match lang { - ProofLang::En | ProofLang::EnGb | ProofLang::EnCa => suggest_english(word, max, lang), - ProofLang::Pl => pl::suggest(word, max), - ProofLang::Hr => hr::suggest(word, max), - ProofLang::Pt => pt::suggest(word, max), - ProofLang::It => it::suggest(word, max), - ProofLang::Tr => tr::suggest(word, max), - ProofLang::Ru => ru::suggest(word, max), - ProofLang::Cs => cs::suggest(word, max), - ProofLang::Id => id::suggest(word, max), - ProofLang::Es | ProofLang::EsMx | ProofLang::Es419 | ProofLang::EsUy => es::suggest(word, max, lang), - ProofLang::Bg | ProofLang::De | ProofLang::Other => Vec::new(), + if lang.is_english() { + return suggest_english(word, max, lang); } + language(lang).map_or_else(Vec::new, |l| (l.suggest)(word, max, lang)) } /// Grammar issues in a paragraph of text in `lang`: the English rules, the Polish ones @@ -824,18 +864,7 @@ fn grammar_in(text: &str, lang: ProofLang, capital_first: bool) -> Vec { match lang { ProofLang::En => grammar_en(text, capital_first), ProofLang::EnGb | ProofLang::EnCa => grammar_en(text, capital_first).into_iter().map(|i| Issue { lang, ..i }).collect(), - ProofLang::Pl => pl::grammar::check_with(text, capital_first), - ProofLang::Hr => hr::grammar::check_with(text, capital_first), - ProofLang::Pt => pt::grammar::check_with(text, capital_first), - ProofLang::It => it::grammar::check_with(text, capital_first), - ProofLang::Tr => tr::grammar::check_with(text, capital_first), - ProofLang::Ru => ru::grammar::check_with(text, capital_first), - // Czech has no sentence-capital rule, so a list item needs no exception. - ProofLang::Cs => cs::grammar::check(text), - ProofLang::Id => id::grammar::check(text), - // Spanish has no sentence-capital rule either. - ProofLang::Es | ProofLang::EsMx | ProofLang::Es419 | ProofLang::EsUy => es::grammar::check(text, lang), - ProofLang::Bg | ProofLang::De | ProofLang::Other => Vec::new(), + _ => language(lang).map_or_else(Vec::new, |l| (l.grammar)(text, lang, capital_first)), } } @@ -1133,6 +1162,33 @@ mod tests { assert!(is_correct_in("дом", ProofLang::Ru) && !is_correct_in("дум", ProofLang::Ru)); } + /// A dictionary that fails to load says why in File › Options › Proofing in every language + /// that reads one; Russian and Czech were missing from the list that named their dictionaries + /// there. + #[test] + fn every_dictionary_language_reports_its_load_error() { + let _guard = DICTIONARY_TESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner); + for (lang, _) in installed_dictionaries() { + let name = dictionary_needed(lang, false).unwrap(); + let reason = format!("zq unreadable {name}"); + let _ = hunspell::note_load::<()>(name, std::path::Path::new("zq.dic"), Err(reason.clone())); + assert_eq!(dictionary_load_error(lang), Some(reason), "{lang:?}"); + hunspell::clear_load_error(name); + } + } + + /// Every proofed language other than English has a row in the language table, and only those. + #[test] + fn the_language_table_covers_the_proofed_languages() { + for l in ProofLang::PROOFED { + assert_eq!(language(l).is_some(), !l.is_english(), "{l:?}"); + } + for (i, row) in LANGUAGES.iter().enumerate() { + assert!(row.lang.is_proofed(), "{:?}", row.lang); + assert!(LANGUAGES.iter().skip(i + 1).all(|other| other.lang != row.lang), "{:?} twice", row.lang); + } + } + /// #1741: a list item may be a fragment, so its first word needs no capital in any language /// with the rule; a plain paragraph's does, and later sentences in the item are still checked. #[test] diff --git a/crates/proof/src/pl/mod.rs b/crates/proof/src/pl/mod.rs index 4d79986d9..30d6d966f 100644 --- a/crates/proof/src/pl/mod.rs +++ b/crates/proof/src/pl/mod.rs @@ -28,7 +28,7 @@ pub use suggest::suggest; const NAMES: &[&str] = &["pl_PL", "pl-PL", "pl"]; /// The Polish dictionary, found on first use (see [`DictSlot`]). -static DICT: DictSlot = DictSlot::new(NAMES, "Polish"); +pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Polish"); /// The Polish dictionary: the installed Hunspell `pl_PL` (read on first use), one given with /// [`install_dictionary`], or an empty one when none is available (Polish spelling is off @@ -43,18 +43,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { DICT.install(aff, dic) } -/// Forget the loaded dictionary, so the next check looks for an installed one again (after -/// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed). -pub fn reload_dictionary() { - DICT.reload(); -} - -/// The `.dic` file of the installed Polish dictionary (File › Options › Proofing shows it); -/// `None` when there is none, or when one given with [`install_dictionary`] is in use. -pub fn installed_path() -> Option { - DICT.installed_path() -} - /// Is a Polish dictionary available (spelling is checked)? pub fn has_dictionary() -> bool { DICT.has_dictionary() diff --git a/crates/proof/src/pt/mod.rs b/crates/proof/src/pt/mod.rs index 860cf4579..0110a8c18 100644 --- a/crates/proof/src/pt/mod.rs +++ b/crates/proof/src/pt/mod.rs @@ -27,7 +27,7 @@ pub use suggest::suggest; const NAMES: &[&str] = &["pt_BR", "pt-BR", "pt"]; /// The Portuguese dictionary, found on first use (see [`DictSlot`]). -static DICT: DictSlot = DictSlot::new(NAMES, "Portuguese"); +pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Portuguese"); /// The Portuguese dictionary: the installed Hunspell `pt_BR` (read on first use), one given with /// [`install_dictionary`], or an empty one when none is available (Portuguese spelling is off @@ -42,18 +42,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { DICT.install(aff, dic) } -/// Forget the loaded dictionary, so the next check looks for an installed one again (after -/// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed). -pub fn reload_dictionary() { - DICT.reload(); -} - -/// The `.dic` file of the installed Portuguese dictionary (File › Options › Proofing shows it); -/// `None` when there is none, or when one given with [`install_dictionary`] is in use. -pub fn installed_path() -> Option { - DICT.installed_path() -} - /// Is a Portuguese dictionary available (spelling is checked)? pub fn has_dictionary() -> bool { DICT.has_dictionary() diff --git a/crates/proof/src/ru/mod.rs b/crates/proof/src/ru/mod.rs index e46bf305f..67be70858 100644 --- a/crates/proof/src/ru/mod.rs +++ b/crates/proof/src/ru/mod.rs @@ -39,7 +39,7 @@ pub use suggest::suggest; const NAMES: &[&str] = &["ru_RU", "ru-RU", "ru"]; /// The Russian dictionary, found on first use (see [`DictSlot`]). -static DICT: DictSlot = DictSlot::new(NAMES, "Russian"); +pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Russian"); /// The Russian dictionary: the installed Hunspell `ru_RU` (read on first use), one given with /// [`install_dictionary`], or an empty one when none is available (Russian spelling is off @@ -54,18 +54,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { DICT.install(aff, dic) } -/// Forget the loaded dictionary, so the next check looks for an installed one again (after -/// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed). -pub fn reload_dictionary() { - DICT.reload(); -} - -/// The `.dic` file of the installed Russian dictionary (File › Options › Proofing shows it); -/// `None` when there is none, or when one given with [`install_dictionary`] is in use. -pub fn installed_path() -> Option { - DICT.installed_path() -} - /// Is a Russian dictionary available (spelling is checked)? pub fn has_dictionary() -> bool { DICT.has_dictionary() diff --git a/crates/proof/src/slot.rs b/crates/proof/src/slot.rs index 3c0f2bd35..8b90a94a9 100644 --- a/crates/proof/src/slot.rs +++ b/crates/proof/src/slot.rs @@ -30,6 +30,37 @@ impl Dictionary for AffixDict { } } +/// What every language's slot answers, whatever its dictionary type (the language table in +/// lib.rs holds them side by side). +pub(crate) trait Slot: Sync { + /// See [`DictSlot::name`]. + fn name(&self) -> &'static str; + /// See [`DictSlot::installed_path`]. + fn installed_path(&self) -> Option; + /// See [`DictSlot::has_dictionary`]. + fn has_dictionary(&self) -> bool; + /// See [`DictSlot::reload`]. + fn reload(&self); +} + +impl Slot for DictSlot { + fn name(&self) -> &'static str { + DictSlot::name(self) + } + + fn installed_path(&self) -> Option { + DictSlot::installed_path(self) + } + + fn has_dictionary(&self) -> bool { + DictSlot::has_dictionary(self) + } + + fn reload(&self) { + DictSlot::reload(self); + } +} + /// The dictionary in use and whether it came from [`DictSlot::install`] rather than a file on /// disk (it then has no file to report, even if a matching file also happens to exist): one lock, /// so a reload and an install can't interleave their two fields. diff --git a/crates/proof/src/tr/mod.rs b/crates/proof/src/tr/mod.rs index eb5f0ea52..f247336be 100644 --- a/crates/proof/src/tr/mod.rs +++ b/crates/proof/src/tr/mod.rs @@ -29,7 +29,7 @@ pub use suggest::suggest; const NAMES: &[&str] = &["tr_TR", "tr-TR", "tr"]; /// The Turkish dictionary, found on first use (see [`DictSlot`]). -static DICT: DictSlot = DictSlot::new(NAMES, "Turkish"); +pub(crate) static DICT: DictSlot = DictSlot::new(NAMES, "Turkish"); /// The Turkish dictionary: the installed Hunspell `tr_TR` (read on first use), one given with /// [`install_dictionary`], or an empty one when none is available. @@ -43,17 +43,6 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { DICT.install(aff, dic) } -/// Forget the loaded dictionary, so the next check looks for an installed one again. -pub fn reload_dictionary() { - DICT.reload(); -} - -/// The `.dic` file of the installed Turkish dictionary (File › Options › Proofing shows it); -/// `None` when there is none, or when one given with [`install_dictionary`] is in use. -pub fn installed_path() -> Option { - DICT.installed_path() -} - /// Is a Turkish dictionary available? pub fn has_dictionary() -> bool { DICT.has_dictionary()