diff --git a/autoload/denippet/load.vim b/autoload/denippet/load.vim index 2fb19a5..9295772 100644 --- a/autoload/denippet/load.vim +++ b/autoload/denippet/load.vim @@ -13,24 +13,16 @@ function s:regex_builder(prefix) abort return regex endfunction -function denippet#load#base(prefix, from_normal) abort - let pattern = a:from_normal - \ ? '\v^$' - \ : '\v^' . s:regex_builder(a:prefix) +function denippet#load#base(prefix) abort + let pattern = '\v^' . s:regex_builder(a:prefix) let enabled = v:true let enabled = enabled && line('$') == 1 let enabled = enabled && getline(1) =~# pattern return enabled endfunction -function denippet#load#start(prefix, from_normal) abort - if a:from_normal && col('.') == 1 - let line_before_cursor = '' - else - let line_before_cursor = getline('.')[:col('.')-2] - endif - let pattern = a:from_normal - \ ? '\v^\s*$' - \ : '\v^\s*' . s:regex_builder(a:prefix) +function denippet#load#start(prefix) abort + let line_before_cursor = getline('.')[:col('.')-2] + let pattern = '\v^\s*' . s:regex_builder(a:prefix) return match(line_before_cursor, pattern) > -1 endfunction diff --git a/denops/denippet/loader.ts b/denops/denippet/loader.ts index e6907ad..df50e3d 100644 --- a/denops/denippet/loader.ts +++ b/denops/denippet/loader.ts @@ -79,105 +79,112 @@ function toString(x?: string | string[]): string { } } -// Keys are filetypes -const Cell: Record = {}; - -function setSnippets( - filetype: string | string[], - snippets: NormalizedSnippet[], -): void { - toArray(filetype).forEach((ft) => { - if (Cell[ft] == null) { - Cell[ft] = []; - } - Cell[ft].push(...snippets); - }); -} +export class Loader { + cell: Record = {}; -export async function getSnippets( - denops: Denops, - ft: string, - fromNormal: boolean, -): Promise { - const snippets = [ - ...Cell[ft] ?? [], - ...Cell["*"] ?? [], - ]; - return await asyncFilter(snippets, async (snippet) => { - if (snippet.if == null) { - return true; - } else if (isIfFunc(snippet.if)) { - return Boolean(await snippet.if(denops)); - } else if (snippet.if === "base") { - return Boolean(await denops.call("denippet#load#base", snippet.prefix, fromNormal)); - } else if (snippet.if === "start") { - return Boolean(await denops.call("denippet#load#start", snippet.prefix, fromNormal)); - } else if (!snippet.eval) { - return false; - } else if (snippet.if === "vimscript") { - return Boolean(await denops.call("eval", snippet.eval)); - } else { - return Boolean(await denops.call("luaeval", "assert(loadstring(_A[1]))()", [snippet.eval])); - } - }); -} + constructor( + public denops: Denops, + ) {} + + reset(): void { + this.cell = {}; + } + + set( + filetype: string | string[], + snippets: NormalizedSnippet[], + ): void { + toArray(filetype).forEach((ft) => { + if (this.cell[ft] == null) { + this.cell[ft] = []; + } + this.cell[ft].push(...snippets); + }); + } -export async function load( - filepath: string, - filetype: string | string[], -): Promise { - const extension = filepath.split(".").pop()!; - let snippets: NormalizedSnippet[] = []; - - if (extension === "ts") { - const content = await import(filepath).then((module) => module.snippets); - u.assert(content, is.RecordOf(isTSSnippet)); - snippets = Object.entries(content).map(([name, snip]) => ({ - ...snip, - name, - prefix: toArray(snip.prefix ?? name), - body: async (denops: Denops) => { - return toString( - typeof snip.body == "function" ? await snip.body(denops) : snip.body, + async get(ft: string): Promise { + const snippets = [ + ...this.cell[ft] ?? [], + ...this.cell["*"] ?? [], + ]; + return await asyncFilter(snippets, async (snippet) => { + if (snippet.if == null) { + return true; + } else if (isIfFunc(snippet.if)) { + return Boolean(await snippet.if(this.denops)); + } else if (snippet.if === "base") { + return Boolean(await this.denops.call("denippet#load#base", snippet.prefix)); + } else if (snippet.if === "start") { + return Boolean(await this.denops.call("denippet#load#start", snippet.prefix)); + } else if (!snippet.eval) { + return false; + } else if (snippet.if === "vimscript") { + return Boolean(await this.denops.call("eval", snippet.eval)); + } else { + return Boolean( + await this.denops.call("luaeval", "assert(loadstring(_A[1]))()", [snippet.eval]), ); - }, - description: toString(snip.description), - })); - } else { - const raw = await Deno.readTextFile(filepath); - if (["json", "toml", "yaml"].includes(extension)) { - const content = (extension === "json") - ? JSON.parse(raw) - : extension === "toml" - ? TOML.parse(raw) - : YAML.parse(raw); - u.assert(content, is.RecordOf(isRawSnippet)); + } + }); + } + + async load( + filepath: string, + filetype: string | string[], + ): Promise { + const extension = filepath.split(".").pop()!; + let snippets: NormalizedSnippet[] = []; + + if (extension === "ts") { + const content = await import(filepath).then((module) => module.snippets); + u.assert(content, is.RecordOf(isTSSnippet)); snippets = Object.entries(content).map(([name, snip]) => ({ ...snip, name, prefix: toArray(snip.prefix ?? name), - body: toString(snip.body), + body: async (denops: Denops) => { + return toString( + typeof snip.body == "function" ? await snip.body(denops) : snip.body, + ); + }, description: toString(snip.description), })); - } else if (extension === "code-snippets") { - const content = JSON.parse(raw); - u.assert(content, is.RecordOf(isGlobalSnippet)); - for (const [name, snippet] of Object.entries(content)) { - const ft = snippet.scope ? snippet.scope.split(",").map(langToFt) : "*"; - const snip = { - ...snippet, + } else { + const raw = await Deno.readTextFile(filepath); + if (["json", "toml", "yaml"].includes(extension)) { + const content = (extension === "json") + ? JSON.parse(raw) + : extension === "toml" + ? TOML.parse(raw) + : YAML.parse(raw); + u.assert(content, is.RecordOf(isRawSnippet)); + snippets = Object.entries(content).map(([name, snip]) => ({ + ...snip, name, - prefix: toArray(snippet.prefix ?? name), - body: toString(snippet.body), - description: toString(snippet.description), - }; - setSnippets(ft, [snip]); + prefix: toArray(snip.prefix ?? name), + body: toString(snip.body), + description: toString(snip.description), + })); + } else if (extension === "code-snippets") { + const content = JSON.parse(raw); + u.assert(content, is.RecordOf(isGlobalSnippet)); + for (const [name, snippet] of Object.entries(content)) { + const ft = snippet.scope ? snippet.scope.split(",").map(langToFt) : "*"; + const snip = { + ...snippet, + name, + prefix: toArray(snippet.prefix ?? name), + body: toString(snippet.body), + description: toString(snippet.description), + }; + this.set(ft, [snip]); + } + return; + } else { + throw new Error(`Unknown extension: ${extension}`); } - return; - } else { - throw new Error(`Unknown extension: ${extension}`); } - } - setSnippets(filetype, snippets); + this.set(filetype, snippets); + } } diff --git a/denops/denippet/main.ts b/denops/denippet/main.ts index 54d4445..c46000a 100644 --- a/denops/denippet/main.ts +++ b/denops/denippet/main.ts @@ -1,7 +1,7 @@ import { au, Denops, fn, g, lambda, op } from "./deps/denops.ts"; import { is, u } from "./deps/unknownutil.ts"; import { lsputil } from "./deps/lsp.ts"; -import { getSnippets, load, NormalizedSnippet } from "./loader.ts"; +import { Loader, NormalizedSnippet } from "./loader.ts"; import { Session } from "./session.ts"; import { register } from "./variable.ts"; import { echoerr } from "./util.ts"; @@ -23,14 +23,14 @@ type SearchResult = { }; async function searchSnippet( - denops: Denops, + loader: Loader, ): Promise { - const ctx = await lsputil.LineContext.create(denops); + const ctx = await lsputil.LineContext.create(loader.denops); const lineBeforeCursor = ctx.text.slice(0, ctx.character); let bestMatch: SearchResult = {}; - const filetype = await op.filetype.get(denops); - (await getSnippets(denops, filetype, false)).forEach((snippet) => { + const filetype = await op.filetype.get(loader.denops); + (await loader.get(filetype)).forEach((snippet) => { snippet.prefix.forEach((prefix) => { if ( lineBeforeCursor.endsWith(prefix) && @@ -46,6 +46,7 @@ async function searchSnippet( export function main(denops: Denops): void { const session = new Session(denops); + const loader = new Loader(denops); let timeoutId: ReturnType | undefined; @@ -81,19 +82,19 @@ export function main(denops: Denops): void { is.OneOf([is.String, is.ArrayOf(is.String)]), ); try { - await load(filepath, filetype); + await loader.load(filepath, filetype); } catch (e) { echoerr(denops, `Failed to load a snippet file ${filepath}.\n${e}`); } }, async expandable(): Promise { - const { body } = await searchSnippet(denops); + const { body } = await searchSnippet(loader); return body != null; }, async expand(): Promise { - const { prefix, body } = await searchSnippet(denops); + const { prefix, body } = await searchSnippet(loader); if (body == null) { return; } @@ -177,10 +178,9 @@ export function main(denops: Denops): void { await session.choice(dir); }, - async getCompleteItems(fromNormalU: unknown): Promise { - const fromNormal = u.ensure(fromNormalU, is.Boolean); + async getCompleteItems(): Promise { const filetype = await op.filetype.get(denops); - return (await getSnippets(denops, filetype, fromNormal)).flatMap((snippet) => + return (await loader.get(filetype)).flatMap((snippet) => snippet.prefix.map((prefix) => ({ word: prefix, kind: "Snippet",