Skip to content

Commit 754cf29

Browse files
authored
Merge pull request #69 from vim-denops/improve-batch
Add `batch` module to handle `denops.batch` more flexibly
2 parents 609b959 + cfab46e commit 754cf29

File tree

8 files changed

+453
-138
lines changed

8 files changed

+453
-138
lines changed

denops_std/batch/README.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# batch
2+
3+
`batch` is a module to provide `denops.batch()` helper functions.
4+
5+
- [API documentation](https://doc.deno.land/https/deno.land/x/denops_std/batch/mod.ts)
6+
7+
## Usage
8+
9+
### batch
10+
11+
Use `batch()` to call multiple denops functions sequentially without overhead
12+
like:
13+
14+
```typescript
15+
import { Denops } from "https://deno.land/x/denops_std/mod.ts";
16+
import { batch } from "https://deno.land/x/denops_std/batch/mod.ts";
17+
18+
export async function main(denops: Denops): Promise<void> {
19+
await batch(denops, async (denops) => {
20+
await denops.cmd("setlocal modifiable");
21+
await denops.cmd("setlocal noswap");
22+
await denops.cmd("setlocal nobackup");
23+
});
24+
}
25+
```
26+
27+
The function can be nested thus users can use functions which may internally use
28+
`batch()` like:
29+
30+
```typescript
31+
import { Denops } from "https://deno.land/x/denops_std/mod.ts";
32+
import { batch } from "https://deno.land/x/denops_std/batch/mod.ts";
33+
34+
async function replace(denops: Denops, content: string): Promise<void> {
35+
await batch(denops, (denops) => {
36+
await denops.cmd("setlocal modifiable");
37+
await denops.cmd("setlocal foldmethod=manual");
38+
await denops.call("setline", 1, content.split(/\r?\n/));
39+
await denops.cmd("setlocal nomodifiable");
40+
await denops.cmd("setlocal nomodified");
41+
});
42+
}
43+
44+
export async function main(denops: Denops): Promise<void> {
45+
await batch(denops, async (denops) => {
46+
await denops.cmd("edit Hello");
47+
await replace(denops, "Hello Darkness My Old Friend");
48+
});
49+
}
50+
```
51+
52+
Note that `denops.call()`, `denops.batch()`, or `denops.eval()` always return
53+
falsy value in `batch()`, indicating that you **cannot** write code like below:
54+
55+
```typescript
56+
import { Denops } from "https://deno.land/x/denops_std/mod.ts";
57+
import { batch } from "https://deno.land/x/denops_std/batch/mod.ts";
58+
59+
export async function main(denops: Denops): Promise<void> {
60+
await batch(denops, async (denops) => {
61+
// !!! DON'T DO THIS !!!
62+
if (await denops.eval("&verbose")) {
63+
await denops.cmd("echomsg 'VERBOSE'");
64+
}
65+
await denops.cmd("echomsg 'Hello world'");
66+
});
67+
}
68+
```
69+
70+
### gather
71+
72+
Use `gather()` to call multiple denops functions sequentially without overhead
73+
and return values like:
74+
75+
```typescript
76+
import { Denops } from "https://deno.land/x/denops_std/mod.ts";
77+
import { gather } from "https://deno.land/x/denops_std/batch/mod.ts";
78+
79+
export async function main(denops: Denops): Promise<void> {
80+
const results = await gather(denops, async (denops) => {
81+
await denops.eval("&modifiable");
82+
await denops.eval("&modified");
83+
await denops.eval("&filetype");
84+
});
85+
// results contains the value of modifiable, modified, and filetype
86+
}
87+
```
88+
89+
Not like `batch`, the function can NOT be nested.
90+
91+
Note that `denops.call()`, `denops.batch()`, or `denops.eval()` always return
92+
falsy value in `gather()`, indicating that you **cannot** write code like below:
93+
94+
```typescript
95+
import { Denops } from "https://deno.land/x/denops_std/mod.ts";
96+
import { batch } from "https://deno.land/x/denops_std/batch/mod.ts";
97+
98+
export async function main(denops: Denops): Promise<void> {
99+
const results = await gather(denops, async (denops) => {
100+
// !!! DON'T DO THIS !!!
101+
if (await denops.call("has", "nvim")) {
102+
await denops.call("api_info").version;
103+
} else {
104+
await denops.eval("v:version");
105+
}
106+
});
107+
}
108+
```

denops_std/batch/batch.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Context, Denops, Dispatcher, Meta } from "../deps.ts";
2+
3+
class BatchHelper implements Denops {
4+
#denops: Denops;
5+
#calls: [string, ...unknown[]][];
6+
7+
constructor(denops: Denops) {
8+
this.#denops = denops;
9+
this.#calls = [];
10+
}
11+
12+
static getCalls(helper: BatchHelper): [string, ...unknown[]][] {
13+
return helper.#calls;
14+
}
15+
16+
get name(): string {
17+
return this.#denops.name;
18+
}
19+
20+
get meta(): Meta {
21+
return this.#denops.meta;
22+
}
23+
24+
get dispatcher(): Dispatcher {
25+
return this.#denops.dispatcher;
26+
}
27+
28+
set dispatcher(dispatcher: Dispatcher) {
29+
this.#denops.dispatcher = dispatcher;
30+
}
31+
32+
call(fn: string, ...args: unknown[]): Promise<unknown> {
33+
this.#calls.push([fn, ...args]);
34+
return Promise.resolve();
35+
}
36+
37+
batch(...calls: [string, ...unknown[]][]): Promise<unknown[]> {
38+
this.#calls.push(...calls);
39+
return Promise.resolve([]);
40+
}
41+
42+
cmd(cmd: string, ctx: Context = {}): Promise<void> {
43+
this.call("denops#api#cmd", cmd, ctx);
44+
return Promise.resolve();
45+
}
46+
47+
eval(expr: string, ctx: Context = {}): Promise<unknown> {
48+
this.call("denops#api#eval", expr, ctx);
49+
return Promise.resolve();
50+
}
51+
52+
dispatch(name: string, fn: string, ...args: unknown[]): Promise<unknown> {
53+
return this.#denops.dispatch(name, fn, ...args);
54+
}
55+
}
56+
57+
/**
58+
* Perform batch call
59+
*/
60+
export async function batch(
61+
denops: Denops,
62+
main: (helper: BatchHelper) => Promise<void>,
63+
): Promise<void> {
64+
const helper = new BatchHelper(denops);
65+
await main(helper);
66+
const calls = BatchHelper.getCalls(helper);
67+
await denops.batch(...calls);
68+
}
69+
70+
export type { BatchHelper };

