Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .scripts/gen-option/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function parse(content: string) {
name: string;
start: number;
block: string;
err: Error;
err: unknown;
}[] = [];
let last = -1;
for (const match of content.matchAll(/\*'(\w+)'\*/g)) {
Expand Down
110 changes: 91 additions & 19 deletions buffer/decoration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

const cacheKey = "denops_std/buffer/decoration/vimDecorate/rs@1";

const prefix = "denops_std:buffer:decoration:decorate";

export interface Decoration {
/**
* Line number
Expand Down Expand Up @@ -61,7 +63,7 @@
export function decorate(
denops: Denops,
bufnr: number,
decorations: Decoration[],
decorations: readonly Decoration[],
): Promise<void> {
switch (denops.meta.host) {
case "vim":
Expand Down Expand Up @@ -131,17 +133,30 @@
}
}

function uniq<T>(array: T[]): T[] {
export function listDecorations(
denops: Denops,
bufnr: number,
): Promise<Decoration[]> {
switch (denops.meta.host) {
case "vim":
return vimListDecorations(denops, bufnr);
case "nvim":
return nvimListDecorations(denops, bufnr);
default:
unreachable(denops.meta.host);

Check warning on line 146 in buffer/decoration.ts

View check run for this annotation

Codecov / codecov/patch

buffer/decoration.ts#L146

Added line #L146 was not covered by tests
}
}

function uniq<T>(array: readonly T[]): T[] {
return [...new Set(array)];
}

async function vimDecorate(
denops: Denops,
bufnr: number,
decorations: Decoration[],
decorations: readonly Decoration[],
): Promise<void> {
const toPropType = (n: string) =>
`denops_std:buffer:decoration:decorate:${n}`;
const toPropType = (n: string) => `${prefix}:${n}`;
const rs = (denops.context[cacheKey] ?? new Set()) as Set<string>;
denops.context[cacheKey] = rs;
const hs = uniq(decorations.map((v) => v.highlight)).filter((v) =>
Expand All @@ -163,8 +178,9 @@
});
rs.add(highlight);
}
let id = 1;
for (const [type, props] of decoMap.entries()) {
await vimFn.prop_add_list(denops, { bufnr, type }, [...props]);
await vimFn.prop_add_list(denops, { bufnr, type, id: id++ }, [...props]);
}
});
}
Expand All @@ -178,11 +194,11 @@
const propList = await vimFn.prop_list(denops, start + 1, {
bufnr,
end_lnum: end,
}) as { id: string; type: string }[];
}) as { id: number; type: string }[];
const propIds = new Set(
propList.filter((p) =>
p.type.startsWith("denops_std:buffer:decoration:decorate:")
).map((p) => p.id),
propList
.filter((p) => p.type.startsWith(`${prefix}:`))
.map((p) => p.id),
);
await batch(denops, async (denops) => {
for (const propId of propIds) {
Expand All @@ -191,15 +207,39 @@
});
}

async function vimListDecorations(
denops: Denops,
bufnr: number,
): Promise<Decoration[]> {
const props = await vimFn.prop_list(denops, 1, {
bufnr,
end_lnum: -1,
}) as {
col: number;
end: number;
id: number;
length: number;
lnum: number;
start: number;
type: string;
type_bufnr: number;
}[];
return props
.filter((prop) => prop.type.startsWith(`${prefix}:`))
.map((prop) => ({
line: prop.lnum,
column: prop.col,
length: prop.length,
highlight: prop.type.split(":").pop() as string,
}));
}

async function nvimDecorate(
denops: Denops,
bufnr: number,
decorations: Decoration[],
decorations: readonly Decoration[],
): Promise<void> {
const ns = await nvimFn.nvim_create_namespace(
denops,
"denops_std:buffer:decoration:decorate",
);
const ns = await nvimFn.nvim_create_namespace(denops, prefix);
for (const chunk of itertools.chunked(decorations, 1000)) {
await batch(denops, async (denops) => {
for (const deco of chunk) {
Expand All @@ -223,9 +263,41 @@
start: number,
end: number,
): Promise<void> {
const ns = await nvimFn.nvim_create_namespace(
denops,
"denops_std:buffer:decoration:decorate",
);
const ns = await nvimFn.nvim_create_namespace(denops, prefix);
await nvimFn.nvim_buf_clear_namespace(denops, bufnr, ns, start, end);
}

async function nvimListDecorations(
denops: Denops,
bufnr: number,
): Promise<Decoration[]> {
const ns = await nvimFn.nvim_create_namespace(denops, prefix);
const extmarks = await nvimFn.nvim_buf_get_extmarks(
denops,
bufnr,
ns,
0,
-1,
{ details: true },
) as [
extmark_id: number,
row: number,
col: number,
{
hl_group: string;
hl_eol: boolean;
end_right_gravity: boolean;
priority: number;
right_gravity: boolean;
end_col: number;
ns_id: number;
end_row: number;
},
][];
return extmarks.map((extmark) => ({
line: extmark[1] + 1,
column: extmark[2] + 1,
length: extmark[3].end_col - extmark[2],
highlight: extmark[3].hl_group,
}));
}
161 changes: 147 additions & 14 deletions buffer/decoration_test.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
import { assertEquals } from "@std/assert";
import { omit } from "@std/collections";
import { test } from "@denops/test";
import * as fn from "../function/mod.ts";
import * as vimFn from "../function/vim/mod.ts";
import * as nvimFn from "../function/nvim/mod.ts";
import * as buffer from "./buffer.ts";
import { decorate } from "./decoration.ts";
import { decorate, listDecorations, undecorate } from "./decoration.ts";

test({
mode: "vim",
name: "decorate define highlights as text properties",
fn: async (denops) => {
const collect = async (bufnr: number) => {
const lines = await fn.getbufline(denops, bufnr, 1, "$");
const props = [];
for (let line = 1; line <= lines.length; line++) {
props.push(
...(await vimFn.prop_list(denops, line, { bufnr }) as unknown[]),
);
}
return props;
const props = await vimFn.prop_list(denops, 1, {
bufnr,
end_lnum: -1,
}) as {
col: number;
end: number;
id: number;
length: number;
lnum: number;
start: number;
type: string;
type_bufnr: number;
}[];
return props.map((prop) => omit(prop, ["id"]));
};
const bufnr = await fn.bufnr(denops);
await buffer.append(denops, bufnr, [
await buffer.replace(denops, bufnr, [
"Hello",
"Darkness",
"My",
Expand All @@ -44,28 +51,45 @@ test({
assertEquals(await collect(bufnr), [{
col: 1,
end: 1,
id: 0,
length: 1,
length: 5,
lnum: 1,
start: 1,
type: "denops_std:buffer:decoration:decorate:Title",
type_bufnr: 0,
}, {
col: 2,
end: 1,
id: 0,
length: 3,
lnum: 2,
start: 1,
type: "denops_std:buffer:decoration:decorate:Search",
type_bufnr: 0,
}]);

// Re-appling the same decorations is OK
await decorate(denops, bufnr, [
{
line: 1,
column: 1,
length: 5,
highlight: "Title",
},
{
line: 2,
column: 2,
length: 3,
highlight: "Search",
},
]);
},
});

test({
mode: "nvim",
name: "decorate define highlights as extmarks",
fn: async (denops) => {
const bufnr = await fn.bufnr(denops);
await buffer.append(denops, bufnr, [
await buffer.replace(denops, bufnr, [
"Hello",
"Darkness",
"My",
Expand Down Expand Up @@ -98,3 +122,112 @@ test({
);
},
});

test({
mode: "all",
name: "listDecorations list decorations defined in the buffer",
fn: async (denops) => {
const bufnr = await fn.bufnr(denops);
await buffer.replace(denops, bufnr, [
"Hello",
"Darkness",
"My",
"Old friend",
]);
await decorate(denops, bufnr, [
{
line: 1,
column: 1,
length: 5,
highlight: "Title",
},
{
line: 2,
column: 2,
length: 3,
highlight: "Search",
},
]);
assertEquals(await listDecorations(denops, bufnr), [
{
line: 1,
column: 1,
length: 5,
highlight: "Title",
},
{
line: 2,
column: 2,
length: 3,
highlight: "Search",
},
]);
},
});

test({
mode: "all",
name: "undecorate clears decorations in the buffer",
fn: async (denops) => {
const bufnr = await fn.bufnr(denops);
await buffer.replace(denops, bufnr, [
"Hello",
"Darkness",
"My",
"Old friend",
]);
await decorate(denops, bufnr, [
{
line: 1,
column: 1,
length: 5,
highlight: "Title",
},
{
line: 2,
column: 2,
length: 3,
highlight: "Search",
},
]);
await undecorate(denops, bufnr);
assertEquals(await listDecorations(denops, bufnr), []);
},
});

test({
mode: "all",
name: "undecorate clears decorations in specified region in the buffer",
fn: async (denops) => {
const bufnr = await fn.bufnr(denops);
await buffer.replace(denops, bufnr, [
"Hello",
"Darkness",
"My",
"Old friend",
]);
await decorate(denops, bufnr, [
{
line: 1,
column: 1,
length: 5,
highlight: "Title",
},
{
line: 2,
column: 2,
length: 3,
highlight: "Search",
},
]);
await undecorate(denops, bufnr, 0, 1);
assertEquals(await listDecorations(denops, bufnr), [
{
line: 2,
column: 2,
length: 3,
highlight: "Search",
},
]);
},
});
Loading