Skip to content

Commit a470596

Browse files
committed
☕ Fix options parse
1 parent 2d3bd1d commit a470596

File tree

3 files changed

+74
-39
lines changed

3 files changed

+74
-39
lines changed

scripts/gen-option/format.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Option } from "./types.ts";
1+
import type { Option, OptionType } from "./types.ts";
22

33
const denops = "https://deno.land/x/denops_core@v4.0.0/mod.ts";
44

@@ -9,16 +9,18 @@ const translate: Record<string, string> = {
99
"function": "function_",
1010
};
1111

12-
function defaultValue(type: string): string {
12+
function defaultValue(type: OptionType): string {
1313
switch (type) {
1414
case "string":
1515
return `""`;
1616
case "number":
1717
return `0`;
1818
case "boolean":
1919
return `false`;
20-
default:
21-
throw new Error(`Unknown type ${type}`);
20+
default: {
21+
const unknownType: never = type;
22+
throw new Error(`Unknown type ${unknownType}`);
23+
}
2224
}
2325
}
2426

@@ -46,7 +48,7 @@ function formatOption({ name, type, scope, docs }: Option): string[] {
4648
return lines;
4749
}
4850

49-
function formatOptionBody(name: string, type: string): string[] {
51+
function formatOptionBody(name: string, type: OptionType): string[] {
5052
const lines = [
5153
` async get(denops: Denops): Promise<${type}> {`,
5254
` return await options.get(denops, "${name}") ?? ${defaultValue(type)};`,
@@ -61,7 +63,7 @@ function formatOptionBody(name: string, type: string): string[] {
6163
return lines;
6264
}
6365

64-
function formatGlobalOptionBody(name: string, type: string): string[] {
66+
function formatGlobalOptionBody(name: string, type: OptionType): string[] {
6567
const lines = [
6668
` async getGlobal(denops: Denops): Promise<${type}> {`,
6769
` return await globalOptions.get(denops, "${name}") ?? ${
@@ -78,7 +80,7 @@ function formatGlobalOptionBody(name: string, type: string): string[] {
7880
return lines;
7981
}
8082

81-
function formatLocalOptionBody(name: string, type: string): string[] {
83+
function formatLocalOptionBody(name: string, type: OptionType): string[] {
8284
const lines = [
8385
` async getLocal(denops: Denops): Promise<${type}> {`,
8486
` return await localOptions.get(denops, "${name}") ?? ${

scripts/gen-option/parse.ts

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import { Option } from "./types.ts";
1+
import { isOptionType, type Option, type OptionType } from "./types.ts";
22
import { regexIndexOf } from "./utils.ts";
33

4+
const fallbackTypes: Record<string, OptionType> = {
5+
"encoding": "string", // not defined type in nvim 0.8.0
6+
};
7+
48
/**
59
* Parse Vim/Neovim help.
610
*
711
* It extract a option definition block like below and return
8-
* a list of `Definition`.
12+
* a list of `Option`.
913
*
1014
* ```text
1115
* *'aleph'* *'al'* *aleph* *Aleph*
@@ -17,44 +21,57 @@ import { regexIndexOf } from "./utils.ts";
1721
* ```
1822
*/
1923
export function parse(content: string) {
24+
// Remove modeline
25+
content = content.replace(/\n vim:[^\n]*\s*$/, "");
26+
2027
const options: Option[] = [];
21-
const optionNames = [...content.matchAll(/\*'(\w+)'\*/g)].map((m) => m[1]);
22-
for (const name of optionNames) {
23-
const pattern = new RegExp(`\n'${name}' (?:'\\w+'\\s*)*\\s+(\\w+)`);
24-
const m1 = content.match(pattern);
25-
if (!m1) {
26-
continue;
28+
for (const match of content.matchAll(/\*'(\w+)'\*/g)) {
29+
const name = match[1];
30+
const block = extractBlock(content, match.index ?? 0);
31+
const option = parseBlock(name, block);
32+
if (option) {
33+
options.push(option);
2734
}
28-
const type = m1[1];
29-
const block = extractBlock(content, m1.index || 0);
30-
31-
const m2 = block.match(/\n\t\t\t(global or local|global|local)\s/);
32-
const scope = (m2 ? m2[1] : "global").split(" or ");
33-
34-
const docs = block
35-
.substring(block.indexOf("\n", (m2 ? m2.index || 0 : 0) + 1))
36-
.split("\n")
37-
.map((v) => v.replace(/^\t/, ""))
38-
.join("\n");
39-
options.push({
40-
name,
41-
type,
42-
scope,
43-
docs,
44-
});
4535
}
4636
return options;
4737
}
4838

4939
function extractBlock(content: string, index: number): string {
5040
const s = content.lastIndexOf("\n", index);
51-
const ms = regexIndexOf(content, /\n[<>\s]/, index);
52-
const me = regexIndexOf(content, /\n[^<>\t]/, ms);
41+
const ms = regexIndexOf(content, /\n[^<>\s]|$/, s);
42+
const me = regexIndexOf(content, /\n[^<>\s]|$/, ms + 1);
5343
const e = content.lastIndexOf("\n", me);
5444
const block = content
5545
.substring(s, e)
56-
.replaceAll(/\*.+?\*/g, "") // Remove tags
57-
.replaceAll(/\s+\n/g, "\n") // Remove trailing '\s'
58-
.trim();
46+
.replace(/(\n<?)(?:\s+\*\S+?\*)+\s*$/, "$1") // Remove next block tag
47+
.trimEnd();
5948
return block;
6049
}
50+
51+
function parseBlock(name: string, body: string): Option | undefined {
52+
const m1 = body.match(
53+
new RegExp(`^'${name}'(?:\\s+'\\w+')*\\s+(\\w+)\\s`, "m"),
54+
);
55+
const type = m1?.[1] ?? fallbackTypes[name];
56+
if (!isOptionType(type)) {
57+
return;
58+
}
59+
60+
const m2 = body.match(/\n\t{3,}(global or local|global|local)(?:\s|$)/);
61+
const scope = (m2?.[1].split(" or ") ?? ["global"]) as Array<
62+
"global" | "local"
63+
>;
64+
65+
const s = regexIndexOf(body, /\n|$/, (m2?.index ?? m1?.index ?? 0) + 1);
66+
body = body.substring(s);
67+
68+
// Remove tags
69+
body = body.replaceAll(/\*\S+?\*/g, "");
70+
// Remove trailing spaces
71+
body = body.split("\n").map((v) => v.trimEnd()).join("\n");
72+
// Remove indent
73+
body = body.split("\n").map((v) => v.replace(/^\t/, "")).join("\n");
74+
75+
const docs = body;
76+
return { name, type, scope, docs };
77+
}

scripts/gen-option/types.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
export type Option = {
22
name: string;
3-
type: string;
4-
scope: string[];
3+
type: OptionType;
4+
scope: OptionScope[];
55
docs: string;
66
};
7+
8+
export const OPTION_TYPES = ["string", "number", "boolean"] as const;
9+
10+
export type OptionType = typeof OPTION_TYPES[number];
11+
12+
export function isOptionType(x: unknown): x is OptionType {
13+
return OPTION_TYPES.includes(x as OptionType);
14+
}
15+
16+
export const OPTION_SCOPES = ["global", "local"] as const;
17+
18+
export type OptionScope = typeof OPTION_SCOPES[number];
19+
20+
export function isOptionScope(x: unknown): x is OptionScope {
21+
return OPTION_SCOPES.includes(x as OptionScope);
22+
}

0 commit comments

Comments
 (0)