denops_std/batch/batch_test.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { assertEquals, test } from "../deps_test.ts";
2+
import { batch } from "./batch.ts";
3+
4+
test({
5+
mode: "any",
6+
name: "batch() sequentially execute 'denops.call()' as batch process.",
7+
fn: async (denops) => {
8+
await denops.cmd("let g:denops_batch_test = []");
9+
await denops.cmd(
10+
"command! -nargs=1 DenopsBatchTest let g:denops_batch_test += [<f-args>]",
11+
);
12+
await batch(denops, async (denops) => {
13+
assertEquals(
14+
await denops.call("execute", "DenopsBatchTest 1"),
15+
undefined,
16+
);
17+
assertEquals(
18+
await denops.call("execute", "DenopsBatchTest 2"),
19+
undefined,
20+
);
21+
assertEquals(
22+
await denops.call("execute", "DenopsBatchTest 3"),
23+
undefined,
24+
);
25+
});
26+
assertEquals(await denops.eval("g:denops_batch_test") as string[], [
27+
"1",
28+
"2",
29+
"3",
30+
]);
31+
},
32+
});
33+
test({
34+
mode: "any",
35+
name: "batch() sequentially execute 'denops.cmd()' as batch process.",
36+
fn: async (denops) => {
37+
await denops.cmd("let g:denops_batch_test = []");
38+
await denops.cmd(
39+
"command! -nargs=1 DenopsBatchTest let g:denops_batch_test += [<f-args>]",
40+
);
41+
await batch(denops, async (denops) => {
42+
assertEquals(await denops.cmd("DenopsBatchTest 1"), undefined);
43+
assertEquals(await denops.cmd("DenopsBatchTest 2"), undefined);
44+
assertEquals(await denops.cmd("DenopsBatchTest 3"), undefined);
45+
});
46+
assertEquals(await denops.eval("g:denops_batch_test") as string[], [
47+
"1",
48+
"2",
49+
"3",
50+
]);
51+
},
52+
});
53+
test({
54+
mode: "any",
55+
name: "batch() sequentially execute 'denops.eval()' as batch process.",
56+
fn: async (denops) => {
57+
await denops.cmd("let g:denops_batch_test = []");
58+
await batch(denops, async (denops) => {
59+
assertEquals(
60+
await denops.eval("add(g:denops_batch_test, string(1))"),
61+
undefined,
62+
);
63+
assertEquals(
64+
await denops.eval("add(g:denops_batch_test, string(2))"),
65+
undefined,
66+
);
67+
assertEquals(
68+
await denops.eval("add(g:denops_batch_test, string(3))"),
69+
undefined,
70+
);
71+
});
72+
assertEquals(await denops.eval("g:denops_batch_test") as string[], [
73+
"1",
74+
"2",
75+
"3",
76+
]);
77+
},
78+
});
79+
test({
80+
mode: "any",
81+
name: "batch() sequentially execute nested 'batch()' as batch process.",
82+
fn: async (denops) => {
83+
await denops.cmd("let g:denops_batch_test = []");
84+
await denops.cmd(
85+
"command! -nargs=1 DenopsBatchTest let g:denops_batch_test += [<f-args>]",
86+
);
87+
await batch(denops, async (denops) => {
88+
assertEquals(
89+
await denops.call("execute", "DenopsBatchTest 1"),
90+
undefined,
91+
);
92+
assertEquals(
93+
await denops.call("execute", "DenopsBatchTest 2"),
94+
undefined,
95+
);
96+
await batch(denops, async (denops) => {
97+
assertEquals(await denops.cmd("DenopsBatchTest 3"), undefined);
98+
assertEquals(await denops.cmd("DenopsBatchTest 4"), undefined);
99+
await batch(denops, async (denops) => {
100+
assertEquals(
101+
await denops.eval("add(g:denops_batch_test, string(5))"),
102+
undefined,
103+
);
104+
assertEquals(
105+
await denops.eval("add(g:denops_batch_test, string(6))"),
106+
undefined,
107+
);
108+
assertEquals(
109+
await denops.eval("add(g:denops_batch_test, string(7))"),
110+
undefined,
111+
);
112+
});
113+
assertEquals(await denops.cmd("DenopsBatchTest 8"), undefined);
114+
});
115+
assertEquals(
116+
await denops.call("execute", "DenopsBatchTest 9"),
117+
undefined,
118+
);
119+
});
120+
assertEquals(await denops.eval("g:denops_batch_test") as string[], [
121+
"1",
122+
"2",
123+
"3",
124+
"4",
125+
"5",
126+
"6",
127+
"7",
128+
"8",
129+
"9",
130+
]);
131+
},
132+
});

