Skip to content

Commit 3d84dbc

Browse files
feat: add tree-sitter ABI version info to health check
1 parent 5df2eab commit 3d84dbc

File tree

4 files changed

+111
-111
lines changed

4 files changed

+111
-111
lines changed

doc/render-markdown.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*render-markdown.txt* For NVIM v0.11.3 Last change: 2025 August 04
1+
*render-markdown.txt* For NVIM v0.11.3 Last change: 2025 August 07
22

33
==============================================================================
44
Table of Contents *render-markdown-table-of-contents*

lua/render-markdown/health.lua

Lines changed: 50 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ local state = require('render-markdown.state')
55
local M = {}
66

77
---@private
8-
M.version = '8.6.11'
8+
M.version = '8.6.12'
99

1010
function M.check()
11-
M.start('version')
12-
vim.health.ok('plugin ' .. M.version)
11+
M.start('versions')
1312
M.neovim('0.9', '0.11')
13+
vim.health.ok('tree-sitter ABI: ' .. vim.treesitter.language_version)
14+
vim.health.ok('plugin: ' .. M.version)
1415

1516
M.start('configuration')
1617
local errors = state.validate()
@@ -25,17 +26,13 @@ function M.check()
2526
local latex = config.latex
2627
local html = config.html
2728

28-
M.start('treesitter')
29-
M.parser('markdown', true)
30-
M.highlights('markdown')
31-
M.highlighter('markdown')
32-
M.parser('markdown_inline', true)
33-
M.highlights('markdown_inline')
29+
M.ts_info('markdown', true, true)
30+
M.ts_info('markdown_inline', true, false)
3431
if latex.enabled then
35-
M.parser('latex', false)
32+
M.ts_info('latex', false, false)
3633
end
3734
if html.enabled then
38-
M.parser('html', false)
35+
M.ts_info('html', false, false)
3936
end
4037

4138
M.start('icons')
@@ -88,48 +85,59 @@ end
8885
---@private
8986
---@param language string
9087
---@param required boolean
91-
function M.parser(language, required)
92-
local ok, parser = pcall(vim.treesitter.get_parser, 0, language)
93-
if ok and parser then
94-
vim.health.ok(language .. ': parser installed')
88+
---@param active boolean
89+
function M.ts_info(language, required, active)
90+
M.start('tree-sitter ' .. language)
91+
92+
local has_parser, parser = pcall(vim.treesitter.get_parser, 0, language)
93+
if has_parser and parser then
94+
vim.health.ok('parser: installed')
9595
else
96-
local message = language .. ': parser not installed'
96+
local message = 'parser: not installed'
9797
if not required then
9898
vim.health.warn(message, M.disable(language))
9999
else
100100
vim.health.error(message)
101101
end
102102
end
103-
end
104103

105-
---@private
106-
---@param language string
107-
function M.highlights(language)
108-
local files = vim.treesitter.query.get_files(language, 'highlights')
109-
if #files > 0 then
110-
for _, file in ipairs(files) do
111-
local path = vim.fn.fnamemodify(file, ':~')
112-
vim.health.ok(language .. ': highlights ' .. path)
113-
end
104+
local has_info, info = pcall(vim.treesitter.language.inspect, language)
105+
if has_info and info then
106+
vim.health.ok('ABI: ' .. info.abi_version)
114107
else
115-
vim.health.error(language .. ': highlights missing')
108+
local message = 'ABI: unknown'
109+
if not required then
110+
vim.health.warn(message, M.disable(language))
111+
else
112+
vim.health.error(message)
113+
end
116114
end
117-
end
118115

119-
---@private
120-
---@param language string
121-
function M.highlighter(language)
122-
-- create a temporary buffer to check if vim.treesitter.start gets called
123-
local buf = vim.api.nvim_create_buf(false, true)
124-
vim.bo[buf].filetype = language
125-
local ok = vim.treesitter.highlighter.active[buf] ~= nil
126-
vim.api.nvim_buf_delete(buf, { force = true })
127-
if ok then
128-
vim.health.ok(language .. ': highlighter enabled')
129-
else
130-
vim.health.error(language .. ': highlighter not enabled', {
131-
('call vim.treesitter.start on %s buffers'):format(language),
132-
})
116+
if required then
117+
local files = vim.treesitter.query.get_files(language, 'highlights')
118+
if #files > 0 then
119+
for _, file in ipairs(files) do
120+
local path = vim.fn.fnamemodify(file, ':~')
121+
vim.health.ok('highlights: ' .. path)
122+
end
123+
else
124+
vim.health.error('highlights: unknown')
125+
end
126+
end
127+
128+
if active then
129+
-- create a temporary buffer to check if vim.treesitter.start gets called
130+
local buf = vim.api.nvim_create_buf(false, true)
131+
vim.bo[buf].filetype = language
132+
local ok = vim.treesitter.highlighter.active[buf] ~= nil
133+
vim.api.nvim_buf_delete(buf, { force = true })
134+
if ok then
135+
vim.health.ok('highlighter: enabled')
136+
else
137+
vim.health.error('highlighter: not enabled', {
138+
('call vim.treesitter.start on %s buffers'):format(language),
139+
})
140+
end
133141
end
134142
end
135143

