Skip to content

Commit e0b49a7

Browse files
authored
Merge pull request #29 from vim-denops/register
Add `anonymous.ts` to help registering anonymous functions
2 parents 6e69459 + 4944696 commit e0b49a7

File tree

2 files changed

+187
-0
lines changed

2 files changed

+187
-0
lines changed

denops_std/anonymous/mod.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { Denops } from "../vendor/https/deno.land/x/denops_core/mod.ts";
2+
3+
// https://github.com/microsoft/TypeScript/issues/26223#issuecomment-674500430
4+
export type TupleOf<T, N extends number> = N extends N
5+
? number extends N ? T[] : _TupleOf<T, N, []>
6+
: never;
7+
export type _TupleOf<T, N extends number, R extends unknown[]> =
8+
R["length"] extends N ? R
9+
: _TupleOf<T, N, [T, ...R]>;
10+
11+
/**
12+
* Anonymous function identifier
13+
*/
14+
export type Identifier = string;
15+
16+
/**
17+
* Anonymous function callback
18+
*/
19+
export type Callback = (...args: unknown[]) => Promise<unknown> | unknown;
20+
21+
/**
22+
* Add anonymous functions as a denops API and return the identifiers
23+
*/
24+
export function add<N extends number>(
25+
denops: Denops,
26+
...callbacks: Callback[] & { length: N }
27+
): TupleOf<Identifier, N> {
28+
return callbacks.map((callback) => {
29+
const id = makeid();
30+
denops.dispatcher[id] = async (...args: unknown[]) => {
31+
return await callback(...args);
32+
};
33+
return id;
34+
// deno-lint-ignore no-explicit-any
35+
}) as any;
36+
}
37+
38+
/**
39+
* Add oneshot anonymous functions as a denops API and return the identifiers
40+
*/
41+
export function once<N extends number>(
42+
denops: Denops,
43+
...callbacks: Callback[] & { length: N }
44+
): TupleOf<Identifier, N> {
45+
return callbacks.map((callback) => {
46+
const id = makeid();
47+
denops.dispatcher[id] = async (...args: unknown[]): Promise<unknown> => {
48+
try {
49+
return await callback(...args);
50+
} finally {
51+
remove(denops, id);
52+
}
53+
};
54+
return id;
55+
// deno-lint-ignore no-explicit-any
56+
}) as any;
57+
}
58+
59+
/**
60+
* Remove anonymous functions identified by names from a denops API
61+
*/
62+
export function remove<N extends number>(
63+
denops: Denops,
64+
...ids: Identifier[] & { length: N }
65+
): TupleOf<boolean, N> {
66+
return ids.map((id) => {
67+
if (id in denops.dispatcher) {
68+
delete denops.dispatcher[id];
69+
return true;
70+
}
71+
return false;
72+
// deno-lint-ignore no-explicit-any
73+
}) as any;
74+
}
75+
76+
// https://stackoverflow.com/a/1349426
77+
function makeid(): string {
78+
const s = 32;
79+
const cs = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
80+
const cn = cs.length;
81+
let r = "";
82+
for (let i = 0; i < s; i++) {
83+
r += cs.charAt(Math.floor(Math.random() * cn));
84+
}
85+
return `anonymous:${r}`;
86+
}

denops_std/anonymous/mod_test.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import {
2+
assertEquals,
3+
assertThrowsAsync,
4+
} from "../vendor/https/deno.land/std/testing/asserts.ts";
5+
import { test } from "../vendor/https/deno.land/x/denops_core/test/mod.ts";
6+
import * as anonymous from "./mod.ts";
7+
8+
test({
9+
mode: "all",
10+
name: "add() registers anonymous functions",
11+
fn: async (denops) => {
12+
const ids = anonymous.add(
13+
denops,
14+
() => Promise.resolve("0"),
15+
() => Promise.resolve("1"),
16+
() => Promise.resolve("2"),
17+
);
18+
assertEquals(await denops.dispatch(denops.name, ids[0]), "0");
19+
assertEquals(await denops.dispatch(denops.name, ids[1]), "1");
20+
assertEquals(await denops.dispatch(denops.name, ids[2]), "2");
21+
assertEquals(await denops.dispatch(denops.name, ids[0]), "0");
22+
assertEquals(await denops.dispatch(denops.name, ids[1]), "1");
23+
assertEquals(await denops.dispatch(denops.name, ids[2]), "2");
24+
},
25+
});
26+
27+
test({
28+
mode: "all",
29+
name: "once() registers oneshot anonymous functions",
30+
fn: async (denops) => {
31+
const ids = anonymous.once(
32+
denops,
33+
() => Promise.resolve("0"),
34+
() => Promise.resolve("1"),
35+
() => Promise.resolve("2"),
36+
);
37+
assertEquals(await denops.dispatch(denops.name, ids[0]), "0");
38+
assertEquals(await denops.dispatch(denops.name, ids[1]), "1");
39+
assertEquals(await denops.dispatch(denops.name, ids[2]), "2");
40+
41+
// The method will be removed
42+
await assertThrowsAsync(
43+
async () => {
44+
await denops.dispatch(denops.name, ids[0]);
45+
},
46+
undefined,
47+
`No method '${ids[0]}' exists`,
48+
);
49+
await assertThrowsAsync(
50+
async () => {
51+
await denops.dispatch(denops.name, ids[1]);
52+
},
53+
undefined,
54+
`No method '${ids[1]}' exists`,
55+
);
56+
await assertThrowsAsync(
57+
async () => {
58+
await denops.dispatch(denops.name, ids[2]);
59+
},
60+
undefined,
61+
`No method '${ids[2]}' exists`,
62+
);
63+
},
64+
});
65+
66+
test({
67+
mode: "all",
68+
name: "remove() unregisters anonymous functions identified by ids",
69+
fn: async (denops) => {
70+
const ids = anonymous.add(
71+
denops,
72+
() => Promise.resolve("0"),
73+
() => Promise.resolve("1"),
74+
() => Promise.resolve("2"),
75+
);
76+
assertEquals(anonymous.remove(denops, ...ids), [true, true, true]);
77+
78+
// The method is removed
79+
await assertThrowsAsync(
80+
async () => {
81+
await denops.dispatch(denops.name, ids[0]);
82+
},
83+
undefined,
84+
`No method '${ids[0]}' exists`,
85+
);
86+
await assertThrowsAsync(
87+
async () => {
88+
await denops.dispatch(denops.name, ids[1]);
89+
},
90+
undefined,
91+
`No method '${ids[1]}' exists`,
92+
);
93+
await assertThrowsAsync(
94+
async () => {
95+
await denops.dispatch(denops.name, ids[2]);
96+
},
97+
undefined,
98+
`No method '${ids[2]}' exists`,
99+
);
100+
},
101+
});

0 commit comments

Comments
 (0)