Skip to content

Commit 240edcb

Browse files
committed
feat: better structured headings
Problems: Header titles cannot be extracted easily (e.g., for generating a table of contents). Solution: Expose nodes for separators (`separator`) and actual heading text (`heading`).
1 parent b711df7 commit 240edcb

File tree

7 files changed

+5324
-5189
lines changed

7 files changed

+5324
-5189
lines changed

grammar.js

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
// the grammar as a String instead of a RegExp.
44
// - Rule Order: Tree-sitter will prefer the token that appears earlier in the
55
// grammar.
6-
//
6+
// - Visibility: Prefer JS regex (/\n/) over literals ('\n') unless it should be
7+
// exposed to queries as an anonymous node.
78
// https://tree-sitter.github.io/tree-sitter/creating-parsers
89
// - Rules starting with underscore are hidden in the syntax tree.
910

@@ -16,6 +17,11 @@ const _li_token = /[-•][ ]+/;
1617
module.exports = grammar({
1718
name: 'vimdoc',
1819

20+
conflicts: $ => [
21+
[$._line_noli, $._column_heading],
22+
[$._column_heading],
23+
],
24+
1925
extras: () => [/[\t ]/],
2026

2127
// inline: ($) => [
@@ -135,14 +141,14 @@ module.exports = grammar({
135141
'>',
136142
choice(
137143
alias(token.immediate(/[a-z0-9]+\n/), $.language),
138-
token.immediate('\n')),
144+
token.immediate(/\n/)),
139145
alias(repeat1(alias($.line_code, $.line)), $.code),
140146
// Codeblock ends if a line starts with non-whitespace.
141147
// Terminating "<" is consumed in other rules.
142148
)),
143149

144150
// Lines.
145-
_blank: () => field('blank', '\n'),
151+
_blank: () => field('blank', /\n/),
146152
line: ($) => choice(
147153
$.column_heading,
148154
$.h1,
@@ -156,18 +162,18 @@ module.exports = grammar({
156162
optional(token.immediate('<')), // Treat codeblock-terminating "<" as whitespace.
157163
_li_token,
158164
choice(
159-
alias(seq(repeat1($._atom), '\n'), $.line),
165+
alias(seq(repeat1($._atom), /\n/), $.line),
160166
seq(alias(repeat1($._atom), $.line), $.codeblock),
161167
),
162168
repeat(alias($._line_noli, $.line)),
163169
)),
164170
// Codeblock lines: must be indented by at least 1 space/tab.
165171
// Line content (incl. whitespace) is captured as a single atom.
166-
line_code: () => choice('\n', /[\t ]+[^\n]+\n/),
172+
line_code: () => choice(/\n/, /[\t ]+[^\n]+\n/),
167173
_line_noli: ($) => seq(
168174
choice($._atom_noli, $._uppercase_words),
169175
repeat($._atom),
170-
choice($.codeblock, '\n')
176+
choice($.codeblock, /\n/)
171177
),
172178

173179
// Modeline: must start with "vim:" (optionally preceded by whitespace)
@@ -177,31 +183,38 @@ module.exports = grammar({
177183
// Intended for table column names per `:help help-writing`.
178184
// TODO: children should be $.word (plaintext), not $.atom.
179185
column_heading: ($) => seq(
180-
field('name', seq(choice($._atom_noli, $._uppercase_words), repeat($._atom))),
181-
'~',
182-
token.immediate('\n'),
186+
alias($._column_heading, $.heading),
187+
alias('~', $.delimiter),
188+
token.immediate(/\n/),
183189
),
190+
// aliasing a seq exposes every item separately: create hidden rule and alias that
191+
_column_heading: $ => prec.dynamic(1, seq(
192+
choice($._atom_noli, $._uppercase_words),
193+
repeat($._atom)
194+
)),
184195

185196
h1: ($) =>
186-
seq(
187-
token.immediate(field('delimiter', /============+[\t ]*\n/)),
188-
repeat1($._atom),
189-
'\n',
190-
),
197+
prec(1, seq(
198+
alias(token.immediate(/============+[\t ]*\n/), $.delimiter),
199+
alias(repeat1($._atom), $.heading),
200+
optional(seq($.tag, repeat($._atom))),
201+
/\n/,
202+
)),
191203

192204
h2: ($) =>
193-
seq(
194-
token.immediate(field('delimiter', /------------+[\t ]*\n/)),
195-
repeat1($._atom),
196-
'\n',
197-
),
205+
prec(1, seq(
206+
alias(token.immediate(/------------+[\t ]*\n/), $.delimiter),
207+
alias(repeat1($._atom), $.heading),
208+
optional(seq($.tag, repeat($._atom))),
209+
/\n/,
210+
)),
198211

199212
// Heading 3: UPPERCASE NAME, followed by optional *tags*.
200213
h3: ($) =>
201214
seq(
202-
field('name', $.uppercase_name),
215+
alias($.uppercase_name, $.heading),
203216
optional(seq($.tag, repeat($._atom))),
204-
'\n',
217+
/\n/,
205218
),
206219

207220
tag: ($) => _word($,

queries/vimdoc/highlights.scm

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1-
(h1) @markup.heading.1
1+
(h1
2+
(delimiter) @markup.heading.1
3+
(heading) @markup.heading.1)
24

3-
(h2) @markup.heading.2
5+
(h2
6+
(delimiter) @markup.heading.2
7+
(heading) @markup.heading.2)
48

5-
(h3) @markup.heading.3
9+
(h3
10+
(heading) @markup.heading.3)
611

7-
(column_heading) @markup.heading.4
12+
(column_heading
13+
(heading) @markup.heading.4)
814

915
(column_heading
10-
"~" @markup.heading.4.marker
16+
(delimiter) @markup.heading.4.marker
1117
(#set! conceal ""))
1218

1319
(tag

0 commit comments

Comments
 (0)