Skip to content

Commit 08b9d76

Browse files
authored
👍 Improve decoration module (#161)
* 💪 Fix text property name on Vim * 🌿 Add unittests for `decorate` * 🚀 Use `prop_add_list` to enhance performance * 👍 Add `undecorate` function Close #151
1 parent 0a9ad26 commit 08b9d76

File tree

3 files changed

+208
-23
lines changed

3 files changed

+208
-23
lines changed

denops_std/buffer/README.md

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ import { open, reload } from "../buffer/mod.ts";
6868

6969
export async function main(denops: Denops): Promise<void> {
7070
await open(denops, "README.md");
71-
const bufnr = await fn.bufnr(denops) as number;
71+
const bufnr = (await fn.bufnr(denops)) as number;
7272
// ...
7373
// Reload the content of the `bufnr` buffer.
7474
await reload(denops, bufnr);
@@ -90,7 +90,7 @@ import { decode, open, replace } from "../buffer/mod.ts";
9090

9191
export async function main(denops: Denops): Promise<void> {
9292
await open(denops, "README.md");
93-
const bufnr = await fn.bufnr(denops) as number;
93+
const bufnr = (await fn.bufnr(denops)) as number;
9494
const data = await Deno.readFile("README.md");
9595
const { content } = await decode(denops, bufnr, data);
9696
await replace(denops, bufnr, content);
@@ -111,7 +111,7 @@ import { append, open } from "../buffer/mod.ts";
111111

112112
export async function main(denops: Denops): Promise<void> {
113113
await open(denops, "README.md");
114-
const bufnr = await fn.bufnr(denops) as number;
114+
const bufnr = (await fn.bufnr(denops)) as number;
115115
// Append the content under the cursor position of the `bufnr` buffer
116116
await append(denops, bufnr, ["Hello", "World"]);
117117
}
@@ -131,7 +131,7 @@ import { open, replace } from "../buffer/mod.ts";
131131

132132
export async function main(denops: Denops): Promise<void> {
133133
await open(denops, "README.md");
134-
const bufnr = await fn.bufnr(denops) as number;
134+
const bufnr = (await fn.bufnr(denops)) as number;
135135
// Set the content of the `bufnr` buffer
136136
await replace(denops, bufnr, ["Hello", "World"]);
137137
}
@@ -153,7 +153,7 @@ import { assign, open } from "../buffer/mod.ts";
153153

154154
export async function main(denops: Denops): Promise<void> {
155155
await open(denops, "README.md");
156-
const bufnr = await fn.bufnr(denops) as number;
156+
const bufnr = (await fn.bufnr(denops)) as number;
157157
const content = await Deno.readFile("README.md");
158158
await assign(denops, bufnr, content);
159159
}
@@ -171,7 +171,7 @@ import { assign, open } from "../buffer/mod.ts";
171171

172172
export async function main(denops: Denops): Promise<void> {
173173
await open(denops, "README.md");
174-
const bufnr = await fn.bufnr(denops) as number;
174+
const bufnr = (await fn.bufnr(denops)) as number;
175175
const content = await Deno.readFile("README.md");
176176
// A preprocessor that replace all non words to "-"
177177
const preprocessor = (repl: string[]): string[] => {
@@ -194,7 +194,7 @@ import { concrete, open, replace } from "../buffer/mod.ts";
194194

195195
export async function main(denops: Denops): Promise<void> {
196196
await open(denops, "README.md");
197-
const bufnr = await fn.bufnr(denops) as number;
197+
const bufnr = (await fn.bufnr(denops)) as number;
198198
await fn.setbufvar(denops, bufnr, "&buftype", "nofile");
199199
await replace(denops, bufnr, ["Hello", "World"]);
200200
await concrete(denops, bufnr);
@@ -215,7 +215,7 @@ import { ensure, open } from "../buffer/mod.ts";
215215

216216
export async function main(denops: Denops): Promise<void> {
217217
await open(denops, "README.md");
218-
const bufnr = await fn.bufnr(denops) as number;
218+
const bufnr = (await fn.bufnr(denops)) as number;
219219
// ...
220220
await ensure(denops, bufnr, async () => {
221221
await option.buftype.set(denops, "nofile");
@@ -239,7 +239,7 @@ import { modifiable, open } from "../buffer/mod.ts";
239239

240240
export async function main(denops: Denops): Promise<void> {
241241
await open(denops, "README.md");
242-
const bufnr = await fn.bufnr(denops) as number;
242+
const bufnr = (await fn.bufnr(denops)) as number;
243243
// ...
244244
await modifiable(denops, bufnr, async () => {
245245
await fn.setline(denops, 1, ["Hello", "World"]);
@@ -258,7 +258,7 @@ import { decorate, open } from "../buffer/mod.ts";
258258

259259
export async function main(denops: Denops): Promise<void> {
260260
await open(denops, "README.md");
261-
const bufnr = await fn.bufnr(denops) as number;
261+
const bufnr = (await fn.bufnr(denops)) as number;
262262
// ...
263263
await decorate(denops, bufnr, [
264264
{
@@ -277,5 +277,43 @@ export async function main(denops: Denops): Promise<void> {
277277
}
278278
```
279279

280+
It uses `prop_add_list` in Vim and `nvim_buf_add_highlight` in Neovim to
281+
decorate the buffer.
282+
283+
### undecorate
284+
285+
Use `undecorate()` to undecorate the `bufnr` buffer decorated with `decorate`
286+
like:
287+
288+
```typescript
289+
import { Denops } from "../mod.ts";
290+
import * as fn from "../function/mod.ts";
291+
import { decorate, open, undecorate } from "../buffer/mod.ts";
292+
293+
export async function main(denops: Denops): Promise<void> {
294+
await open(denops, "README.md");
295+
const bufnr = (await fn.bufnr(denops)) as number;
296+
// ...
297+
await decorate(denops, bufnr, [
298+
{
299+
line: 1,
300+
column: 1,
301+
length: 10,
302+
highlight: "Special",
303+
},
304+
{
305+
line: 2,
306+
column: 2,
307+
length: 3,
308+
highlight: "Comment",
309+
},
310+
]);
311+
312+
// Do something
313+
314+
await undecorate(denops, bufnr);
315+
}
316+
```
317+
280318
It uses `prop_add` in Vim and `nvim_buf_add_highlight` in Neovim to decorate the
281319
buffer.

denops_std/buffer/decoration.ts

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,23 @@ export function decorate(
3636
}
3737
}
3838

39+
/**
40+
* Undecorate the specified buffer
41+
*/
42+
export function undecorate(
43+
denops: Denops,
44+
bufnr: number,
45+
): Promise<void> {
46+
switch (denops.meta.host) {
47+
case "vim":
48+
return vimUndecorate(denops, bufnr);
49+
case "nvim":
50+
return nvimUndecorate(denops, bufnr);
51+
default:
52+
unreachable(denops.meta.host);
53+
}
54+
}
55+
3956
function uniq<T>(array: T[]): T[] {
4057
return [...new Set(array)];
4158
}
@@ -45,12 +62,20 @@ async function vimDecorate(
4562
bufnr: number,
4663
decorations: Decoration[],
4764
): Promise<void> {
48-
const toPropType = (n: string) => `denps_std:buffer:decoration:decorate:${n}`;
65+
const toPropType = (n: string) =>
66+
`denops_std:buffer:decoration:decorate:${n}`;
4967
const rs = (denops.context[cacheKey] ?? new Set()) as Set<string>;
5068
denops.context[cacheKey] = rs;
5169
const hs = uniq(decorations.map((v) => v.highlight)).filter((v) =>
5270
!rs.has(v)
5371
);
72+
const decoMap = new Map<string, Set<[number, number, number, number]>>();
73+
for (const deco of decorations) {
74+
const propType = toPropType(deco.highlight);
75+
const props = decoMap.get(propType) ?? new Set();
76+
props.add([deco.line, deco.column, deco.line, deco.column + deco.length]);
77+
decoMap.set(propType, props);
78+
}
5479
await batch.batch(denops, async (denops) => {
5580
for (const highlight of hs) {
5681
const propType = toPropType(highlight);
@@ -60,19 +85,30 @@ async function vimDecorate(
6085
});
6186
rs.add(highlight);
6287
}
88+
for (const [type, props] of decoMap.entries()) {
89+
await vimFn.prop_add_list(denops, { bufnr, type }, [...props]);
90+
}
91+
});
92+
}
93+
94+
async function vimUndecorate(
95+
denops: Denops,
96+
bufnr: number,
97+
): Promise<void> {
98+
const propList = await vimFn.prop_list(denops, 1, {
99+
bufnr,
100+
end_lnum: -1,
101+
}) as { id: string; type: string }[];
102+
const propIds = new Set(
103+
propList.filter((p) =>
104+
p.type.startsWith("denops_std:buffer:decoration:decorate:")
105+
).map((p) => p.id),
106+
);
107+
await batch.batch(denops, async (denops) => {
108+
for (const propId of propIds) {
109+
await vimFn.prop_remove(denops, { id: propId, bufnr, all: true });
110+
}
63111
});
64-
for (const chunk of itertools.chunked(decorations, 1000)) {
65-
await batch.batch(denops, async (denops) => {
66-
for (const deco of chunk) {
67-
const propType = toPropType(deco.highlight);
68-
await vimFn.prop_add(denops, deco.line, deco.column, {
69-
bufnr,
70-
length: deco.length,
71-
type: propType,
72-
});
73-
}
74-
});
75-
}
76112
}
77113

78114
async function nvimDecorate(
@@ -100,3 +136,14 @@ async function nvimDecorate(
100136
});
101137
}
102138
}
139+
140+
async function nvimUndecorate(
141+
denops: Denops,
142+
bufnr: number,
143+
): Promise<void> {
144+
const ns = await nvimFn.nvim_create_namespace(
145+
denops,
146+
"denops_std:buffer:decoration:decorate",
147+
);
148+
await nvimFn.nvim_buf_clear_namespace(denops, bufnr, ns, 0, -1);
149+
}

denops_std/buffer/decoration_test.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { assertEquals } from "https://deno.land/std@0.167.0/testing/asserts.ts";
2+
import { test } from "../test/mod.ts";
3+
import * as fn from "../function/mod.ts";
4+
import * as vimFn from "../function/vim/mod.ts";
5+
import * as nvimFn from "../function/nvim/mod.ts";
6+
import * as buffer from "./buffer.ts";
7+
import { decorate } from "./decoration.ts";
8+
9+
test({
10+
mode: "vim",
11+
name: "decorate define highlights as text properties",
12+
fn: async (denops) => {
13+
const collect = async (bufnr: number) => {
14+
const lines = await fn.getbufline(denops, bufnr, 1, "$");
15+
const props = [];
16+
for (let line = 1; line <= lines.length; line++) {
17+
props.push(
18+
...(await vimFn.prop_list(denops, line, { bufnr }) as unknown[]),
19+
);
20+
}
21+
return props;
22+
};
23+
const bufnr = await fn.bufnr(denops);
24+
await buffer.append(denops, bufnr, [
25+
"Hello",
26+
"Darkness",
27+
"My",
28+
"Old friend",
29+
]);
30+
await decorate(denops, bufnr, [
31+
{
32+
line: 1,
33+
column: 1,
34+
length: 5,
35+
highlight: "Title",
36+
},
37+
{
38+
line: 2,
39+
column: 2,
40+
length: 3,
41+
highlight: "Search",
42+
},
43+
]);
44+
assertEquals(await collect(bufnr), [{
45+
col: 1,
46+
end: 1,
47+
id: 0,
48+
length: 1,
49+
start: 1,
50+
type: "denops_std:buffer:decoration:decorate:Title",
51+
type_bufnr: 0,
52+
}, {
53+
col: 2,
54+
end: 1,
55+
id: 0,
56+
length: 3,
57+
start: 1,
58+
type: "denops_std:buffer:decoration:decorate:Search",
59+
type_bufnr: 0,
60+
}]);
61+
},
62+
});
63+
test({
64+
mode: "nvim",
65+
name: "decorate define highlights as extmarks",
66+
fn: async (denops) => {
67+
const bufnr = await fn.bufnr(denops);
68+
await buffer.append(denops, bufnr, [
69+
"Hello",
70+
"Darkness",
71+
"My",
72+
"Old friend",
73+
]);
74+
await decorate(denops, bufnr, [
75+
{
76+
line: 1,
77+
column: 1,
78+
length: 5,
79+
highlight: "Title",
80+
},
81+
{
82+
line: 2,
83+
column: 2,
84+
length: 3,
85+
highlight: "Search",
86+
},
87+
]);
88+
const ns = await nvimFn.nvim_create_namespace(
89+
denops,
90+
"denops_std:buffer:decoration:decorate",
91+
);
92+
assertEquals(
93+
await nvimFn.nvim_buf_get_extmarks(denops, bufnr, ns, 0, -1, {}),
94+
[
95+
[1, 0, 0],
96+
[2, 1, 1],
97+
],
98+
);
99+
},
100+
});

0 commit comments

Comments
 (0)