diff --git a/src/dictionary/loader.rs b/src/dictionary/loader.rs index ef60ac7d..205b428f 100644 --- a/src/dictionary/loader.rs +++ b/src/dictionary/loader.rs @@ -7,11 +7,11 @@ use std::{ path::{Path, PathBuf}, }; -use log::info; +use log::{info, warn}; use crate::{ editor::{AbbrevTable, SymbolSelector}, - path::{find_path_by_files, sys_path_from_env_var, userphrase_path}, + path::{find_extra_dat_by_path, find_path_by_files, sys_path_from_env_var, userphrase_path}, }; #[cfg(feature = "sqlite")] @@ -70,15 +70,28 @@ impl SystemDictionaryLoader { let sys_path = find_path_by_files(&search_path, &[SD_WORD_FILE_NAME, SD_TSI_FILE_NAME]) .ok_or(LoadDictionaryError::NotFound)?; - let tsi_dict_path = sys_path.join(SD_TSI_FILE_NAME); - info!("Loading {SD_TSI_FILE_NAME}"); - let tsi_dict = Trie::open(tsi_dict_path).map_err(io_err)?; + let mut results: Vec> = vec![]; let word_dict_path = sys_path.join(SD_WORD_FILE_NAME); info!("Loading {SD_WORD_FILE_NAME}"); let word_dict = Trie::open(word_dict_path).map_err(io_err)?; + results.push(Box::new(word_dict)); + + let tsi_dict_path = sys_path.join(SD_TSI_FILE_NAME); + info!("Loading {SD_TSI_FILE_NAME}"); + let tsi_dict = Trie::open(tsi_dict_path).map_err(io_err)?; + results.push(Box::new(tsi_dict)); + + let extra_files = find_extra_dat_by_path(&search_path); + for path in extra_files { + info!("Loading {}", path.display()); + match Trie::open(&path) { + Ok(dict) => results.push(Box::new(dict)), + Err(e) => warn!("Failed to load {}: {e}", path.display()), + } + } - Ok(vec![Box::new(word_dict), Box::new(tsi_dict)]) + Ok(results) } pub fn load_abbrev(&self) -> Result { let search_path = if let Some(sys_path) = &self.sys_path { diff --git a/src/path.rs b/src/path.rs index 65222a72..dc67c39b 100644 --- a/src/path.rs +++ b/src/path.rs @@ -2,6 +2,7 @@ use std::{ env, + ffi::OsStr, path::{Path, PathBuf}, }; @@ -17,6 +18,8 @@ const SEARCH_PATH_SEP: char = ';'; #[cfg(target_family = "unix")] const SEARCH_PATH_SEP: char = ':'; +const DICT_FOLDER: &str = "dictionary.d"; + pub(crate) fn sys_path_from_env_var() -> String { let chewing_path = env::var("CHEWING_PATH"); if let Ok(chewing_path) = chewing_path { @@ -55,6 +58,33 @@ pub(crate) fn find_path_by_files(search_path: &str, files: &[&str]) -> Option Vec { + let mut results = vec![]; + for path in search_path.split(SEARCH_PATH_SEP) { + let prefix = Path::new(path).join(DICT_FOLDER); + info!("Search dictionary files in {}", prefix.display()); + if let Ok(read_dir) = prefix.read_dir() { + let mut files = vec![]; + for entry in read_dir { + if let Ok(entry) = entry { + let path = entry.path(); + let is_dat = path + .extension() + .and_then(OsStr::to_str) + .map_or(false, |ext| ext == "dat"); + if path.is_file() && is_dat { + info!("Found {}", path.display()); + files.push(path); + } + } + } + files.sort(); + results.extend(files); + } + } + results +} + /// Returns the path to the user's default chewing data directory. /// /// The returned value depends on the operating system and is either a diff --git a/tests/data/dictionary.d/01-extra.dat b/tests/data/dictionary.d/01-extra.dat new file mode 100644 index 00000000..be03d548 Binary files /dev/null and b/tests/data/dictionary.d/01-extra.dat differ diff --git a/tests/data/dictionary.d/02-empty.dat b/tests/data/dictionary.d/02-empty.dat new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/extra.src b/tests/data/extra.src new file mode 100644 index 00000000..3ad635f4 --- /dev/null +++ b/tests/data/extra.src @@ -0,0 +1,2 @@ +額 0 ㄜˊ +外 0 ㄨㄞˋ diff --git a/tests/test-config.c b/tests/test-config.c index c3263377..0c3e9a34 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -745,6 +745,21 @@ void test_runtime_version() ok(strcmp(buf, version) == 0, "chewing_version can be created from components"); } +void test_dictionary_d() +{ + ChewingContext *ctx; + + ctx = chewing_new2(TEST_DATA_DIR, NULL, logger, fd); + start_testcase(ctx, fd); + + ok(ctx != NULL, "chewing_new2 returns `%#p' shall not be `%#p'", ctx, NULL); + + type_keystroke_by_string(ctx, "k6j94"); + ok_commit_buffer(ctx, "額外"); + + chewing_delete(ctx); +} + int main(int argc, char *argv[]) { char *logname; @@ -785,6 +800,8 @@ int main(int argc, char *argv[]) test_runtime_version(); + test_dictionary_d(); + fclose(fd); return exit_status();