Skip to content

Commit d17dd0b

Browse files
committed
👍 Add waitLoaded() for waiting plugin load
1 parent 1b9206a commit d17dd0b

File tree

2 files changed

+43
-5
lines changed

2 files changed

+43
-5
lines changed

denops/@denops-private/service.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,56 @@ import { toFileUrl } from "https://deno.land/std@0.217.0/path/mod.ts";
66
import { toErrorObject } from "https://deno.land/x/errorutil@v0.1.1/mod.ts";
77
import { DenopsImpl, Host } from "./denops.ts";
88

9+
// We can use `PromiseWithResolvers<void>` but Deno 1.38 doesn't have `PromiseWithResolvers`
10+
type Waiter = {
11+
promise: Promise<void>;
12+
resolve: () => void;
13+
};
14+
915
/**
1016
* Service manage plugins and is visible from the host (Vim/Neovim) through `invoke()` function.
1117
*/
1218
export class Service implements Disposable {
1319
#plugins: Map<string, Plugin> = new Map();
20+
#waiters: Map<string, Waiter> = new Map();
1421
#meta: Meta;
1522
#host?: Host;
1623

1724
constructor(meta: Meta) {
1825
this.#meta = meta;
1926
}
2027

28+
#getWaiter(name: string): Waiter {
29+
if (!this.#waiters.has(name)) {
30+
this.#waiters.set(name, Promise.withResolvers());
31+
}
32+
return this.#waiters.get(name)!;
33+
}
34+
2135
bind(host: Host): void {
2236
this.#host = host;
2337
}
2438

25-
load(
39+
async load(
2640
name: string,
2741
script: string,
2842
suffix = "",
2943
): Promise<void> {
3044
if (!this.#host) {
31-
return Promise.reject(new Error("No host is bound to the service"));
45+
throw new Error("No host is bound to the service");
3246
}
3347
let plugin = this.#plugins.get(name);
3448
if (plugin) {
3549
if (this.#meta.mode === "debug") {
3650
console.log(`A denops plugin '${name}' is already loaded. Skip`);
3751
}
38-
return Promise.resolve();
52+
return;
3953
}
4054
const denops = new DenopsImpl(name, this.#meta, this.#host, this);
4155
plugin = new Plugin(denops, name, script);
4256
this.#plugins.set(name, plugin);
43-
return plugin.load(suffix);
57+
await plugin.load(suffix);
58+
this.#getWaiter(name).resolve();
4459
}
4560

4661
reload(
@@ -54,12 +69,17 @@ export class Service implements Disposable {
5469
return Promise.resolve();
5570
}
5671
this.#plugins.delete(name);
72+
this.#waiters.delete(name);
5773
// Import module with fragment so that reload works properly
5874
// https://github.com/vim-denops/denops.vim/issues/227
5975
const suffix = `#${performance.now()}`;
6076
return this.load(name, plugin.script, suffix);
6177
}
6278

79+
waitLoaded(name: string): Promise<void> {
80+
return this.#getWaiter(name).promise;
81+
}
82+
6383
async #dispatch(name: string, fn: string, args: unknown[]): Promise<unknown> {
6484
const plugin = this.#plugins.get(name);
6585
if (!plugin) {

denops/@denops-private/service_test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
assert,
3+
assertEquals,
34
assertMatch,
45
assertRejects,
56
} from "https://deno.land/std@0.217.0/assert/mod.ts";
@@ -9,9 +10,10 @@ import {
910
stub,
1011
} from "https://deno.land/std@0.217.0/testing/mock.ts";
1112
import type { Meta } from "https://deno.land/x/denops_core@v6.0.5/mod.ts";
13+
import { promiseState } from "https://deno.land/x/async@v2.1.0/mod.ts";
14+
import { unimplemented } from "https://deno.land/x/errorutil@v0.1.1/mod.ts";
1215
import type { Host } from "./denops.ts";
1316
import { Service } from "./service.ts";
14-
import { unimplemented } from "https://deno.land/x/errorutil@v0.1.1/mod.ts";
1517

1618
const scriptValid =
1719
new URL("./testdata/dummy_valid_plugin.ts", import.meta.url).href;
@@ -60,6 +62,15 @@ Deno.test("Service", async (t) => {
6062

6163
service.bind(host);
6264

65+
const waitLoaded = service.waitLoaded("dummy");
66+
67+
await t.step(
68+
"the result promise of waitLoaded() is 'pending' when the plugin is not loaded yet",
69+
async () => {
70+
assertEquals(await promiseState(waitLoaded), "pending");
71+
},
72+
);
73+
6374
await t.step("load() loads plugin and emits autocmd events", async () => {
6475
const s = stub(host, "call");
6576
try {
@@ -91,6 +102,13 @@ Deno.test("Service", async (t) => {
91102
}
92103
});
93104

105+
await t.step(
106+
"the result promise of waitLoaded() become 'fulfilled' when the plugin is loaded",
107+
async () => {
108+
assertEquals(await promiseState(waitLoaded), "fulfilled");
109+
},
110+
);
111+
94112
await t.step(
95113
"load() loads plugin and emits autocmd events (failure)",
96114
async () => {

0 commit comments

Comments
 (0)