Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion denops/skkeleton/dictionary_test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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) {
Expand Down
91 changes: 51 additions & 40 deletions denops/skkeleton/sources/skk_dictionary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,32 +23,12 @@ interface Jisyo {
}

export class Source implements BaseSource {
async getDictionaries(): Promise<BaseDictionary[]> {
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<BaseDictionary[]> {
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;
}
}

Expand All @@ -57,22 +37,38 @@ export class Dictionary implements BaseDictionary {
#okuriNasi: Map<string, string[]>;

#cachedCandidates: Map<string, CompletionData>;
#path: string | undefined;
#encoding: string;
#loadPromise: Promise<void> | undefined;

constructor(
okuriAri?: Map<string, string[]>,
okuriNasi?: Map<string, string[]>,
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<string[]> {
async getHenkanResult(type: HenkanType, word: string): Promise<string[]> {
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<CompletionData> {
async getCompletionResult(
prefix: string,
feed: string,
): Promise<CompletionData> {
await this.ensureLoaded();
const candidates: CompletionData = [];
if (feed != "") {
const table = getKanaTable();
Expand All @@ -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 {
Expand All @@ -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<void> {
if (!this.#path || this.#loadPromise === undefined) {
if (!this.#path) {
return;
}
this.#loadPromise = this.readFile();
}
await this.#loadPromise;
}

private async readFile(): Promise<void> {
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) {
Expand Down
Loading