mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-03 00:40:33 -06:00
Proofing: one language table instead of nine lists
lib.rs named every dictionary language in nine places: reloading for a new folder, the installed list, load errors, the missing-dictionary hint, the loaded check, the spelling message, spelling, suggestions and grammar. The lists had drifted. dictionary_load_error had no Russian or Czech entry, so a Russian or Czech dictionary that failed to load showed no reason in File › Options › Proofing, though the status bar, which reads another list, said why. LANGUAGES holds one row per language: its dictionary slot, its spelling message, and its spelling, suggestion and grammar functions. The nine functions read the row, so a language now joins with its module, its ProofLang and one row. The per-language reload_dictionary and installed_path wrappers had no callers left, inside the crate or out, and are gone. Tests: every_dictionary_language_reports_its_load_error, which fails for Russian on the old code, and the_language_table_covers_the_proofed_languages (each ProofLang::PROOFED language but English has exactly one row). Proof 285, engine 927 and layout 501 (lib).
This commit is contained in:
@@ -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<AffixDict> = DictSlot::new(NAMES, "Czech");
|
||||
pub(crate) static DICT: DictSlot<AffixDict> = 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<std::path::PathBuf> {
|
||||
DICT.installed_path()
|
||||
}
|
||||
|
||||
/// Is a Czech dictionary available (spelling is checked)?
|
||||
pub fn has_dictionary() -> bool {
|
||||
DICT.has_dictionary()
|
||||
|
||||
@@ -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<AffixDict> = DictSlot::new(SPAIN, "Spanish (Spain)");
|
||||
static MEXICO_DICT: DictSlot<AffixDict> = DictSlot::new(MEXICO, "Spanish (Mexico)");
|
||||
static LATIN_AMERICA_DICT: DictSlot<AffixDict> = DictSlot::new(LATIN_AMERICA, "Spanish (Latin America)");
|
||||
static URUGUAY_DICT: DictSlot<AffixDict> = DictSlot::new(URUGUAY, "Spanish (Uruguay)");
|
||||
pub(crate) static SPAIN_DICT: DictSlot<AffixDict> = DictSlot::new(SPAIN, "Spanish (Spain)");
|
||||
pub(crate) static MEXICO_DICT: DictSlot<AffixDict> = DictSlot::new(MEXICO, "Spanish (Mexico)");
|
||||
pub(crate) static LATIN_AMERICA_DICT: DictSlot<AffixDict> = DictSlot::new(LATIN_AMERICA, "Spanish (Latin America)");
|
||||
pub(crate) static URUGUAY_DICT: DictSlot<AffixDict> = 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<std::path::PathBuf> {
|
||||
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()
|
||||
|
||||
@@ -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<AffixDict> = DictSlot::new(NAMES, "Croatian");
|
||||
pub(crate) static DICT: DictSlot<AffixDict> = 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<std::path::PathBuf> {
|
||||
DICT.installed_path()
|
||||
}
|
||||
|
||||
/// Is a Croatian dictionary available (spelling is checked)?
|
||||
pub fn has_dictionary() -> bool {
|
||||
DICT.has_dictionary()
|
||||
|
||||
@@ -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<AffixDict> = DictSlot::new(NAMES, "Indonesian");
|
||||
pub(crate) static DICT: DictSlot<AffixDict> = 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<std::path::PathBuf> {
|
||||
DICT.installed_path()
|
||||
}
|
||||
|
||||
/// Is an Indonesian dictionary available (spelling is checked)?
|
||||
pub fn has_dictionary() -> bool {
|
||||
DICT.has_dictionary()
|
||||
|
||||
@@ -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<AffixDict> = DictSlot::new(NAMES, "Italian");
|
||||
pub(crate) static DICT: DictSlot<AffixDict> = 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<std::path::PathBuf> {
|
||||
DICT.installed_path()
|
||||
}
|
||||
|
||||
/// Is an Italian dictionary available (spelling is checked)?
|
||||
pub fn has_dictionary() -> bool {
|
||||
DICT.has_dictionary()
|
||||
|
||||
+165
-109
@@ -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<String>,
|
||||
/// Grammar issues; the flag says whether the first word must have a capital (not in a list
|
||||
/// item).
|
||||
grammar: fn(&str, ProofLang, bool) -> Vec<Issue>,
|
||||
}
|
||||
|
||||
/// 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<std::path::PathBuf>) {
|
||||
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<std::path::PathBuf>)> {
|
||||
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<String> {
|
||||
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<bool> {
|
||||
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<String> {
|
||||
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<Issue> {
|
||||
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]
|
||||
|
||||
@@ -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<AffixDict> = DictSlot::new(NAMES, "Polish");
|
||||
pub(crate) static DICT: DictSlot<AffixDict> = 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<std::path::PathBuf> {
|
||||
DICT.installed_path()
|
||||
}
|
||||
|
||||
/// Is a Polish dictionary available (spelling is checked)?
|
||||
pub fn has_dictionary() -> bool {
|
||||
DICT.has_dictionary()
|
||||
|
||||
@@ -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<AffixDict> = DictSlot::new(NAMES, "Portuguese");
|
||||
pub(crate) static DICT: DictSlot<AffixDict> = 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<std::path::PathBuf> {
|
||||
DICT.installed_path()
|
||||
}
|
||||
|
||||
/// Is a Portuguese dictionary available (spelling is checked)?
|
||||
pub fn has_dictionary() -> bool {
|
||||
DICT.has_dictionary()
|
||||
|
||||
@@ -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<AffixDict> = DictSlot::new(NAMES, "Russian");
|
||||
pub(crate) static DICT: DictSlot<AffixDict> = 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<std::path::PathBuf> {
|
||||
DICT.installed_path()
|
||||
}
|
||||
|
||||
/// Is a Russian dictionary available (spelling is checked)?
|
||||
pub fn has_dictionary() -> bool {
|
||||
DICT.has_dictionary()
|
||||
|
||||
@@ -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<PathBuf>;
|
||||
/// See [`DictSlot::has_dictionary`].
|
||||
fn has_dictionary(&self) -> bool;
|
||||
/// See [`DictSlot::reload`].
|
||||
fn reload(&self);
|
||||
}
|
||||
|
||||
impl<D: Dictionary> Slot for DictSlot<D> {
|
||||
fn name(&self) -> &'static str {
|
||||
DictSlot::name(self)
|
||||
}
|
||||
|
||||
fn installed_path(&self) -> Option<PathBuf> {
|
||||
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.
|
||||
|
||||
@@ -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<TrDict> = DictSlot::new(NAMES, "Turkish");
|
||||
pub(crate) static DICT: DictSlot<TrDict> = 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<std::path::PathBuf> {
|
||||
DICT.installed_path()
|
||||
}
|
||||
|
||||
/// Is a Turkish dictionary available?
|
||||
pub fn has_dictionary() -> bool {
|
||||
DICT.has_dictionary()
|
||||
|
||||
Reference in New Issue
Block a user