diff --git a/crates/proof/src/hr/mod.rs b/crates/proof/src/hr/mod.rs index 05b5a88b..5bbb55bc 100644 --- a/crates/proof/src/hr/mod.rs +++ b/crates/proof/src/hr/mod.rs @@ -60,6 +60,13 @@ pub fn reload_dictionary() { *DICT.write().unwrap_or_else(|e| e.into_inner()) = None; } +/// The `.dic` file of the Hunspell `hr_HR` dictionary found in the dictionaries folder or the +/// system locations now (File › Options › Proofing shows it, #1063); `None` when there is none. +/// A dictionary given with [`install_dictionary`] has no file and isn't reported. +pub fn installed_path() -> Option { + crate::hunspell::find(NAMES).map(|(_, dic)| dic) +} + /// Is a Croatian dictionary available (spelling is checked)? pub fn has_dictionary() -> bool { !dictionary().is_empty() diff --git a/crates/proof/src/it/mod.rs b/crates/proof/src/it/mod.rs index 41f63e48..7e163559 100644 --- a/crates/proof/src/it/mod.rs +++ b/crates/proof/src/it/mod.rs @@ -66,6 +66,13 @@ pub fn reload_dictionary() { *DICT.write().unwrap_or_else(|e| e.into_inner()) = None; } +/// The `.dic` file of the Hunspell `it_IT` dictionary found in the dictionaries folder or the +/// system locations now (File › Options › Proofing shows it, #1063); `None` when there is none. +/// A dictionary given with [`install_dictionary`] has no file and isn't reported. +pub fn installed_path() -> Option { + crate::hunspell::find(NAMES).map(|(_, dic)| dic) +} + /// Is an Italian dictionary available (spelling is checked)? pub fn has_dictionary() -> bool { !dictionary().is_empty() diff --git a/crates/proof/src/lib.rs b/crates/proof/src/lib.rs index d644b62f..d285c5ba 100644 --- a/crates/proof/src/lib.rs +++ b/crates/proof/src/lib.rs @@ -125,7 +125,33 @@ pub fn ignore_word(w: &str) { } } -/// A number that changes whenever a word is added to or removed from the user dictionary. +/// 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(); + 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()), + ] +} + +/// A number that changes whenever a word is added to or removed from the user dictionary, or the +/// dictionaries folder changes. pub fn dictionary_generation() -> u64 { DICTIONARY_GENERATION.load(Ordering::Relaxed) } @@ -748,6 +774,41 @@ fn starts_consonant_sound(w: &str) -> bool { mod tests { use super::*; + /// #1063: the dictionaries folder is searched before the system locations; the dictionaries + /// in it are reported and used, and cached checks are dropped. The folder holds the same test + /// dictionaries the other tests install, so tests running meanwhile see the same words; it + /// stays set for the rest of the run for that reason. + #[test] + fn dictionaries_folder_is_searched_first() { + let dir = std::env::temp_dir().join(format!("wc-proof-dictionaries-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + for (name, aff, dic) in [ + ("pl_PL", pl::test_dict::AFF, pl::test_dict::DIC), + ("pt_BR", pt::test_dict::AFF, pt::test_dict::DIC), + ("it_IT", it::test_dict::AFF, it::test_dict::DIC), + ("hr_HR", hr::test_dict::AFF, hr::test_dict::DIC), + ] { + std::fs::write(dir.join(format!("{name}.aff")), aff).unwrap(); + std::fs::write(dir.join(format!("{name}.dic")), dic).unwrap(); + } + let before = dictionary_generation(); + set_dictionaries_folder(Some(dir.clone())); + assert!(dictionary_generation() > before, "cached checks are dropped"); + // Turkish has no test dictionary to put in the folder; it's listed after the others. + let found = installed_dictionaries(); + assert_eq!(found.last().map(|(lang, _)| *lang), Some(ProofLang::Tr)); + assert_eq!( + found[..4], + [ + (ProofLang::Pl, Some(dir.join("pl_PL.dic"))), + (ProofLang::Pt, Some(dir.join("pt_BR.dic"))), + (ProofLang::It, Some(dir.join("it_IT.dic"))), + (ProofLang::Hr, Some(dir.join("hr_HR.dic"))), + ] + ); + assert!(is_correct_in("chrząszcz", ProofLang::Pl) && !is_correct_in("chrzonszcz", ProofLang::Pl)); + } + #[test] fn dictionary_loads() { assert!(dict::Dictionary::en_us().len() > 100_000); diff --git a/crates/proof/src/pl/mod.rs b/crates/proof/src/pl/mod.rs index 74d8b8d2..22725340 100644 --- a/crates/proof/src/pl/mod.rs +++ b/crates/proof/src/pl/mod.rs @@ -67,6 +67,13 @@ pub fn reload_dictionary() { *DICT.write().unwrap_or_else(|e| e.into_inner()) = None; } +/// The `.dic` file of the installed Polish dictionary, searching the dictionaries folder and +/// the system locations now (File › Options › Proofing shows it, #1063); `None` when there is +/// none. A dictionary given with [`install_dictionary`] has no file and isn't reported. +pub fn installed_path() -> Option { + crate::hunspell::find(NAMES).map(|(_, dic)| dic) +} + /// Is a Polish dictionary available (spelling is checked)? pub fn has_dictionary() -> bool { !dictionary().is_empty() diff --git a/crates/proof/src/pt/mod.rs b/crates/proof/src/pt/mod.rs index be16c74b..d175f63c 100644 --- a/crates/proof/src/pt/mod.rs +++ b/crates/proof/src/pt/mod.rs @@ -66,6 +66,13 @@ pub fn reload_dictionary() { *DICT.write().unwrap_or_else(|e| e.into_inner()) = None; } +/// The `.dic` file of the installed Portuguese dictionary, searching the dictionaries folder and +/// the system locations now (File › Options › Proofing shows it, #1063); `None` when there is +/// none. A dictionary given with [`install_dictionary`] has no file and isn't reported. +pub fn installed_path() -> Option { + crate::hunspell::find(NAMES).map(|(_, dic)| dic) +} + /// Is a Portuguese dictionary available (spelling is checked)? pub fn has_dictionary() -> bool { !dictionary().is_empty() diff --git a/crates/proof/src/tr/mod.rs b/crates/proof/src/tr/mod.rs index e0b6fab9..2b18158a 100644 --- a/crates/proof/src/tr/mod.rs +++ b/crates/proof/src/tr/mod.rs @@ -65,6 +65,13 @@ pub fn reload_dictionary() { *DICT.write().unwrap_or_else(|e| e.into_inner()) = None; } +/// The `.dic` file of the Hunspell `tr_TR` dictionary found in the dictionaries folder or the +/// system locations now (File › Options › Proofing shows it, #1063); `None` when there is none. +/// A dictionary given with [`install_dictionary`] has no file and isn't reported. +pub fn installed_path() -> Option { + crate::hunspell::find(NAMES).map(|(_, dic)| dic) +} + /// Is a Turkish dictionary available? pub fn has_dictionary() -> bool { !dictionary().is_empty()