scripts/generate.py

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,20 @@
1-
import abc
21
import argparse
32
from dataclasses import dataclass
43
from pathlib import Path
5-
from typing import override
4+
from typing import Protocol
65

76

8-
@dataclass(frozen=True)
9-
class Generator(abc.ABC):
10-
@abc.abstractmethod
11-
def name(self, size: str) -> str:
12-
pass
7+
class Generator(Protocol):
8+
def name(self, size: str) -> str: ...
139

14-
@abc.abstractmethod
15-
def create(self, n: int) -> list[list[str]]:
16-
pass
10+
def create(self, n: int) -> list[list[str]]: ...
1711

1812

1913
@dataclass(frozen=True)
20-
class Heading(Generator):
21-
@override
14+
class Heading:
2215
def name(self, size: str) -> str:
2316
return f"{size}.md"
2417

25-
@override
2618
def create(self, n: int) -> list[list[str]]:
2719
sections: list[list[str]] = []
2820
for i in range(10 * n):
@@ -31,12 +23,10 @@ def create(self, n: int) -> list[list[str]]:
3123

3224

3325
@dataclass(frozen=True)
34-
class Table(Generator):
35-
@override
26+
class Table:
3627
def name(self, size: str) -> str:
3728
return f"{size}-table.md"
3829

