-
-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
👍 Add
NaivePlugin
that directly loads in main thread
- Loading branch information
1 parent
e4b28b2
commit b5dcab8
Showing
3 changed files
with
344 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { ensure, is } from "https://deno.land/x/[email protected]/mod.ts"; | ||
import { | ||
BatchError, | ||
Context, | ||
Denops, | ||
Dispatcher, | ||
Meta, | ||
} from "../../../@denops/mod.ts"; | ||
import type { Service } from "../../service.ts"; | ||
|
||
const isBatchReturn = is.TupleOf([is.Array, is.String] as const); | ||
|
||
export class DenopsImpl implements Denops { | ||
readonly context: Record<string | number | symbol, unknown> = {}; | ||
readonly name: string; | ||
dispatcher: Dispatcher = {}; | ||
#service: Service; | ||
|
||
constructor( | ||
name: string, | ||
service: Service, | ||
) { | ||
this.name = name; | ||
this.#service = service; | ||
} | ||
|
||
get meta(): Meta { | ||
return this.#service.meta; | ||
} | ||
|
||
redraw(force?: boolean): Promise<void> { | ||
return this.#service.host.redraw(force); | ||
} | ||
|
||
call(fn: string, ...args: unknown[]): Promise<unknown> { | ||
return this.#service.host.call(fn, ...normArgs(args)); | ||
} | ||
|
||
batch( | ||
...calls: [string, ...unknown[]][] | ||
): Promise<unknown[]> { | ||
const normCalls = calls.map(([fn, ...args]) => | ||
[fn, ...normArgs(args)] as const | ||
); | ||
return this.#service.host.batch(...normCalls).then((ret) => { | ||
const [results, errmsg] = ensure(ret, isBatchReturn); | ||
if (errmsg !== "") { | ||
throw new BatchError(errmsg, results); | ||
} | ||
return results; | ||
}); | ||
} | ||
|
||
cmd(cmd: string, ctx: Context = {}): Promise<void> { | ||
return this.#service.host.call("denops#api#cmd", cmd, ctx).then(); | ||
} | ||
|
||
eval(expr: string, ctx: Context = {}): Promise<unknown> { | ||
return this.#service.host.call("denops#api#eval", expr, ctx); | ||
} | ||
|
||
dispatch( | ||
name: string, | ||
fn: string, | ||
...args: unknown[] | ||
): Promise<unknown> { | ||
return this.#service.dispatch(name, fn, args); | ||
} | ||
} | ||
|
||
function normArgs(args: unknown[]): unknown[] { | ||
const normArgs = []; | ||
for (const arg of args) { | ||
if (arg === undefined) { | ||
break; | ||
} | ||
normArgs.push(arg); | ||
} | ||
return normArgs; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
import * as path from "https://deno.land/[email protected]/path/mod.ts"; | ||
import { | ||
assertEquals, | ||
assertRejects, | ||
} from "https://deno.land/[email protected]/assert/mod.ts"; | ||
import { test } from "https://deno.land/x/[email protected]/mod.ts"; | ||
import { BatchError } from "../../../@denops/mod.ts"; | ||
|
||
test({ | ||
mode: "all", | ||
name: "impl", | ||
fn: async (denops, t) => { | ||
await t.step({ | ||
name: "denops.redraw() does nothing", | ||
fn: async () => { | ||
assertEquals( | ||
await denops.redraw(), | ||
undefined, | ||
); | ||
|
||
assertEquals( | ||
await denops.redraw(true), | ||
undefined, | ||
); | ||
|
||
assertEquals( | ||
await denops.redraw(false), | ||
undefined, | ||
); | ||
}, | ||
}); | ||
|
||
await t.step({ | ||
name: "denops.call() calls a Vim/Neovim function and return a result", | ||
fn: async () => { | ||
assertEquals( | ||
await denops.call("range", 10), | ||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], | ||
); | ||
}, | ||
}); | ||
|
||
await t.step({ | ||
name: "denops.call() calls a Vim/Neovim function and throw an error", | ||
fn: async () => { | ||
await assertRejects( | ||
async () => { | ||
await denops.call("no-such-function"); | ||
}, | ||
"E117: Unknown function: no-such-function", | ||
); | ||
}, | ||
}); | ||
|
||
await t.step({ | ||
name: | ||
"denops.call() drop arguments after `undefined` (but `null`) for convenience", | ||
fn: async () => { | ||
assertEquals( | ||
await denops.call("denops#api#id", 0, 1, 2), | ||
[0, 1, 2], | ||
); | ||
assertEquals( | ||
await denops.call("denops#api#id", 0, 1, undefined, 2), | ||
[0, 1], | ||
); | ||
assertEquals( | ||
await denops.call("denops#api#id", 0, undefined, 1, 2), | ||
[0], | ||
); | ||
assertEquals( | ||
await denops.call("denops#api#id", 0, 1, null, 2), | ||
[0, 1, null, 2], | ||
); | ||
assertEquals( | ||
await denops.call("denops#api#id", 0, null, 1, 2), | ||
[0, null, 1, 2], | ||
); | ||
}, | ||
}); | ||
|
||
await t.step({ | ||
name: "denops.cmd() invoke a Vim/Neovim command", | ||
fn: async () => { | ||
await denops.cmd("execute 'let g:denops_test = value'", { | ||
value: "Hello World", | ||
}); | ||
assertEquals( | ||
await denops.eval("g:denops_test") as string, | ||
"Hello World", | ||
); | ||
}, | ||
}); | ||
|
||
await t.step({ | ||
name: "denops.cmd() invoke a Vim/Neovim command and throw an error", | ||
fn: async () => { | ||
await assertRejects( | ||
async () => { | ||
await denops.cmd("NoSuchCommand"); | ||
}, | ||
"E492: Not an editor command: NoSuchCommand", | ||
); | ||
}, | ||
}); | ||
|
||
await t.step({ | ||
name: | ||
"denops.eval() evaluate a Vim/Neovim expression and return a result", | ||
fn: async () => { | ||
await denops.cmd("execute 'let g:denops_test = value'", { | ||
value: "Hello World", | ||
}); | ||
assertEquals( | ||
await denops.eval("g:denops_test") as string, | ||
"Hello World", | ||
); | ||
}, | ||
}); | ||
|
||
await t.step({ | ||
name: "denops.eval() evaluate a Vim/Neovim expression and throw an error", | ||
fn: async () => { | ||
await assertRejects( | ||
async () => { | ||
await denops.eval("g:no_such_variable"); | ||
}, | ||
"g:no_such_variable", | ||
// Vim: "E15: Invalid expression: g:no_such_variable", | ||
// Neovim: "E121: Undefined variable: g:no_such_variable", | ||
); | ||
}, | ||
}); | ||
|
||
await t.step({ | ||
name: | ||
"denops.batch() calls multiple Vim/Neovim functions and return results", | ||
fn: async () => { | ||
const results = await denops.batch(["range", 1], ["range", 2], [ | ||
"range", | ||
3, | ||
]); | ||
assertEquals(results, [[0], [0, 1], [0, 1, 2]]); | ||
}, | ||
}); | ||
|
||
await t.step({ | ||
name: | ||
"denops.batch() calls multiple Vim/Neovim functions and throws an error with results", | ||
fn: async () => { | ||
await assertRejects(async () => { | ||
await denops.batch( | ||
["range", 1], | ||
["no-such-function", 2], | ||
["range", 3], | ||
); | ||
}, BatchError); | ||
}, | ||
}); | ||
|
||
await t.step({ | ||
name: | ||
"denops.batch() drop arguments after `undefined` (but `null`) for convenience", | ||
fn: async () => { | ||
const results = await denops.batch( | ||
["denops#api#id", 0, 1, 2], | ||
["denops#api#id", 0, 1, undefined, 2], | ||
["denops#api#id", 0, undefined, 1, 2], | ||
["denops#api#id", 0, 1, null, 2], | ||
["denops#api#id", 0, null, 1, 2], | ||
); | ||
assertEquals(results, [[0, 1, 2], [0, 1], [0], [0, 1, null, 2], [ | ||
0, | ||
null, | ||
1, | ||
2, | ||
]]); | ||
}, | ||
}); | ||
|
||
await t.step({ | ||
name: "denops.call() works properly even when called concurrently", | ||
fn: async () => { | ||
const cwd = await denops.call("getcwd") as string; | ||
await denops.cmd("edit dummy1"); | ||
await denops.cmd("file dummy2"); | ||
const results = await Promise.all([ | ||
denops.call("expand", "%"), | ||
denops.call("expand", "%:p"), | ||
denops.call("expand", "%hello"), | ||
denops.call("expand", "#"), | ||
denops.call("expand", "#:p"), | ||
denops.call("expand", "#hello"), | ||
]); | ||
assertEquals(results, [ | ||
"dummy2", | ||
path.join(cwd, "dummy2"), | ||
"dummy2", | ||
"dummy1", | ||
path.join(cwd, "dummy1"), | ||
"dummy1", | ||
]); | ||
}, | ||
}); | ||
|
||
await t.step({ | ||
name: "denops.dispatch() invokes APIs of the plugin", | ||
fn: async () => { | ||
denops.dispatcher = { | ||
hello(name: unknown): Promise<unknown> { | ||
return Promise.resolve(`Hello ${name}`); | ||
}, | ||
}; | ||
assertEquals( | ||
await denops.dispatch(denops.name, "hello", "denops"), | ||
"Hello denops", | ||
); | ||
}, | ||
}); | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import type { Service } from "../../service.ts"; | ||
import type { Plugin } from "../base.ts"; | ||
import type { Denops, Dispatcher } from "../../../@denops/mod.ts"; | ||
import { DenopsImpl } from "./denops.ts"; | ||
|
||
export class NaivePlugin implements Plugin { | ||
#denops: DenopsImpl; | ||
|
||
readonly name: string; | ||
readonly script: string; | ||
|
||
dispatcher: Dispatcher = {}; | ||
|
||
constructor(name: string, script: string, service: Service) { | ||
this.name = name; | ||
this.script = script; | ||
this.#denops = new DenopsImpl(name, service); | ||
const suffix = `#${performance.now()}`; | ||
import(`${script}${suffix}`).then(async (mod) => { | ||
try { | ||
await emit(this.#denops, `DenopsSystemPluginPre:${name}`); | ||
await mod.main(this.#denops); | ||
await emit(this.#denops, `DenopsSystemPluginPost:${name}`); | ||
} catch (e) { | ||
console.error(e); | ||
await emit(this.#denops, `DenopsSystemPluginFail:${name}`); | ||
} | ||
}); | ||
} | ||
|
||
async call(fn: string, ...args: unknown[]): Promise<unknown> { | ||
return await this.#denops.dispatcher[fn](...args); | ||
} | ||
|
||
dispose(): void { | ||
// Do nothing | ||
} | ||
} | ||
|
||
function emit(denops: Denops, name: string): Promise<void> { | ||
return denops.cmd(`doautocmd <nomodeline> User ${name}`) | ||
.catch((e) => console.warn(`Failed to emit ${name}: ${e}`)); | ||
} |