diff --git a/denops/skkeleton/dictionary_test.ts b/denops/skkeleton/dictionary_test.ts index ee6da5630..4fbce9cd5 100644 --- a/denops/skkeleton/dictionary_test.ts +++ b/denops/skkeleton/dictionary_test.ts @@ -1,9 +1,15 @@ -import { Dictionary, Library, wrapDictionary } from "./dictionary.ts"; +import { + Dictionary, + Library, + okuriNasiMarker, + wrapDictionary, +} from "./dictionary.ts"; import { Dictionary as SkkDictionary } from "./sources/skk_dictionary.ts"; import { Dictionary as DenoKvDictionary } from "./sources/deno_kv.ts"; import { Dictionary as UserDictionary } from "./sources/user_dictionary.ts"; import { assertEquals } from "@std/assert/equals"; +import { assertRejects } from "@std/assert/rejects"; import { dirname } from "@std/path/dirname"; import { fromFileUrl } from "@std/path/from-file-url"; import { join } from "@std/path/join"; @@ -107,6 +113,24 @@ Deno.test({ }, }); +Deno.test({ + name: "lazy loads global dictionary", + async fn() { + const tmp = await Deno.makeTempFile(); + try { + await Deno.writeTextFile( + tmp, + `${okuriNasiMarker}\nあ /あ/\n`, + ); + const dic = SkkDictionary.fromFile(tmp, "utf-8"); + await Deno.remove(tmp); + await assertRejects(() => dic.getHenkanResult("okurinasi", "あ")); + } finally { + await Deno.remove(tmp).catch(() => {}); + } + }, +}); + Deno.test({ name: "get num candidates", async fn(t) { diff --git a/denops/skkeleton/sources/skk_dictionary.ts b/denops/skkeleton/sources/skk_dictionary.ts index 9b3166cde..83b845efa 100644 --- a/denops/skkeleton/sources/skk_dictionary.ts +++ b/denops/skkeleton/sources/skk_dictionary.ts @@ -23,32 +23,12 @@ interface Jisyo { } export class Source implements BaseSource { - async getDictionaries(): Promise { - const globalDictionaries = await Promise.all( - config.globalDictionaries.map(async ([path, encodingName]) => { - try { - const dict = new Dictionary(); - await dict.load(path, encodingName); - return dict; - } catch (e) { - console.error("globalDictionary loading failed"); - console.error(`at ${path}`); - if (config.debug) { - console.error(e); - } - return undefined; - } - }), + getDictionaries(): Promise { + return Promise.resolve( + config.globalDictionaries.map(([path, encodingName]) => + wrapDictionary(Dictionary.fromFile(path, encodingName)) + ), ); - - const dictionaries: BaseDictionary[] = []; - for (const d of globalDictionaries) { - if (d) { - dictionaries.push(wrapDictionary(d)); - } - } - - return dictionaries; } } @@ -57,22 +37,38 @@ export class Dictionary implements BaseDictionary { #okuriNasi: Map; #cachedCandidates: Map; + #path: string | undefined; + #encoding: string; + #loadPromise: Promise | undefined; constructor( okuriAri?: Map, okuriNasi?: Map, + path?: string, + encoding = "", ) { this.#okuriAri = okuriAri ?? new Map(); this.#okuriNasi = okuriNasi ?? new Map(); this.#cachedCandidates = new Map(); + this.#path = path; + this.#encoding = encoding; + } + + static fromFile(path: string, encoding: string): Dictionary { + return new Dictionary(undefined, undefined, path, encoding); } - getHenkanResult(type: HenkanType, word: string): Promise { + async getHenkanResult(type: HenkanType, word: string): Promise { + await this.ensureLoaded(); const target = type === "okuriari" ? this.#okuriAri : this.#okuriNasi; - return Promise.resolve(target.get(word) ?? []); + return target.get(word) ?? []; } - getCompletionResult(prefix: string, feed: string): Promise { + async getCompletionResult( + prefix: string, + feed: string, + ): Promise { + await this.ensureLoaded(); const candidates: CompletionData = []; if (feed != "") { const table = getKanaTable(); @@ -95,7 +91,7 @@ export class Dictionary implements BaseDictionary { } candidates.sort((a, b) => a[0].localeCompare(b[0])); - return Promise.resolve(candidates); + return candidates; } private getCachedCandidates(prefix: string): CompletionData { @@ -115,22 +111,37 @@ export class Dictionary implements BaseDictionary { return candidates; } - async load(path: string, encoding: string) { + async load(path?: string, encoding?: string) { + if (path !== undefined) { + this.#path = path; + this.#encoding = encoding ?? ""; + this.#loadPromise = undefined; + } + await this.ensureLoaded(); + return this; + } + + private async ensureLoaded(): Promise { + if (!this.#path || this.#loadPromise === undefined) { + if (!this.#path) { + return; + } + this.#loadPromise = this.readFile(); + } + await this.#loadPromise; + } + + private async readFile(): Promise { + const path = this.#path!; if (path.endsWith(".yaml") || path.endsWith(".yml")) { - const file = await Deno.readTextFile(path); - this.loadYaml(file); + this.loadYaml(await Deno.readTextFile(path)); } else if (path.endsWith(".json")) { - const file = await Deno.readTextFile(path); - this.loadJson(file); + this.loadJson(await Deno.readTextFile(path)); } else if (path.endsWith(".mpk")) { - const file = await Deno.readFile(path); - this.loadMsgpack(file); + this.loadMsgpack(await Deno.readFile(path)); } else { - const file = await readFileWithEncoding(path, encoding); - this.loadString(file); + this.loadString(await readFileWithEncoding(path, this.#encoding)); } - - return this; } private loadJson(data: string) {