@willbooster/tree-sitter-markdown 0.0.0-placeholder.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,13 +1,77 @@
1
1
  {
2
2
  "name": "@willbooster/tree-sitter-markdown",
3
- "version": "0.0.0-placeholder.0",
4
- "description": "Placeholder for WillBooster's fork of tree-sitter-markdown; the first release is 1.0.0",
3
+ "version": "1.0.0",
4
+ "description": "Markdown grammar for tree-sitter",
5
+ "keywords": [
6
+ "incremental",
7
+ "parsing",
8
+ "tree-sitter",
9
+ "markdown"
10
+ ],
5
11
  "repository": {
6
12
  "type": "git",
7
13
  "url": "git+https://github.com/WillBooster/tree-sitter-markdown.git"
8
14
  },
9
15
  "license": "MIT",
10
16
  "author": "WillBooster Inc.",
17
+ "files": [
18
+ "common/common.js",
19
+ "common/html_entities.json",
20
+ "tree-sitter-markdown/grammar.js",
21
+ "tree-sitter-markdown-inline/grammar.js",
22
+ "tree-sitter-markdown/queries/*.scm",
23
+ "tree-sitter-markdown-inline/queries/*.scm",
24
+ "tree-sitter-markdown/src/node-types.json",
25
+ "tree-sitter-markdown-inline/src/node-types.json",
26
+ "tree-sitter.json",
27
+ "*.wasm"
28
+ ],
29
+ "scripts": {
30
+ "build-wasm": "script/generate --build-wasm",
31
+ "build/ci": "bun run build-wasm",
32
+ "cleanup": "bun wb lint --fix --format",
33
+ "format": "bun wb lint --format",
34
+ "generate": "script/generate",
35
+ "lint": "bun wb lint",
36
+ "lint-fix": "bun wb lint --fix",
37
+ "prepare": "(bun lefthook install || true) && (test ! -f .lefthook/normalize-bun-lockfile.sh || bash .lefthook/normalize-bun-lockfile.sh)",
38
+ "release": "wb release",
39
+ "prestart": "bun run build-wasm",
40
+ "start": "script/tree-sitter playground",
41
+ "test": "bun wb test",
42
+ "test/ci": "bun wb test-on-ci",
43
+ "test/ci-setup": "playwright install --with-deps chromium",
44
+ "typecheck": "bun wb typecheck",
45
+ "verify": "bun wb verify",
46
+ "verify-full": "bun wb verify --full"
47
+ },
48
+ "devDependencies": {
49
+ "@tsconfig/bun": "1.0.11",
50
+ "@types/bun": "1.4.2",
51
+ "@vitest/browser-playwright": "5.0.3",
52
+ "@willbooster/oxfmt-config": "1.2.2",
53
+ "@willbooster/oxlint-config": "1.4.8",
54
+ "@willbooster/wb": "22.26.1",
55
+ "@willbooster/web-tree-sitter": "2.0.28",
56
+ "conventional-changelog-conventionalcommits": "10.4.0",
57
+ "lefthook": "2.2.1",
58
+ "oxfmt": "0.72.0",
59
+ "oxlint": "1.87.0",
60
+ "oxlint-tsgolint": "7.0.2004",
61
+ "playwright": "1.63.0",
62
+ "semantic-release": "25.0.9",
63
+ "sort-package-json": "4.0.0",
64
+ "vitest": "5.0.3",
65
+ "wrangler": "4.147.0"
66
+ },
67
+ "peerDependencies": {
68
+ "@willbooster/web-tree-sitter": "^2.0.0"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "@willbooster/web-tree-sitter": {
72
+ "optional": true
73
+ }
74
+ },
11
75
  "publishConfig": {
12
76
  "access": "public",
13
77
  "registry": "https://registry.npmjs.org/"
@@ -0,0 +1,611 @@
1
+ // This grammar only concerns the block structure according to the CommonMark Spec
2
+ // (https://spec.commonmark.org/0.30/#blocks-and-inlines)
3
+ // For more information see README.md
4
+
5
+ /// <reference types="tree-sitter-cli/dsl" />
6
+
7
+ const common = require('../common/common');
8
+
9
+ const PRECEDENCE_LEVEL_LINK = common.PRECEDENCE_LEVEL_LINK;
10
+
11
+ const PUNCTUATION_CHARACTERS_REGEX = '!-/:-@\\[-`\\{-~';
12
+
13
+ module.exports = grammar({
14
+ name: 'markdown',
15
+
16
+ rules: {
17
+ document: $ => seq(
18
+ optional(choice(
19
+ common.EXTENSION_MINUS_METADATA ? $.minus_metadata : choice(),
20
+ common.EXTENSION_PLUS_METADATA ? $.plus_metadata : choice(),
21
+ )),
22
+ alias(prec.right(repeat($._block_not_section)), $.section),
23
+ repeat($.section),
24
+ ),
25
+
26
+ ...common.rules,
27
+ _last_token_punctuation: $ => choice(), // needed for compatability with common rules
28
+
29
+ // BLOCK STRUCTURE
30
+
31
+ // All blocks. Every block contains a trailing newline.
32
+ _block: $ => choice(
33
+ $._block_not_section,
34
+ $.section,
35
+ ),
36
+ _block_not_section: $ => choice(
37
+ alias($._setext_heading1, $.setext_heading),
38
+ alias($._setext_heading2, $.setext_heading),
39
+ $.paragraph,
40
+ $.indented_code_block,
41
+ $.block_quote,
42
+ $.thematic_break,
43
+ $.list,
44
+ $.fenced_code_block,
45
+ $._blank_line,
46
+ $.html_block,
47
+ $.link_reference_definition,
48
+ common.EXTENSION_PIPE_TABLE ? $.pipe_table : choice(),
49
+ ),
50
+ section: $ => choice($._section1, $._section2, $._section3, $._section4, $._section5, $._section6),
51
+ _section1: $ => prec.right(seq(
52
+ alias($._atx_heading1, $.atx_heading),
53
+ repeat(choice(
54
+ alias(choice($._section6, $._section5, $._section4, $._section3, $._section2), $.section),
55
+ $._block_not_section
56
+ ))
57
+ )),
58
+ _section2: $ => prec.right(seq(
59
+ alias($._atx_heading2, $.atx_heading),
60
+ repeat(choice(
61
+ alias(choice($._section6, $._section5, $._section4, $._section3), $.section),
62
+ $._block_not_section
63
+ ))
64
+ )),
65
+ _section3: $ => prec.right(seq(
66
+ alias($._atx_heading3, $.atx_heading),
67
+ repeat(choice(
68
+ alias(choice($._section6, $._section5, $._section4), $.section),
69
+ $._block_not_section
70
+ ))
71
+ )),
72
+ _section4: $ => prec.right(seq(
73
+ alias($._atx_heading4, $.atx_heading),
74
+ repeat(choice(
75
+ alias(choice($._section6, $._section5), $.section),
76
+ $._block_not_section
77
+ ))
78
+ )),
79
+ _section5: $ => prec.right(seq(
80
+ alias($._atx_heading5, $.atx_heading),
81
+ repeat(choice(
82
+ alias($._section6, $.section),
83
+ $._block_not_section
84
+ ))
85
+ )),
86
+ _section6: $ => prec.right(seq(
87
+ alias($._atx_heading6, $.atx_heading),
88
+ repeat($._block_not_section)
89
+ )),
90
+
91
+ // LEAF BLOCKS
92
+
93
+ // A thematic break. This is currently handled by the external scanner but maybe could be
94
+ // parsed using normal tree-sitter rules.
95
+ //
96
+ // https://github.github.com/gfm/#thematic-breaks
97
+ thematic_break: $ => seq($._thematic_break, choice($._newline, $._eof)),
98
+
99
+ // An ATX heading. This is currently handled by the external scanner but maybe could be
100
+ // parsed using normal tree-sitter rules.
101
+ //
102
+ // https://github.github.com/gfm/#atx-headings
103
+ _atx_heading1: $ => prec(1, seq(
104
+ $.atx_h1_marker,
105
+ optional($._atx_heading_content),
106
+ $._newline
107
+ )),
108
+ _atx_heading2: $ => prec(1, seq(
109
+ $.atx_h2_marker,
110
+ optional($._atx_heading_content),
111
+ $._newline
112
+ )),
113
+ _atx_heading3: $ => prec(1, seq(
114
+ $.atx_h3_marker,
115
+ optional($._atx_heading_content),
116
+ $._newline
117
+ )),
118
+ _atx_heading4: $ => prec(1, seq(
119
+ $.atx_h4_marker,
120
+ optional($._atx_heading_content),
121
+ $._newline
122
+ )),
123
+ _atx_heading5: $ => prec(1, seq(
124
+ $.atx_h5_marker,
125
+ optional($._atx_heading_content),
126
+ $._newline
127
+ )),
128
+ _atx_heading6: $ => prec(1, seq(
129
+ $.atx_h6_marker,
130
+ optional($._atx_heading_content),
131
+ $._newline
132
+ )),
133
+ _atx_heading_content: $ => prec(1, seq(
134
+ optional($._whitespace),
135
+ field('heading_content', alias($._line, $.inline))
136
+ )),
137
+
138
+ // A setext heading. The underlines are currently handled by the external scanner but maybe
139
+ // could be parsed using normal tree-sitter rules.
140
+ //
141
+ // https://github.github.com/gfm/#setext-headings
142
+ _setext_heading1: $ => seq(
143
+ field('heading_content', $.paragraph),
144
+ $.setext_h1_underline,
145
+ choice($._newline, $._eof),
146
+ ),
147
+ _setext_heading2: $ => seq(
148
+ field('heading_content', $.paragraph),
149
+ $.setext_h2_underline,
150
+ choice($._newline, $._eof),
151
+ ),
152
+
153
+ // An indented code block. An indented code block is made up of indented chunks and blank
154
+ // lines. The indented chunks are handeled by the external scanner.
155
+ //
156
+ // https://github.github.com/gfm/#indented-code-blocks
157
+ indented_code_block: $ => prec.right(seq($._indented_chunk, repeat(choice($._indented_chunk, $._blank_line)))),
158
+ _indented_chunk: $ => seq($._indented_chunk_start, repeat(choice($._line, $._newline)), $._block_close, optional($.block_continuation)),
159
+
160
+ // A fenced code block. Fenced code blocks are mainly handled by the external scanner. In
161
+ // case of backtick code blocks the external scanner also checks that the info string is
162
+ // proper.
163
+ //
164
+ // https://github.github.com/gfm/#fenced-code-blocks
165
+ fenced_code_block: $ => prec.right(choice(
166
+ seq(
167
+ alias($._fenced_code_block_start_backtick, $.fenced_code_block_delimiter),
168
+ optional($._whitespace),
169
+ optional($.info_string),
170
+ $._newline,
171
+ optional($.code_fence_content),
172
+ optional(seq(alias($._fenced_code_block_end_backtick, $.fenced_code_block_delimiter), $._close_block, $._newline)),
173
+ $._block_close,
174
+ ),
175
+ seq(
176
+ alias($._fenced_code_block_start_tilde, $.fenced_code_block_delimiter),
177
+ optional($._whitespace),
178
+ optional($.info_string),
179
+ $._newline,
180
+ optional($.code_fence_content),
181
+ optional(seq(alias($._fenced_code_block_end_tilde, $.fenced_code_block_delimiter), $._close_block, $._newline)),
182
+ $._block_close,
183
+ ),
184
+ )),
185
+ code_fence_content: $ => repeat1(choice($._newline, $._line)),
186
+ info_string: $ => choice(
187
+ seq($.language, repeat(choice($._line, $.backslash_escape, $.entity_reference, $.numeric_character_reference))),
188
+ seq(
189
+ repeat1(choice('{', '}')),
190
+ optional(choice(
191
+ seq($.language, repeat(choice($._line, $.backslash_escape, $.entity_reference, $.numeric_character_reference))),
192
+ seq($._whitespace, repeat(choice($._line, $.backslash_escape, $.entity_reference, $.numeric_character_reference))),
193
+ ))
194
+ )
195
+ ),
196
+ language: $ => prec.right(repeat1(choice($._word, common.punctuation_without($, ['{', '}', ',']), $.backslash_escape, $.entity_reference, $.numeric_character_reference))),
197
+
198
+ // An HTML block. We do not emit addition nodes relating to the kind or structure or of the
199
+ // html block as this is best done using language injections and a proper html parsers.
200
+ //
201
+ // See the `build_html_block` function for more information.
202
+ // See the spec for the different kinds of html blocks.
203
+ //
204
+ // https://github.github.com/gfm/#html-blocks
205
+ html_block: $ => prec(1, seq(optional($._whitespace), choice(
206
+ $._html_block_1,
207
+ $._html_block_2,
208
+ $._html_block_3,
209
+ $._html_block_4,
210
+ $._html_block_5,
211
+ $._html_block_6,
212
+ $._html_block_7,
213
+ ))),
214
+ _html_block_1: $ => build_html_block($,
215
+ // new RegExp(
216
+ // '[ \t]*<' + regex_case_insensitive_list(HTML_TAG_NAMES_RULE_1) + '([\\r\\n]|[ \\t>][^<\\r\\n]*(\\n|\\r\\n?)?)'
217
+ // ),
218
+ $._html_block_1_start,
219
+ $._html_block_1_end,
220
+ true
221
+ ),
222
+ _html_block_2: $ => build_html_block($, $._html_block_2_start, '-->', true),
223
+ _html_block_3: $ => build_html_block($, $._html_block_3_start, '?>', true),
224
+ _html_block_4: $ => build_html_block($, $._html_block_4_start, '>', true),
225
+ _html_block_5: $ => build_html_block($, $._html_block_5_start, ']]>', true),
226
+ _html_block_6: $ => build_html_block(
227
+ $,
228
+ $._html_block_6_start,
229
+ seq($._newline, $._blank_line),
230
+ true
231
+ ),
232
+ _html_block_7: $ => build_html_block(
233
+ $,
234
+ $._html_block_7_start,
235
+ seq($._newline, $._blank_line),
236
+ false
237
+ ),
238
+
239
+ // A link reference definition. We need to make sure that this is not mistaken for a
240
+ // paragraph or indented chunk. The `$._no_indented_chunk` token is used to tell the
241
+ // external scanner not to allow indented chunks when the `$.link_title` of the link
242
+ // reference definition would be valid.
243
+ //
244
+ // https://github.github.com/gfm/#link-reference-definitions
245
+ link_reference_definition: $ => prec.dynamic(PRECEDENCE_LEVEL_LINK, seq(
246
+ optional($._whitespace),
247
+ $.link_label,
248
+ ':',
249
+ optional(seq(optional($._whitespace), optional(seq($._soft_line_break, optional($._whitespace))))),
250
+ $.link_destination,
251
+ optional(prec.dynamic(2 * PRECEDENCE_LEVEL_LINK, seq(
252
+ choice(
253
+ seq($._whitespace, optional(seq($._soft_line_break, optional($._whitespace)))),
254
+ seq($._soft_line_break, optional($._whitespace)),
255
+ ),
256
+ optional($._no_indented_chunk),
257
+ $.link_title
258
+ ))),
259
+ choice($._newline, $._soft_line_break, $._eof),
260
+ )),
261
+ _text_inline_no_link: $ => choice($._word, $._whitespace, common.punctuation_without($, ['[', ']'])),
262
+
263
+ // A paragraph. The parsing tactic for deciding when a paragraph ends is as follows:
264
+ // on every newline inside a paragraph a conflict is triggered manually using
265
+ // `$._split_token` to split the parse state into two branches.
266
+ //
267
+ // One of them - the one that also contains a `$._soft_line_break_marker` will try to
268
+ // continue the paragraph, but we make sure that the beginning of a new block that can
269
+ // interrupt a paragraph can also be parsed. If this is the case we know that the paragraph
270
+ // should have been closed and the external parser will emit an `$._error` to kill the parse
271
+ // branch.
272
+ //
273
+ // The other parse branch consideres the paragraph to be over. It will be killed if no valid new
274
+ // block is detected before the next newline. (For example it will also be killed if a indented
275
+ // code block is detected, which cannot interrupt paragraphs).
276
+ //
277
+ // Either way, after the next newline only one branch will exist, so the ammount of branches
278
+ // related to paragraphs ending does not grow.
279
+ //
280
+ // https://github.github.com/gfm/#paragraphs
281
+ paragraph: $ => seq(alias(repeat1(choice($._line, $._soft_line_break)), $.inline), choice($._newline, $._eof)),
282
+
283
+ // A blank line including the following newline.
284
+ //
285
+ // https://github.github.com/gfm/#blank-lines
286
+ _blank_line: $ => seq($._blank_line_start, choice($._newline, $._eof)),
287
+
288
+
289
+ // CONTAINER BLOCKS
290
+
291
+ // A block quote. This is the most basic example of a container block handled by the
292
+ // external scanner.
293
+ //
294
+ // https://github.github.com/gfm/#block-quotes
295
+ block_quote: $ => seq(
296
+ alias($._block_quote_start, $.block_quote_marker),
297
+ optional($.block_continuation),
298
+ repeat($._block),
299
+ $._block_close,
300
+ optional($.block_continuation)
301
+ ),
302
+
303
+ // A list. This grammar does not differentiate between loose and tight lists for efficiency
304
+ // reasons.
305
+ //
306
+ // Lists can only contain list items with list markers of the same type. List items are
307
+ // handled by the external scanner.
308
+ //
309
+ // https://github.github.com/gfm/#lists
310
+ list: $ => prec.right(choice(
311
+ $._list_plus,
312
+ $._list_minus,
313
+ $._list_star,
314
+ $._list_dot,
315
+ $._list_parenthesis
316
+ )),
317
+ _list_plus: $ => prec.right(repeat1(alias($._list_item_plus, $.list_item))),
318
+ _list_minus: $ => prec.right(repeat1(alias($._list_item_minus, $.list_item))),
319
+ _list_star: $ => prec.right(repeat1(alias($._list_item_star, $.list_item))),
320
+ _list_dot: $ => prec.right(repeat1(alias($._list_item_dot, $.list_item))),
321
+ _list_parenthesis: $ => prec.right(repeat1(alias($._list_item_parenthesis, $.list_item))),
322
+ // Some list items can not interrupt a paragraph and are marked as such by the external
323
+ // scanner.
324
+ list_marker_plus: $ => choice($._list_marker_plus, $._list_marker_plus_dont_interrupt),
325
+ list_marker_minus: $ => choice($._list_marker_minus, $._list_marker_minus_dont_interrupt),
326
+ list_marker_star: $ => choice($._list_marker_star, $._list_marker_star_dont_interrupt),
327
+ list_marker_dot: $ => choice($._list_marker_dot, $._list_marker_dot_dont_interrupt),
328
+ list_marker_parenthesis: $ => choice($._list_marker_parenthesis, $._list_marker_parenthesis_dont_interrupt),
329
+ _list_item_plus: $ => seq(
330
+ $.list_marker_plus,
331
+ optional($.block_continuation),
332
+ $._list_item_content,
333
+ $._block_close,
334
+ optional($.block_continuation)
335
+ ),
336
+ _list_item_minus: $ => seq(
337
+ $.list_marker_minus,
338
+ optional($.block_continuation),
339
+ $._list_item_content,
340
+ $._block_close,
341
+ optional($.block_continuation)
342
+ ),
343
+ _list_item_star: $ => seq(
344
+ $.list_marker_star,
345
+ optional($.block_continuation),
346
+ $._list_item_content,
347
+ $._block_close,
348
+ optional($.block_continuation)
349
+ ),
350
+ _list_item_dot: $ => seq(
351
+ $.list_marker_dot,
352
+ optional($.block_continuation),
353
+ $._list_item_content,
354
+ $._block_close,
355
+ optional($.block_continuation)
356
+ ),
357
+ _list_item_parenthesis: $ => seq(
358
+ $.list_marker_parenthesis,
359
+ optional($.block_continuation),
360
+ $._list_item_content,
361
+ $._block_close,
362
+ optional($.block_continuation)
363
+ ),
364
+ // List items are closed after two consecutive blank lines
365
+ _list_item_content: $ => choice(
366
+ prec(1, seq(
367
+ $._blank_line,
368
+ $._blank_line,
369
+ $._close_block,
370
+ optional($.block_continuation)
371
+ )),
372
+ repeat1($._block),
373
+ common.EXTENSION_TASK_LIST ? prec(1, seq(
374
+ choice($.task_list_marker_checked, $.task_list_marker_unchecked),
375
+ $._whitespace,
376
+ $.paragraph,
377
+ repeat($._block)
378
+ )) : choice()
379
+ ),
380
+
381
+ // Newlines as in the spec. Parsing a newline triggers the matching process by making
382
+ // the external parser emit a `$._line_ending`.
383
+ _newline: $ => seq(
384
+ $._line_ending,
385
+ optional($.block_continuation)
386
+ ),
387
+ _soft_line_break: $ => seq(
388
+ $._soft_line_ending,
389
+ optional($.block_continuation)
390
+ ),
391
+ // Some symbols get parsed as single tokens so that html blocks get detected properly
392
+ _line: $ => prec.right(repeat1(choice($._word, $._whitespace, common.punctuation_without($, [])))),
393
+ _word: $ => choice(
394
+ new RegExp('[^' + PUNCTUATION_CHARACTERS_REGEX + ' \\t\\n\\r]+'),
395
+ common.EXTENSION_TASK_LIST ? choice(
396
+ /\[[xX]\]/,
397
+ /\[[ \t]\]/,
398
+ ) : choice()
399
+ ),
400
+ // The external scanner emits some characters that should just be ignored.
401
+ _whitespace: $ => /[ \t]+/,
402
+
403
+ ...(common.EXTENSION_TASK_LIST ? {
404
+ task_list_marker_checked: $ => prec(1, /\[[xX]\]/),
405
+ task_list_marker_unchecked: $ => prec(1, /\[[ \t]\]/),
406
+ } : {}),
407
+
408
+ ...(common.EXTENSION_PIPE_TABLE ? {
409
+ pipe_table: $ => prec.right(seq(
410
+ $._pipe_table_start,
411
+ alias($.pipe_table_row, $.pipe_table_header),
412
+ $._newline,
413
+ $.pipe_table_delimiter_row,
414
+ repeat(seq($._pipe_table_newline, optional($.pipe_table_row))),
415
+ choice($._newline, $._eof),
416
+ )),
417
+
418
+ _pipe_table_newline: $ => seq(
419
+ $._pipe_table_line_ending,
420
+ optional($.block_continuation)
421
+ ),
422
+
423
+ pipe_table_delimiter_row: $ => seq(
424
+ optional(seq(
425
+ optional($._whitespace),
426
+ '|',
427
+ )),
428
+ repeat1(prec.right(seq(
429
+ optional($._whitespace),
430
+ $.pipe_table_delimiter_cell,
431
+ optional($._whitespace),
432
+ '|',
433
+ ))),
434
+ optional($._whitespace),
435
+ optional(seq(
436
+ $.pipe_table_delimiter_cell,
437
+ optional($._whitespace)
438
+ )),
439
+ ),
440
+
441
+ pipe_table_delimiter_cell: $ => seq(
442
+ optional(alias(':', $.pipe_table_align_left)),
443
+ repeat1('-'),
444
+ optional(alias(':', $.pipe_table_align_right)),
445
+ ),
446
+
447
+ pipe_table_row: $ => seq(
448
+ optional(seq(
449
+ optional($._whitespace),
450
+ '|',
451
+ )),
452
+ choice(
453
+ seq(
454
+ repeat1(prec.right(seq(
455
+ choice(
456
+ seq(
457
+ optional($._whitespace),
458
+ $.pipe_table_cell,
459
+ optional($._whitespace)
460
+ ),
461
+ alias($._whitespace, $.pipe_table_cell)
462
+ ),
463
+ '|',
464
+ ))),
465
+ optional($._whitespace),
466
+ optional(seq(
467
+ $.pipe_table_cell,
468
+ optional($._whitespace)
469
+ )),
470
+ ),
471
+ seq(
472
+ optional($._whitespace),
473
+ $.pipe_table_cell,
474
+ optional($._whitespace)
475
+ )
476
+ ),
477
+ ),
478
+
479
+ pipe_table_cell: $ => prec.right(seq(
480
+ choice(
481
+ $._word,
482
+ $._backslash_escape,
483
+ common.punctuation_without($, ['|']),
484
+ ),
485
+ repeat(choice(
486
+ $._word,
487
+ $._whitespace,
488
+ $._backslash_escape,
489
+ common.punctuation_without($, ['|']),
490
+ )),
491
+ )),
492
+ } : {}),
493
+ },
494
+
495
+ externals: $ => [
496
+ // Quite a few of these tokens could maybe be implemented without use of the external parser.
497
+ // For this the `$._open_block` and `$._close_block` tokens should be used to tell the external
498
+ // parser to put a new anonymous block on the block stack.
499
+
500
+ // Block structure gets parsed as follows: After every newline (`$._line_ending`) we try to match
501
+ // as many open blocks as possible. For example if the last line was part of a block quote we look
502
+ // for a `>` at the beginning of the next line. We emit a `$.block_continuation` for each matched
503
+ // block. For this process the external scanner keeps a stack of currently open blocks.
504
+ //
505
+ // If we are not able to match all blocks that does not necessarily mean that all unmatched blocks
506
+ // have to be closed. It could also mean that the line is a lazy continuation line
507
+ // (https://github.github.com/gfm/#lazy-continuation-line, see also `$._split_token` and
508
+ // `$._soft_line_break_marker` below)
509
+ //
510
+ // If a block does get closed (because it was not matched or because some closing token was
511
+ // encountered) we emit a `$._block_close` token
512
+
513
+ $._line_ending, // this token does not contain the actual newline characters. see `$._newline`
514
+ $._soft_line_ending,
515
+ $._block_close,
516
+ $.block_continuation,
517
+
518
+ // Tokens signifying the start of a block. Blocks that do not need a `$._block_close` because they
519
+ // always span one line are marked as such.
520
+
521
+ $._block_quote_start,
522
+ $._indented_chunk_start,
523
+ $.atx_h1_marker, // atx headings do not need a `$._block_close`
524
+ $.atx_h2_marker,
525
+ $.atx_h3_marker,
526
+ $.atx_h4_marker,
527
+ $.atx_h5_marker,
528
+ $.atx_h6_marker,
529
+ $.setext_h1_underline, // setext headings do not need a `$._block_close`
530
+ $.setext_h2_underline,
531
+ $._thematic_break, // thematic breaks do not need a `$._block_close`
532
+ $._list_marker_minus,
533
+ $._list_marker_plus,
534
+ $._list_marker_star,
535
+ $._list_marker_parenthesis,
536
+ $._list_marker_dot,
537
+ $._list_marker_minus_dont_interrupt, // list items that do not interrupt an ongoing paragraph
538
+ $._list_marker_plus_dont_interrupt,
539
+ $._list_marker_star_dont_interrupt,
540
+ $._list_marker_parenthesis_dont_interrupt,
541
+ $._list_marker_dot_dont_interrupt,
542
+ $._fenced_code_block_start_backtick,
543
+ $._fenced_code_block_start_tilde,
544
+ $._blank_line_start, // Does not contain the newline characters. Blank lines do not need a `$._block_close`
545
+
546
+ // Special tokens for block structure
547
+
548
+ // Closing backticks or tildas for a fenced code block. They are used to trigger a `$._close_block`
549
+ // which in turn will trigger a `$._block_close` at the beginning the following line.
550
+ $._fenced_code_block_end_backtick,
551
+ $._fenced_code_block_end_tilde,
552
+
553
+ $._html_block_1_start,
554
+ $._html_block_1_end,
555
+ $._html_block_2_start,
556
+ $._html_block_3_start,
557
+ $._html_block_4_start,
558
+ $._html_block_5_start,
559
+ $._html_block_6_start,
560
+ $._html_block_7_start,
561
+
562
+ // Similarly this is used if the closing of a block is not decided by the external parser.
563
+ // A `$._block_close` will be emitted at the beginning of the next line. Notice that a
564
+ // `$._block_close` can also get emitted if the parent block closes.
565
+ $._close_block,
566
+
567
+ // This is a workaround so the external parser does not try to open indented blocks when
568
+ // parsing a link reference definition.
569
+ $._no_indented_chunk,
570
+
571
+ // An `$._error` token is never valid and gets emmited to kill invalid parse branches. Concretely
572
+ // this is used to decide wether a newline closes a paragraph and together and it gets emitted
573
+ // when trying to parse the `$._trigger_error` token in `$.link_title`.
574
+ $._error,
575
+ $._trigger_error,
576
+ $._eof,
577
+
578
+ $.minus_metadata,
579
+ $.plus_metadata,
580
+
581
+ $._pipe_table_start,
582
+ $._pipe_table_line_ending,
583
+ ],
584
+ precedences: $ => [
585
+ [$._setext_heading1, $._block],
586
+ [$._setext_heading2, $._block],
587
+ [$.indented_code_block, $._block],
588
+ ],
589
+ conflicts: $ => [
590
+ [$.link_reference_definition],
591
+ [$.link_label, $._line],
592
+ [$.link_reference_definition, $._line],
593
+ ],
594
+ extras: $ => [],
595
+ });
596
+
597
+ // General purpose structure for html blocks. The different kinds mostly work the same but have
598
+ // different openling and closing conditions. Some html blocks may not interrupt a paragraph and
599
+ // have to be marked as such.
600
+ function build_html_block($, open, close, interrupt_paragraph) {
601
+ return seq(
602
+ open,
603
+ repeat(choice(
604
+ $._line,
605
+ $._newline,
606
+ seq(close, $._close_block),
607
+ )),
608
+ $._block_close,
609
+ optional($.block_continuation),
610
+ );
611
+ }