39-
@override
4030
def create(self, n: int) -> list[list[str]]:
4131
sections: list[list[str]] = []
4232
for i in range(n // 2):
@@ -46,19 +36,18 @@ def create(self, n: int) -> list[list[str]]:
4636

4737
def table(self, n: int) -> list[str]:
4838
rows: list[str] = []
49-
rows.append(f"| `Column 1` | **Column 2** | *Column 3* |")
50-
rows.append(f"| -------------- | :--------------- | -------------: |")
39+
rows.append("| `Column 1` | **Column 2** | *Column 3* |")
40+
rows.append("| -------------- | :--------------- | -------------: |")
5141
for i in range(n):
5242
rows.append(f"| Row {i:<4} Col 1 | `Row {i:<4} Col 2` | Row {i:<4} Col 3 |")
5343
return rows
5444

5545

5646
def main(force: bool) -> None:
57-
sizes: list[str] = ["small", "medium", "large"]
5847
generators: list[Generator] = [Heading(), Table()]
59-
for i, size in enumerate(sizes):
60-
n: int = 10 ** (i + 1)
61-
for generator in generators:
48+
sizes: dict[str, int] = dict(small=10, medium=100, large=1000)
49+
for generator in generators:
50+
for size, n in sizes.items():
6251
path = Path("temp").joinpath(generator.name(size))
6352
if not path.exists() or force:
6453
sections = generator.create(n)
@@ -67,9 +56,7 @@ def main(force: bool) -> None:
6756

6857

6958
if __name__ == "__main__":
70-
parser = argparse.ArgumentParser(
71-
description="Generate sample data for benchmarking"
72-
)
59+
parser = argparse.ArgumentParser(description="Generate data for benchmarking")
7360
parser.add_argument("--force", action="store_true")
7461
args = parser.parse_args()
7562
main(args.force)

scripts/update.py

Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,26 @@
22
from dataclasses import dataclass, field
33
from pathlib import Path
44
from textwrap import indent
5+
from typing import Protocol
56

67
import tree_sitter_lua
78
import tree_sitter_markdown
89
from tree_sitter import Language, Parser
910

1011

12+
class LuaType(Protocol):
13+
@property
14+
def value(self) -> str: ...
15+
16+
def add(self, value: str) -> None: ...
17+
18+
def name(self) -> str: ...
19+
20+
def to_user(self) -> str | None: ...
21+
22+
def to_str(self) -> str: ...
23+
24+
1125
@dataclass(frozen=True)
1226
class LuaAlias:
1327
value: str
@@ -21,21 +35,17 @@ def name(self) -> str:
2135
return self.value.split()[1]
2236

2337
def to_user(self) -> str | None:
24-
if not self.config():
38+
simple = self.name().split(".")[-1]
39+
if simple != "Configs":
2540
return None
2641

27-
def user(s: str) -> str:
28-
return s.replace(".Config", ".UserConfig")
29-
30-
lines: list[str] = [user(self.value)]
31-
for s in self.options:
32-
option = user(s)
33-
lines.append(option)
42+
lines: list[str] = []
43+
values = [self.value] + self.options
44+
for value in values:
45+
value = value.replace(".Config", ".UserConfig")
46+
lines.append(value)
3447
return "\n".join(lines)
3548

36-
def config(self) -> bool:
37-
return self.name().split(".")[-1] == "Configs"
38-
3949
def to_str(self) -> str:
4050
return "\n".join([self.value] + self.options)
4151

@@ -55,27 +65,22 @@ def name(self) -> str:
5565
return self.value.split(":")[0].split()[-1]
5666

5767
def to_user(self) -> str | None:
58-
if not self.exact() or not self.config():
68+
kind = self.value.split()[1]
69+
simple = self.name().split(".")[-1]
70+
if kind != "(exact)" or simple != "Config":
5971
return None
6072

61-
def user(s: str) -> str:
62-
return s.replace(".Config", ".UserConfig")
63-
64-
lines: list[str] = [user(self.value)]
65-
for s in self.fields:
66-
field = user(s)
67-
name = field.split()[1]
68-
if not name.endswith("?"):
69-
field = field.replace(f" {name} ", f" {name}? ")
70-
lines.append(field)
73+
lines: list[str] = []
74+
values = [self.value] + self.fields
75+
for i, value in enumerate(values):
76+
value = value.replace(".Config", ".UserConfig")
77+
if i > 0:
78+
name = value.split()[1]
79+
if not name.endswith("?"):
80+
value = value.replace(f" {name} ", f" {name}? ")
81+
lines.append(value)
7182
return "\n".join(lines)
7283

73-
def exact(self) -> bool:
74-
return self.value.split()[1] == "(exact)"
75-
76-
def config(self) -> bool:
77-
return self.name().split(".")[-1] == "Config"
78-
7984
def to_str(self) -> str:
8085
return "\n".join([self.value] + self.fields)
8186

@@ -91,14 +96,14 @@ def update_types(root: Path) -> None:
9196
files: list[Path] = [root.joinpath("init.lua")]
9297
files.extend(sorted(root.joinpath("config").iterdir()))
9398

94-
classes: list[str] = ["---@meta"]
95-
for definition in get_definitions(files):
96-
user = definition.to_user()
99+
sections: list[str] = ["---@meta"]
100+
for lua_type in get_lua_types(files):
101+
user = lua_type.to_user()
97102
if user is not None:
98-
classes.append(user)
103+
sections.append(user)
99104

100105
types = root.joinpath("types.lua")
101-
types.write_text("\n\n".join(classes) + "\n")
106+
types.write_text("\n\n".join(sections) + "\n")
102107

103108

104109
def update_readme(root: Path) -> None:
@@ -148,7 +153,7 @@ def update_handlers(root: Path) -> None:
148153
root.joinpath("config/handlers.lua"),
149154
root.joinpath("lib/marks.lua"),
150155
]
151-
name_lua = {lua.name(): lua for lua in get_definitions(files)}
156+
lua_types = {lua_type.name(): lua_type for lua_type in get_lua_types(files)}
152157
names = [
153158
"render.md.Handler",
154159
"render.md.handler.Context",
@@ -158,17 +163,17 @@ def update_handlers(root: Path) -> None:
158163
"render.md.mark.Text",
159164
"render.md.mark.Hl",
160165
]
161-
definitions = [name_lua[name] for name in names]
166+
sections = [lua_types[name].to_str() for name in names]
162167

163168
handlers = Path("doc/custom-handlers.md")
164-
old = get_code_block(handlers, definitions[0].value, 1)
165-
new = "\n".join([lua.to_str() + "\n" for lua in definitions])
169+
old = get_code_block(handlers, names[0], 1)
170+
new = "\n\n".join(sections) + "\n"
166171
text = handlers.read_text().replace(old, new)
167172
handlers.write_text(text)
168173

169174

170-
def get_definitions(files: list[Path]) -> list[LuaAlias | LuaClass]:
171-
result: list[LuaAlias | LuaClass] = []
175+
def get_lua_types(files: list[Path]) -> list[LuaType]:
176+
result: list[LuaType] = []
172177
for file in files:
173178
for comment in get_comments(file):
174179
# ---@class md.Init: md.Api -> class
@@ -178,12 +183,12 @@ def get_definitions(files: list[Path]) -> list[LuaAlias | LuaClass]:
178183
# ---@type md.Config -> type
179184
# ---@param opts? md.UserConfig -> param
180185
# -- Inlined with 'image' elements -> --
181-
annotation = comment.split()[0].split("@")[-1]
182-
if annotation == "alias":
186+
kind = comment.split()[0].split("@")[-1]
187+
if kind == "alias":
183188
result.append(LuaAlias(comment))
184-
elif annotation == "class":
189+
elif kind == "class":
185190
result.append(LuaClass(comment))
186-
elif annotation in ["field", "---|"]:
191+
elif kind in ["field", "---|"]:
187192
result[-1].add(comment)
188193
return result
189194

0 commit comments

Comments
 (0)