denops_std/batch/gather.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Context, Denops, Dispatcher, Meta } from "../deps.ts";
2+
3+
class GatherHelper implements Denops {
4+
#denops: Denops;
5+
#calls: [string, ...unknown[]][];
6+
7+
constructor(denops: Denops) {
8+
this.#denops = denops;
9+
this.#calls = [];
10+
}
11+
12+
static getCalls(helper: GatherHelper): [string, ...unknown[]][] {
13+
return helper.#calls;
14+
}
15+
16+
get name(): string {
17+
return this.#denops.name;
18+
}
19+
20+
get meta(): Meta {
21+
return this.#denops.meta;
22+
}
23+
24+
get dispatcher(): Dispatcher {
25+
return this.#denops.dispatcher;
26+
}
27+
28+
set dispatcher(dispatcher: Dispatcher) {
29+
this.#denops.dispatcher = dispatcher;
30+
}
31+
32+
call(fn: string, ...args: unknown[]): Promise<unknown> {
33+
this.#calls.push([fn, ...args]);
34+
return Promise.resolve();
35+
}
36+
37+
batch(..._calls: [string, ...unknown[]][]): Promise<unknown[]> {
38+
throw new Error("The 'batch' method is not available on GatherHelper.");
39+
}
40+
41+
cmd(cmd: string, ctx: Context = {}): Promise<void> {
42+
this.call("denops#api#cmd", cmd, ctx);
43+
return Promise.resolve();
44+
}
45+
46+
eval(expr: string, ctx: Context = {}): Promise<unknown> {
47+
this.call("denops#api#eval", expr, ctx);
48+
return Promise.resolve();
49+
}
50+
51+
dispatch(name: string, fn: string, ...args: unknown[]): Promise<unknown> {
52+
return this.#denops.dispatch(name, fn, ...args);
53+
}
54+
}
55+
56+
/**
57+
* Perform gather call
58+
*/
59+
export async function gather(
60+
denops: Denops,
61+
main: (helper: GatherHelper) => Promise<void>,
62+
): Promise<unknown[]> {
63+
const helper = new GatherHelper(denops);
64+
await main(helper);
65+
const calls = GatherHelper.getCalls(helper);
66+
return await denops.batch(...calls);
67+
}
68+
69+
export type { GatherHelper };

0 commit comments

Comments
 (0)