diff --git a/crates/proof/src/hunspell.rs b/crates/proof/src/hunspell.rs index 8e83104a..4cf75210 100644 --- a/crates/proof/src/hunspell.rs +++ b/crates/proof/src/hunspell.rs @@ -105,16 +105,7 @@ fn search_tiers(lo_exts: Option<&[String]>) -> (Vec, Vec) { system.extend(lo_dirs(&base.join("LibreOffice").join("share").join("extensions").to_string_lossy())); } } - // If LibreOffice is installed on another drive (e.g. D:\Program Files\LibreOffice), check drives C..Z - for drive in b'C'..=b'Z' { - let drive_char = drive as char; - for pf in ["Program Files", "Program Files (x86)"] { - let lo_base = PathBuf::from(format!(r"{drive_char}:\{pf}\LibreOffice\share\extensions")); - if lo_base.is_dir() { - system.extend(lo_dirs(&lo_base.to_string_lossy())); - } - } - } + system.extend(lo_drive_dirs(lo_exts, Path::is_dir)); } else if cfg!(target_os = "macos") { if let Some(h) = &home { // An app started from Finder or the Dock doesn't see `WORDCRAFT_DICTIONARIES`; this @@ -144,6 +135,23 @@ fn search_tiers(lo_exts: Option<&[String]>) -> (Vec, Vec) { (dirs, system) } +/// LibreOffice's language folders (`:\Program Files\LibreOffice\share\extensions\`) +/// on the drives C to Z where `is_dir` finds LibreOffice installed. No drive is probed without +/// `lo_exts` (no LibreOffice folders are wanted then). +fn lo_drive_dirs(lo_exts: Option<&[String]>, is_dir: impl Fn(&Path) -> bool) -> Vec { + let Some(exts) = lo_exts else { return Vec::new() }; + let mut out = Vec::new(); + for drive in 'C'..='Z' { + for pf in ["Program Files", "Program Files (x86)"] { + let base = PathBuf::from(format!(r"{drive}:\{pf}\LibreOffice\share\extensions")); + if is_dir(&base) { + out.extend(exts.iter().map(|e| base.join(e))); + } + } + } + out +} + /// The user's data folder on Linux and the BSDs: `$XDG_DATA_HOME` when it is an absolute path (the /// XDG Base Directory spec ignores a relative one), else `~/.local/share`. fn xdg_data_home(xdg: Option, home: Option<&Path>) -> Option { @@ -610,6 +618,22 @@ fn decode_utf8_dic(dic: &[u8]) -> Result { mod tests { use super::*; + /// CodeRabbit on craft-libs#489: without LibreOffice extension names no drive is probed; with + /// them, only drives where LibreOffice is found give folders. + #[test] + fn libreoffice_drives_are_probed_only_when_wanted() { + let probes = std::cell::Cell::new(0); + let count = |_: &Path| { + probes.set(probes.get() + 1); + false + }; + assert!(lo_drive_dirs(None, count).is_empty()); + assert_eq!(probes.get(), 0); + let exts = ["dict-tr".to_string()]; + let found = lo_drive_dirs(Some(&exts), |p: &Path| p.to_string_lossy().starts_with(r"D:\Program Files\")); + assert_eq!(found, [PathBuf::from(r"D:\Program Files\LibreOffice\share\extensions").join("dict-tr")]); + } + /// Another region's Language pack never beats the system's dictionary of the right region /// (`pt-pt` pack vs system `pt_BR`; `de-ch` vs `de_DE`; `en-gb` vs `en_US`), but beats the /// system's other names; the right region's pack beats the system; the dictionaries folder